Jump to content
Search Community

Animating to/from backgroundSize: "contain" or "cover"

GreenSock test
Moderator Tag

Warning: Please note

This thread was started before GSAP 3 was released. Some information, especially the syntax, may be out of date for GSAP 3. Please see the GSAP 3 migration guide and release notes for more information about how to update the code to GSAP 3's syntax. 

Recommended Posts

I was asked yesterday about animating to/from backgroundSize: "cover" or "contain" with GSAP, so I figured I'd share the solution here in case it helps anyone else. 

 

The problem: GSAP interpolates between numbers, but how is it supposed to interpolate between something like "300px 250px" and "contain" (not a number)? So I whipped together a function that basically translates "contain" or "cover" into their px-based equivalents for that particular element at whatever size it is then. Once we've got it converted, it's easy to animate. 

 

//this function converts the backgroundSize of an element from "cover" or "contain" or "auto" into px-based dimensions. To set it immediately, pass true as the 2nd parameter.
function getBGSize(element, setInPx) {
    var e = (typeof(element) === "string") ? document.querySelector(element) : element,
            cs = window.getComputedStyle(e),
            imageUrl = cs.backgroundImage,
            size = cs.backgroundSize,
            image, w, h, iw, ih, ew, eh, ratio;
    if (imageUrl && !/\d/g.test(size)) {
        image = new Image();
        image.setAttribute("src", imageUrl.replace(/(^url\("|^url\('|^url\(|"\)$|'\)$|\)$)/gi, "")); //remove any url() wrapper. Note: some browsers include quotes, some don't.
        iw = image.naturalWidth;
        ih = image.naturalHeight;
        ratio = iw / ih;
        ew = e.offsetWidth;
        eh = e.offsetHeight;
        if (!iw || !ih) {
            console.log("getBGSize() failed; image hasn't loaded yet.");
        }
        if (size === "cover" || size === "contain") {
            if ((size === "cover") === (iw / ew > ih / eh)) {
                h = eh;
                w = eh * ratio;
            } else {
                w = ew;
                h = ew / ratio;    
            }
        } else { //"auto"
            w = iw;
            h = ih;
        }
        size = Math.ceil(w) + "px " + Math.ceil(h) + "px";
        if (setInPx) {
            e.style.backgroundSize = size;
        }
    }
    return size;
}

 

The only catch is that the image must be already loaded, otherwise it's impossible to figure out the native dimensions of the image (aspect ratio). 

 

While it's technically possible to add this functionality into CSSPlugin, it didn't seem advisable because it eats up a fair amount of kb and it's EXTREMELY uncommon for folks to want to animate to/from a background-size of cover or contain. So maybe 0.0001% of the audience would benefit but 100% would pay the kb price. Didn't seem worthwhile, so a helper function like this struck me as more appropriate. Feel free to chime in if you disagree. 

 

Happy tweening!

See the Pen ?editors=0010 by GreenSock (@GreenSock) on CodePen

  • Like 4
Link to comment
Share on other sites

10 hours ago, GreenSock said:

While it's technically possible to add this functionality into CSSPlugin, it didn't seem advisable because it eats up a fair amount of kb and it's EXTREMELY uncommon for folks to want to animate to/from a background-size of cover or contain. So maybe 0.0001% of the audience would benefit but 100% would pay the kb price. Didn't seem worthwhile, so a helper function like this struck me as more appropriate. Feel free to chime in if you disagree. 

 

It's a hard choice. I know people are going to start asking for this functionality more. I think there are better ways of animating this, like using an image and scale, but I know a lot of people will still want to do it with backgroundSize. The CSSPlugin is already pretty big, and trying to match every CSS feature, while probably technically possible for the most part, would just make the plugin even bigger. 

 

Maybe this would be better off as a utility. This is a very common problem for canvas developers too. I can't tell you how many helpers libraries I see like this.

https://github.com/kittykatattack/scaleToWindow

 

The math is still the same.

  • Like 5
Link to comment
Share on other sites

15 hours ago, GreenSock said:

So maybe 0.0001% of the audience would benefit

 

I'm honestly curious ... what are those 0.0001% doing that requires scaling the background from `contain` or `cover` to a fixed/relative size? Are they looking for the tiling that occurs when either background dimension is less than the size of the element it's applied to and the background-image is set to repeat?

  • Like 1
Link to comment
Share on other sites

Join the Slack - Animation at Work and you can see where the question came from. It was posted in the #gsap channel.

http://damp-lake-50659.herokuapp.com/

 

But I don't think the repeat was really part of the question. That's just what the demo had in place when Jack forked it. I think what people are trying to do is more of lightbox effect. Click on small image, and make it fill a larger area.

  • Like 2
Link to comment
Share on other sites

OK, I see what dunkie was getting at ... wanting to do a .set('.something', {backgroundSize: 'cover'}) ...which @GreenSock addressed with a GSAP update! (man is Jack quick!!)

 

Back to the wanting an image to scale up to occupy a bigger space (which I definitely see the need for :) ) ... I've always found it super easy to apply the image background with cover ... but tween the element's size -- and perhaps within a wrapper so it doesn't mess with the document flow -- and let CSS worry about the actual sizing of the background. Like this (a perfectly hideous example :) )

 

See the Pen pLOdvx by sgorneau (@sgorneau) on CodePen

 

Or like this in another thread that I chimed in on

 

See the Pen JLKbBm by sgorneau (@sgorneau) on CodePen

 

But I might be missing a more specific use case. I'm not dismissing the solution :) ... just honestly curious where people would need to actually tween a background image.

  • Like 4
Link to comment
Share on other sites

I would echo Blake's advice above about joining the Animation At Work slack and following these channels

http://damp-lake-50659.herokuapp.com/

 

#GSAP

#general

#codepen

#uianimation

#svg

 

There are definitely some interesting conversations going on and you get exposed to some ideas and techniques that you wouldn't necessarily stumble into elsewhere. 

 

At first we were worried that the #GSAP channel would turn into a support nightmare (with people expecting instant "live-chat" answers from Jack or myself) but so far it really hasn't been too bad. Just a few questions per week that are often answered by other people in a reasonable amount of time. 

  • Like 5
Link to comment
Share on other sites

Hi @Shaun Gorneau,

 

26 minutes ago, Shaun Gorneau said:

But I might be missing a more specific use case. I'm not dismissing the solution :) ... just honestly curious where people would need to actually tween a background image.

 

You can tween a background image when animating image sprites or doing say a ken burns type of effect for an image using background-size. But usually the limitation falls short due to some browsers not doing sub-pixel rendering. For example, i used to be able to force autoRound to false and I would see Firefox and or Chrome animate on a sub-pixel level for values of background-size and background-position without pixel snapping, even though it would not render with GPU. But now it seems to be intermittent in honoring autoRound:false.

 

See the Pen AigpI by jonathan (@jonathan) on CodePen

 

I prefer animating transforms like scale for images like @OSUblake said above, but there are a lot of uses for animating a background image, it just depends what works for each project.

 

Thanks for this solution @GreenSock Jack!

 

Thanks guys i just signed up for that Slack - Animation at Work.

 

Just my two bits :)

  • Like 4
Link to comment
Share on other sites

  • 3 years later...

Doesn't look like it, 

it's animating but it's not respecting the initial cover size

See the Pen 9f61045d39ed7ffb10b89ad9495a4dfe?editors=1111 by cassie-codes (@cassie-codes) on CodePen



I'd probably steer clear of animating background images if possible though - it's likely to be a lot more janky than animating with transforms.

If you really need it maybe @GreenSock would update the helper?

Link to comment
Share on other sites

I agree with @Cassie - it's almost always better to animate an actual image with scaleX/scaleY/x/y but I went ahead and enhanced the helper function to have several new features.

 

You can define a config object where you can set size, nativeWidth, and/or nativeHeight. It handles percentage-based values too. 

 

Here's a fork with that function in place: 

See the Pen BaRNZxm?editors=0010 by GreenSock (@GreenSock) on CodePen

  • Like 4
Link to comment
Share on other sites

  • 1 month later...
  • 5 months later...

I came across an interesting use-case for this function.

 

I wanted to animate TO a background-size:cover FROM a scaled UP size. I needed the background image to maintain it's aspect ratio and stay centered at it's large size through the tween. Prior to reading @Chris Heuberger's post I came up with my own custom solution that required bgSize() to return the dimensions without the "px" so I could scale them up.

 

As always I was looking for a quick, brute-force, solution and not necessarily the most elegant.

 

I hacked bgSize() so I could get back a custom object with width and height {width:xxx, height:yyy}) if I passed in a parameter of returnType:"object"

See the Pen dyZYdBv?editors=0110 by snorkltv (@snorkltv) on CodePen

 

In the example above note:

  • the image is square at 1200x1200 native size
  • you can click to replay the animation
  • it isn't responsive but you can resize the browser and then reload to see it work at different aspect ratios
  • circles always stay perfectly round

I wasn't able to get the official OR Chris' modified function to perform like that but I may not have been using them right.

 

I think there is some value in being able to scale the "cover" dimensions and preserve aspect ratio.

 

My API suggestion would be that if you want to get "scaled cover" dimensions you could pass in

 

size:"cover-3" and return the cover dimensions at a scale of 3.

 

While I agree that in most cases it is probably better and easier just to scale an image as opposed to a background I had a very specific use where I was using the background image as part of a "clip-text" effect and thus needed the scaled-up cover dimensions.

 

See the Pen ea5402e6ae9a5a70723c280f3b06539c by snorkltv (@snorkltv) on CodePen

 

If you look closely you will see that the background image inside the text is scaling down (as the text scales up) and it perfectly aligns with another background image that is set to "cover" and is sitting BEHIND the scaling text.

 

Notes

  • not responsive unless you reload after resize (didn't put that in)
  • performance is atrocious on FireFox but silky smooth on Chrome
  • I will probably try to just recreate this with SVG

Any way I guess what I'm voting for here is a clean way to scale up (or down) the cover dimensions (like in my first demo) and possibly contain too.

If that's already available OR if Chris' modifications can handle it just let me know.

 

Like @Shaun Gorneau mentioned I think these scenarios may be more common than the tiling effect in the original demo.

 

Regardless of my mis-use or misunderstanding of the function I'm glad it was available! Thanks!

 

 

 

 

 

 

 

 

  • Like 3
Link to comment
Share on other sites

Thanks for the feedback and suggestion(s), @Carl. In the CodePen below, I whipped together a plugin just for you that'll let you do stuff like this: 

gsap.fromTo(".photo", {
	backgroundSize: {
		size: "cover", 
		scale: 3, // scale up the "cover" dimensions (pixels)
		nativeWidth:1200, 
		nativeHeight:1200
	}
}, {
	backgroundSize: {
		size: "cover", 
		nativeWidth:1200, 
		nativeHeight:1200
	}, 
	duration: 2
});

 

See the Pen rNYxENg?editors=0010 by GreenSock (@GreenSock) on CodePen

 

Does that help? 

 

I added it to the helper functions page too. :)

  • Like 1
  • Thanks 2
Link to comment
Share on other sites

Wow. That's Great.

The plugin makes it very easy to do what I need and also do things like animate from contain to a scaled up cover (which I found quite cool).

 

However, as I tend to do, I found an interesting issue.

Being that the background image in the demo had existing css to set background-size:cover I tried omitting the "size" property from the tween. I thought maybe it would just pick up the existing value.

 

I tried this tween

 

// now let's animate...
let tween = gsap.to(".photo", {
    backgroundSize: {
        nativeWidth:1200, 
        nativeHeight:1200,
        scale:2
    }, 
    duration: 2
});

 

I'm not embedding the pen as it seemed to cause codepen to lockup and I couldn't edit it or re-run. Eventually Chrome gave me an "aw snap, something went wrong". 

 

If specifying size in the tween is required that's totally fine, but maybe it's worth looking into preventing any big problems if it isn't.

 

Otherwise, this is great. Thanks so much for creating it!

Link to comment
Share on other sites

8 hours ago, Carl said:

Being that the background image in the demo had existing css to set background-size:cover I tried omitting the "size" property from the tween. I thought maybe it would just pick up the existing value.

Sure, I added the code necessary to accommodate that. Better? 

 

See the Pen rNYxENg by GreenSock (@GreenSock) on CodePen

  • Like 1
Link to comment
Share on other sites

Thanks  @GreenSock that's really great.

 

To keep up with the forum trend of "just one more question"...

 

At first I wasn't sure if the getBGSize() helper function would still have value, but alas I find I could still use it. I wonder if it would be worthwhile to combine the two so that BackgroundSize Plugin has a getBGSize() method? 

 

I know this may be pushing the bounds of what should be expected of an un-official plugin so I don't have high expectations and it's not the end of the world to include them both separately. 

  • Haha 1
Link to comment
Share on other sites

3 hours ago, Carl said:

To keep up with the forum trend of "just one more question"...

 

Please read the forum guidelines - we cannot provide custom consulting services...

 

Just kidding - we make exceptions for chaps like you who have invested thousands of hours into this community :)

 

Your wish is my command:

See the Pen rNYxENg?editors=0110 by GreenSock (@GreenSock) on CodePen

 

let containSize = BackgroundSizePlugin.getSize(".photo", {size: "contain", nativeWidth: 1200, nativeHeight: 1200}); 
console.log("containSize:", containSize); // {width: 513, height: 513}

 

Is that what you were looking for? 

  • Haha 3
Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...