Pen Settings

HTML

CSS

CSS Base

Vendor Prefixing

Add External Stylesheets/Pens

Any URL's added here will be added as <link>s in order, and before the CSS in the editor. If you link to another Pen, it will include the CSS from that Pen. If the preprocessor matches, it will attempt to combine them before processing.

+ add another resource

JavaScript

Babel is required to process package imports. If you need a different preprocessor remove all packages first.

Add External Scripts/Pens

Any URL's added here will be added as <script>s in order, and run before the JavaScript in the editor. You can use the URL of any other Pen and it will include the JavaScript from that Pen.

+ add another resource

Behavior

Save Automatically?

If active, Pens will autosave every 30 seconds after being saved once.

Auto-Updating Preview

If enabled, the preview panel updates automatically as you code. If disabled, use the "Run" button to update.

Format on Save

If enabled, your code will be formatted when you actively save your Pen. Note: your code becomes un-folded during formatting.

Editor Settings

Code Indentation

Want to change your Syntax Highlighting theme, Fonts and more?

Visit your global Editor Settings.

HTML

              
                <link href='//fonts.googleapis.com/css?family=Signika+Negative:300,400,600' rel='stylesheet' type='text/css'>
<div id="header"><img src="https://s3-us-west-2.amazonaws.com/s.cdpn.io/16327/logo-man.svg" width="100" style="vertical-align: middle; margin-right: 16px;" /><h1>Position-based staggers in GSAP 3</h1></div>


<div id="grid">
  <div><i></i><i></i><i></i><i></i></div>
  <div><i></i><i></i><i></i><i></i><i></i><i></i><i style="margin-left:40px;"></i><i></i><i></i><i></i></div>
  <div><i style="margin-left:60px"></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
  <div><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
  <div><i style="margin-left:30px"></i><i></i><i></i><i></i></div>
  <div><i></i><i style="margin-right:60px; margin-top:20px;"></i><i></i><i></i><i></i><i></i></div>
  <div><i></i><i></i><i></i><i></i><i></i><i></i><i></i></div>
</div>


<div id="instructions">
  <p><a href="https://greensock.com/gsap">GSAP</a>'s advanced staggers allow you to define a <code class="featured">grid</code> which assumes that everything in the array is uniformly spaced. But what if there are gaps, different quantities in each row, etc. like in the example above? Perhaps it doesn't even resemble a grid at all. Notice the staggering still flows nicely in this demo thanks to a <code class="featured">distributeByPositions()</code> helper function.</p>
  <h2>How does it work?</h2>
  <p>In the JS panel, you'll find a <code class="featured">distributeByPosition()</code> method that you can copy into your project. Then, simply wrap it around your config object that you would normally use for an advanced stagger, and it'll spit back a function that's understood by GSAP's advanced stagger feature. Staggers will be distributed based on the x/y positions of the DOM elements using <code>getBoundingClientRect()</code> internally. The elements could be anywhere on the screen and it'll measure the distances and stagger things accordingly.</p>
    
  <h3>Sample code:</h3>
  <div class="code">
    <code>gsap.to(".box", {</code><br>
    <code>&nbsp;&nbsp;duration: 1,</code><br>
    <code>&nbsp;&nbsp;scale: 0.1,</code><br>
    <code>&nbsp;&nbsp;y: 40,</code><br>
    <code>&nbsp;&nbsp;ease: "power1.inOut",</code><br>
    <code class="featured">&nbsp;&nbsp;stagger: distributeByPosition({</code><br>
    <code class="featured">&nbsp;&nbsp;&nbsp;&nbsp;from: <span id="from">"center"</span>,<br></code>
    <code id="axisCode" class="featured" style="display:none;">&nbsp;&nbsp;&nbsp;&nbsp;axis: <span id="axis">null</span>,<br></code>
    <code id="easeCode" class="featured" style="display:none;">&nbsp;&nbsp;&nbsp;&nbsp;ease: <span id="ease">"none"</span>,<br></code>
    <code class="featured">&nbsp;&nbsp;&nbsp;&nbsp;amount: 2</code><br>
    <code class="featured">&nbsp;&nbsp;})</code><br>
    <code>});</code>
  </div> 
  
  <h3>Configuration</h3>
    <p>The config object may contain any of the following optional properties:</p> 
<ul>
  <li><strong>amount</strong> [number]: The total amount of time (in seconds) that gets split up among all the staggers. So if <code>amount</code> is <code>1</code> and there are 100 elements that stagger linearly, there would be 0.01 seconds between each tween's start time. If you prefer to specify a certain amount of time between each tween, use the <code>each</code> property <i>instead</i>. A negative <code>amount</code> will invert the timing effect which can be very handy. So <code>{amount:-1, from:"center"}</code> would cause staggers to go from the outer edges in toward the center.</li>
  <li><strong>from</strong> [string | integer]: The position in the array from which the stagger will emanate. To begin with a particular element, for example, use the number representing that element's index in the target array. So <code>from:4</code> begins staggering at the 5th element in the array (because arrays use zero-based indexes). The animation for each element will begin based on the element's proximity to the "from" value in the array (the closer it is, the sooner it'll begin). You can also use the following string values: <code>"start"</code>, <code>"center"</code>, <code>"edges"</code>, or <code>"end"</code>. Default: 0.</li>
  <li><strong>axis</strong> [string]: If you define a <code>grid</code>, staggers are based on each element's total distance to the "from" value on both the x and y axis, but you can focus on just one axis if you prefer (<code>"x"</code> or <code>"y"</code>). Use the demo above to see the effect (it makes more sense when you see it visually).</li>
  <li><strong>ease</strong> [Ease]: The ease that distributes the start times of the animation. So <code>"power2"</code> would start out with bigger gaps and then get more tightly clustered toward the end. Default: <code>"none"</code>.</li>
  </ul>
 
  <h3>Why wasn't wasn't this baked into GSAP? Why is a helper function needed?</h3>
  <p>Primarily because of file size concerns. The <code>grid</code> feature seemed to cover the vast majority of use cases and GSAP was built to allow for these advanced functions to be leveraged externally if you need to accommodate an edge case like this.</p>
  <p>If you need any help, post in our <a href="https://greensock.com/forums/">forums</a>. Happy tweening!</p>
</div>

              
            
!

CSS

              
                html, body {
  width: 0;         /* for fixing iOS iframe issues */
  min-width: 100%;  /* for fixing iOS iframe issues */
  overflow-x: hidden;
  position: relative;
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}
body {
  background-color: #222;
  text-align: center;
  color: #bbb;
  font-family: "Signika Negative", sans-serif;
  font-weight: 300;
  font-size: 18px;
  padding: 10px 0;
}
h1 {
  margin: 0;
  font-size: 50px;
  text-align: left;
}
h2 {
  font-size: 38px;
  margin-bottom: 0;
}
h1, h2, h3 {
  color: white;
  font-weight: 400;
}
h3 {
  margin-bottom: -12px;
}
#header {
  display: flex;
  flex-direction: row;
  align-items: center;
  justify-content: center;
}
a {
  color: #88ce02;
  text-decoration: none;
  font-weight: 400;
}
a:hover {
  text-decoration: underline;
}
#grid {
  margin-bottom: 0;
  padding-bottom: 0;
}
#grid div i {
  background-color: #765e98;
  position: relative;
  width: 30px;
  height: 30px;
  margin: 4px;
  display: inline-block;
}

#instructions {
  padding:0 16px 16px 16px;
  text-align: left;
  max-width: 1000px;
  display: inline-block;
}

.code {
  background-color: #111;
  padding: 12px;
  border-radius: 10px;
  margin-top: 20px;
}
.featured {
  color: white;
}
strong {
  color: white;
  font-weight: 400;
  font-size: 20px;
}
.question {
  color: white;
  font-size: 21px;
  font-weight: 400;
  display: block;
}
#controls {
  text-align: center;
  display:flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
}
.control {
  display: flex;
  align-items: flex-start;
  font-family: "Consolas", "Bitstream Vera Sans Mono", "Courier New", Courier, monospace;
  color: white;
  margin-bottom: 10px;
}
.control-label {
  margin-top: 10px;
}
.control-vertical {
  display: flex;
  flex-direction: column;
  align-content: flex-start;
  text-align: left;
  justify-content: flex-start;
  padding: 6px 8px;
  border-radius: 9px;
  background-color: #111;
  margin-right: 18px;
  color: #bbb;
}

.fr-video {
  display: block;
  position: relative;
  width: 100%;
  height: 0;
  float: left;
  padding-bottom: 56.25%;
  margin: 12px 0 35px 0;
}

.fr-video iframe {
  position: absolute;
  width: 100%;
  height: 100%;
  top: 0;
  left: 0;
  border: 2px solid #444;
}

#instructions li {
  margin-bottom: 10px;
}

@media only screen and (max-width: 640px) {
  h1 {
    font-size: 30px;
    line-height: 30px;
  }
}
              
            
!

JS

              
                var tl = gsap.timeline({repeat: -1, repeatDelay: 0.5});
tl.to("#grid div i", {
  duration: 1, 
  scale: 0.1, 
  y: 40,
  yoyo: true, 
  repeat: 1, 
  ease: "power1.inOut",
  stagger: distributeByPosition({
    amount: 2, 
    //axis: "y",
    //ease: "power2.inOut",
    from: "center" // or try an index like 5
  })
});


/*
pass in an object with any of the following optional properties (just like the stagger special object):
{
  amount: amount (in seconds) that should be distributed
  from: "center" | "end" | "edges" | start" | index value (integer)
  ease: any ease, like "power1"
  axis: "x" | "y" (or omit, and it'll be based on both the x and y positions)
}
*/
function distributeByPosition(vars) {
	var ease = vars.ease,
		from = vars.from || 0,
		base = vars.base || 0,
		axis = vars.axis,
		ratio = {center: 0.5, end: 1, edges:0.5}[from] || 0,
		distances;
	return function(i, target, a) {
		var l = a.length,
			originX, originY, x, y, d, j, minX, maxX, minY, maxY, positions;
		if (!distances) {
			distances = [];
			minX = minY = Infinity;
			maxX = maxY = -minX;
			positions = [];
			for (j = 0; j < l; j++) {
				d = a[j].getBoundingClientRect();
				x = (d.left + d.right) / 2; //based on the center of each element
				y = (d.top + d.bottom) / 2;
				if (x < minX) {
					minX = x;
				}
				if (x > maxX) {
					maxX = x;
				}
				if (y < minY) {
					minY = y;
				}
				if (y > maxY) {
					maxY = y;
				}
				positions[j] = {x:x, y:y};
			}
			originX = isNaN(from) ? minX + (maxX - minX) * ratio : positions[from].x || 0;
			originY = isNaN(from) ? minY + (maxY - minY) * ratio : positions[from].y || 0;
			maxX = 0;
			minX = Infinity;
			for (j = 0; j < l; j++) {
				x = positions[j].x - originX;
				y = originY - positions[j].y;
				distances[j] = d = !axis ? Math.sqrt(x * x + y * y) : Math.abs((axis === "y") ? y : x);
				if (d > maxX) {
					maxX = d;
				}
				if (d < minX) {
					minX = d;
				}
			}
			distances.max = maxX - minX;
			distances.min = minX;
			distances.v = l = (vars.amount || (vars.each * l) || 0) * (from === "edges" ? -1 : 1);
			distances.b = (l < 0) ? base - l : base;
		}
		l = (distances[i] - distances.min) / distances.max;
		return distances.b + (ease ? ease.getRatio(l) : l) * distances.v;
	};
}

//END FUNCTION









//this just helps avoid the pixel-snapping that some browsers do.
TweenMax.set("#grid div i", {rotation:0.5, force3D:true});
              
            
!
999px

Console