<div class="viewport"></div>
.viewport {
	background-image: url('https://images.unsplash.com/photo-1496276577120-a58058bc5935?ixlib=rb-0.3.5&q=85&fm=jpg&crop=entropy&cs=srgb&ixid=eyJhcHBfaWQiOjE0NTg5fQ&s=dd6bd068e0f38725ecb1a9203906b993');
	background-size: cover;
	height: 100vh;
}
View Compiled
gsap.fromTo('.viewport', {
		backgroundSize: (i, e) => bgSize(e) //I use a function-based value so that it works with multiple targets and so that it grabs the value EXACTLY at the time the tween starts.
	}, {
		duration: 3,
		backgroundSize: "300px 250px",
		yoyo: true,
		repeat: -1, 
		delay: 1, 
		ease: "ExpoScaleEase"
});


/*
config is optional and can have any of the following properties: 
 - size [string] - the size to set and convert into px before it gets returned, like "cover" or "150% auto".
 - nativeWidth [number] - native width of the image (in pixels)
 - nativeHeight [number] - native height of the image (in pixels)
 
Simple example: 
// returns current backgroundSize in px
bgSize(".class"); 
 
Advanced example: 
// sets the backgroundSize to "cover" and returns it in the equivalent px-based amount assuming the image's native width is 600px and height is 400px.
bgSize(".class", {size: "cover", nativeWidth: 600, nativeHeight: 400}); 
 
Note: if you can define the nativeWidth and nativeHeight, it helps becaues it can skip tasks like creating 
an Image and loading the URL to detect the native size automatically. Sometimes images don't load fast enough,
so skipping that step avoids the whole issue. 
*/
function bgSize(element, config) {
  config = config || {};
	let e = gsap.utils.toArray(element)[0],
			cs = window.getComputedStyle(e),
			imageUrl = cs.backgroundImage,
      {nativeWidth, nativeHeight} = config,
			size = config.size || cs.backgroundSize,
			image, w, h, ew, eh, ratio;
	if (imageUrl && (!/\d/g.test(size) || size.indexOf("%") > -1)) {
    if (!nativeWidth || !nativeHeight) {
      image = new Image();
      image.setAttribute("src", imageUrl.replace(/(^url\("|^url\('|^url\(|"\)$|'\)$|\)$)/gi, ""));
      nativeWidth = image.naturalWidth;
      nativeHeight = image.naturalHeight;
    }
		ew = e.offsetWidth;
		eh = e.offsetHeight;
		if (!nativeWidth || !nativeHeight) {
      console.log("bgSize() failed;", imageUrl, "hasn't loaded yet.");
      nativeWidth = ew;
      nativeHeight = eh;
    }
		ratio = nativeWidth / nativeHeight;
		if (size === "cover" || size === "contain") {
			if ((size === "cover") === (nativeWidth / ew > nativeHeight / eh)) {
				h = eh;
				w = eh * ratio;
			} else {
				w = ew;
				h = ew / ratio;	
			}
		} else { // "auto" or %
      size = size.split(" ");
      size.push("");
			w = ~size[0].indexOf("%") ? ew * parseFloat(size[0]) / 100 : nativeWidth;
			h = ~size[1].indexOf("%") ? eh * parseFloat(size[1]) / 100 : nativeHeight;
		}
		size = Math.ceil(w) + "px " + Math.ceil(h) + "px";
		config.size && (e.style.backgroundSize = size);
	}
	return size;
}



External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

  1. https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/gsap-latest-beta.min.js