|
| 1 | +/* |
| 2 | +Plugin Name: jQuery Animation Scroll Plugin |
| 3 | +Author: Jason Lusk |
| 4 | +URI: JasonLusk.com |
| 5 | +Example: |
| 6 | +<p data-animate-scroll='{"scaleX": "1.5","scaleY": "1.5","x": "-10","y": "-10","rotation": "-3","alpha": "1","easingType": "Cubic.easeOut","duration": "1"}'>test</p> |
| 7 | + */ |
| 8 | +(function($, window, document) { |
| 9 | + 'use strict'; |
| 10 | + var $window = $(window), |
| 11 | + attachEvent = document.attachEvent, |
| 12 | + //init scroll-event only once for better performance -> save target-data first in arrays |
| 13 | + animateScroll = { |
| 14 | + throttles: {}, |
| 15 | + animations: [], |
| 16 | + bind: function(el, options) { |
| 17 | + $(el).on('reverse play', function(evt) { |
| 18 | + if(evt.type == 'play' && !options.reverse){ |
| 19 | + return true; |
| 20 | + } |
| 21 | + // animate target object based on viewport check event |
| 22 | + var $this = $(this), |
| 23 | + $timeline = $this.data('tween'), |
| 24 | + action; |
| 25 | + if (!!$timeline && evt.type != action) { |
| 26 | + if (!options.animateAll && evt.type == 'play') { |
| 27 | + $timeline.progress(1, true); |
| 28 | + } |
| 29 | + $timeline[evt.type](); |
| 30 | + action = evt.type; |
| 31 | + } |
| 32 | + }); |
| 33 | + if (!!animateScroll.throttles.watch) { |
| 34 | + $window.find('body').andSelf().on('scroll resize orientationchange touchend gestureend check', function(e) { |
| 35 | + // trigger viewport animation check |
| 36 | + animateScroll.throttles.scroll = (animateScroll.throttles.scroll == null) && setTimeout(function() { |
| 37 | + animateScroll.inview(e.type == 'resize' || e.type == 'orientationchange'); |
| 38 | + animateScroll.throttles.scroll = null; |
| 39 | + }, 101); |
| 40 | + }); |
| 41 | + } |
| 42 | + animateScroll.throttles.watch = el; |
| 43 | + }, |
| 44 | + check: function(target) { |
| 45 | + // is stored original element position centered within the viewport |
| 46 | + var tObj = $(target), |
| 47 | + vTop = $window.scrollTop(), |
| 48 | + vBottom = vTop + $window.height(), |
| 49 | + elTop = tObj.data('originalOffset').top, |
| 50 | + elBottom = elTop + parseInt(tObj.data('originalSize').height); |
| 51 | + animateScroll.throttles.transition = null; |
| 52 | + return (vTop < elBottom && elBottom < vBottom) || (vTop < elTop && elTop < vBottom) ? 'reverse' : 'play'; |
| 53 | + }, |
| 54 | + update: function($this, $timeline) { |
| 55 | + var newOffset = $this.offset(), |
| 56 | + oldOffset = $this.data('originalOffset') || { |
| 57 | + top: 0 |
| 58 | + }; |
| 59 | + if (newOffset.top != oldOffset.top) { |
| 60 | + // update saved offset and size |
| 61 | + $this.data({ |
| 62 | + 'originalOffset': newOffset, |
| 63 | + 'originalSize': { |
| 64 | + width: $this.width(), |
| 65 | + height: $this.height() |
| 66 | + } |
| 67 | + }); |
| 68 | + } |
| 69 | + }, |
| 70 | + inview: function(resize) { |
| 71 | + animateScroll.animations.each(function() { |
| 72 | + var $this = $(this), |
| 73 | + $timeline = $this.data('tween'); |
| 74 | + // update trigger position |
| 75 | + if (!!resize) { |
| 76 | + setTimeout(function() { |
| 77 | + animateScroll.update($this, $timeline); |
| 78 | + // trigger animation event |
| 79 | + $this |
| 80 | + .trigger(animateScroll.check($this)); |
| 81 | + }, 250); |
| 82 | + } else { |
| 83 | + // trigger animation event |
| 84 | + $this |
| 85 | + .trigger(animateScroll.check($this)); |
| 86 | + } |
| 87 | + }); |
| 88 | + }, |
| 89 | + init: function(animations, options) { |
| 90 | + |
| 91 | + // add resize event to body |
| 92 | + addResizeListener($('body')[0], function() { |
| 93 | + $('body').trigger('resize'); |
| 94 | + }); |
| 95 | + |
| 96 | + // function to initialize plugin and pass custom variables from html5 data attributes |
| 97 | + animations.each(function() { |
| 98 | + var $el = $(this); |
| 99 | + $el |
| 100 | + .animateScroll($.extend({}, options, $el.data('animate-scroll'))); |
| 101 | + }) |
| 102 | + .promise() |
| 103 | + .done(function() { |
| 104 | + animateScroll.inview(); |
| 105 | + }); |
| 106 | + }, |
| 107 | + setup: function($this, $parent, options) { |
| 108 | + // setup parent element perspective |
| 109 | + TweenMax.set($parent, { |
| 110 | + transformPerspective: options.transformPerspective, |
| 111 | + onComplete: function() { |
| 112 | + $parent |
| 113 | + .css({ |
| 114 | + 'perspective': options.transformPerspective |
| 115 | + }); |
| 116 | + } |
| 117 | + }); |
| 118 | + $this |
| 119 | + .addClass('animateScroll') |
| 120 | + .css({ |
| 121 | + "-webkit-transform": "translate3D(0,0,1px)" |
| 122 | + }); |
| 123 | + $this |
| 124 | + .data({ |
| 125 | + 'originalOffset': $this.offset(), |
| 126 | + 'originalSize': { |
| 127 | + width: $this.width(), |
| 128 | + height: $this.height() |
| 129 | + }, |
| 130 | + 'tween': new TimelineMax({ |
| 131 | + paused: true, |
| 132 | + onComplete: function() { |
| 133 | + animateScroll.update($this, this); |
| 134 | + } |
| 135 | + }) |
| 136 | + .to($this, options.duration, { |
| 137 | + transformStyle: 'preserve-3d', |
| 138 | + transformOrigin: options.transformOrigin, |
| 139 | + x: options.x, |
| 140 | + y: options.y, |
| 141 | + scaleX: options.scaleX, |
| 142 | + scaleY: options.scaleY, |
| 143 | + rotation: options.rotation, |
| 144 | + rotationX: options.rotationX, |
| 145 | + rotationY: options.rotationY, |
| 146 | + autoAlpha: options.alpha, |
| 147 | + delay: options.delay, |
| 148 | + z: options.z, |
| 149 | + force3D: options.force3D, |
| 150 | + ease: options.easingType |
| 151 | + }).progress(1, false) |
| 152 | + }) |
| 153 | + .promise() |
| 154 | + .done(function() { |
| 155 | + animateScroll |
| 156 | + .bind($this, options); |
| 157 | + }); |
| 158 | + } |
| 159 | + }; |
| 160 | + $.fn.animateScroll = function(opts) { |
| 161 | + //set up default options |
| 162 | + var defaults = { |
| 163 | + transformPerspective: 1000, // Parent Transform Perspective |
| 164 | + lazyLoad: false, // Lazy Load Images (experimental) |
| 165 | + animateAll: false, // Animate elements outside of viewport? |
| 166 | + reverse: false, // Allow elements reverse animation state |
| 167 | + transformOrigin: '50% 50%', // Transform Origin X/Y Position |
| 168 | + x: 0, // Horizontal offset in px |
| 169 | + y: 0, // Vertical offset in px |
| 170 | + scaleX: 1, // Scale X position |
| 171 | + scaleY: 1, // Scale Y position |
| 172 | + rotation: 0, // Rotation in degrees |
| 173 | + rotationX: 0, // Rotation X position |
| 174 | + rotationY: 0, // Rotation X position |
| 175 | + alpha: 0.9, // Opacity from 0.0-1 |
| 176 | + delay: 0, // Animation Delay |
| 177 | + z: 0.1, // Z position |
| 178 | + force3D: true, // Force 3D Hardware |
| 179 | + easingType: 'Back.easeInOut', // Animation easing Type |
| 180 | + duration: 0.3 // Animation diration in seconds |
| 181 | + }; |
| 182 | + |
| 183 | + var options = $.extend({}, defaults, opts); |
| 184 | + if (this[0] != document) { |
| 185 | + |
| 186 | + this.each(function(index) { |
| 187 | + options = $.extend({}, defaults, opts); |
| 188 | + var $this = $(this), |
| 189 | + $parent = $this.parent(); |
| 190 | + if (options.lazyLoad && $this.is('img')) { |
| 191 | + $this.on('load', function() { |
| 192 | + animateScroll |
| 193 | + .setup($this, $parent, options); |
| 194 | + }); |
| 195 | + } else { |
| 196 | + animateScroll |
| 197 | + .setup($this, $parent, options); |
| 198 | + } |
| 199 | + }); |
| 200 | + } else { |
| 201 | + animateScroll |
| 202 | + .animations = $('[data-animate-scroll]'); |
| 203 | + animateScroll |
| 204 | + .init(animateScroll.animations, options); |
| 205 | + } |
| 206 | + } |
| 207 | + |
| 208 | + // element resize listener [add|remove]ResizeListener(resizeElement, resizeCallback); |
| 209 | + // based on https://github.com/sdecima/javascript-detect-element-resize |
| 210 | + if (!attachEvent) { |
| 211 | + var createStyles = function() { |
| 212 | + if (!!createStyles) { |
| 213 | + var n = document.head || document.getElementsByTagName("head")[0], |
| 214 | + r = document.createElement("style"), |
| 215 | + css = '.r-t {\ |
| 216 | + visibility: hidden;\ |
| 217 | + }\ |
| 218 | + .r-t, .r-t > div, .c-t:before {\ |
| 219 | + content: " ";\ |
| 220 | + display: block;\ |
| 221 | + position: absolute;\ |
| 222 | + top: 0;\ |
| 223 | + left: 0;\ |
| 224 | + height: 100%;\ |
| 225 | + width: 100%;\ |
| 226 | + overflow: hidden\ |
| 227 | + }\ |
| 228 | + .r-t > div {\ |
| 229 | + background: #eee;\ |
| 230 | + overflow: auto\ |
| 231 | + }\ |
| 232 | + .c-t:before {\ |
| 233 | + width: 200%;\ |
| 234 | + height: 200%\ |
| 235 | + }'; |
| 236 | + r.type = "text/css"; |
| 237 | + if (r.styleSheet) { |
| 238 | + r.styleSheet.cssText = css |
| 239 | + } else { |
| 240 | + r.appendChild(document.createTextNode(css)) |
| 241 | + } |
| 242 | + n.appendChild(r) |
| 243 | + return r.sheet || false; |
| 244 | + } else { |
| 245 | + return false; |
| 246 | + } |
| 247 | + }, |
| 248 | + requestFrame = function() { |
| 249 | + return function(t) { |
| 250 | + return window.requestAnimationFrame(t) |
| 251 | + } |
| 252 | + }(), |
| 253 | + cancelFrame = function() { |
| 254 | + return function(t) { |
| 255 | + return window.cancelAnimationFrame(t) |
| 256 | + } |
| 257 | + }(), |
| 258 | + resetTriggers = function(e) { |
| 259 | + var t = e.__rt__, |
| 260 | + n = t.firstElementChild, |
| 261 | + r = t.lastElementChild, |
| 262 | + i = n.firstElementChild; |
| 263 | + r.scrollLeft = r.scrollWidth; |
| 264 | + r.scrollTop = r.scrollHeight; |
| 265 | + i.style.width = n.offsetWidth + 1 + "px"; |
| 266 | + i.style.height = n.offsetHeight + 1 + "px"; |
| 267 | + n.scrollLeft = n.scrollWidth; |
| 268 | + n.scrollTop = n.scrollHeight |
| 269 | + }, |
| 270 | + checkTriggers = function(e) { |
| 271 | + return e.offsetWidth != e.__rl__.width || e.offsetHeight != e.__rl__.height |
| 272 | + }, |
| 273 | + scrollListener = function(e) { |
| 274 | + var t = this; |
| 275 | + resetTriggers(this); |
| 276 | + if (this.__rRAF__) cancelFrame(this.__rRAF__); |
| 277 | + this.__rRAF__ = requestFrame(function() { |
| 278 | + if (checkTriggers(t)) { |
| 279 | + t.__rl__.width = t.offsetWidth; |
| 280 | + t.__rl__.height = t.offsetHeight; |
| 281 | + t.__rl__.forEach(function(n) { |
| 282 | + n.call(t, e) |
| 283 | + }) |
| 284 | + } |
| 285 | + }) |
| 286 | + }, |
| 287 | + rafPolyFill = function() { |
| 288 | + var _af = 'AnimationFrame', |
| 289 | + _req = 'Request', |
| 290 | + _raf = 'request' + _af, |
| 291 | + _can = 'Cancel', |
| 292 | + _caf = 'cancel' + _af, |
| 293 | + expire = 0, |
| 294 | + vendors = ['moz', 'ms', 'o', 'webkit'], |
| 295 | + pre; |
| 296 | + |
| 297 | + while (!window[_raf] && (pre = vendors.pop())) { |
| 298 | + window[_raf] = window[pre + _req + _af]; |
| 299 | + window[_caf] = window[pre + _can + _af] || window[pre + _can + _req + _af]; |
| 300 | + } |
| 301 | + |
| 302 | + if (!window[_raf]) { |
| 303 | + window[_raf] = function(callback) { |
| 304 | + var current = +new Date, |
| 305 | + adjusted = 16 - (current - expire), |
| 306 | + delay = adjusted > 0 ? adjusted : 0; |
| 307 | + expire = current + delay; |
| 308 | + |
| 309 | + return setTimeout(function() { |
| 310 | + callback(expire); |
| 311 | + }, delay); |
| 312 | + }; |
| 313 | + window[_caf] = clearTimeout; |
| 314 | + } |
| 315 | + } |
| 316 | + } |
| 317 | + window.addResizeListener = function(t, n) { |
| 318 | + if (attachEvent) t.attachEvent("onresize", n); |
| 319 | + else { |
| 320 | + if (!t.__rt__) { |
| 321 | + if (getComputedStyle(t).position == "static") t.style.position = "relative"; |
| 322 | + rafPolyFill(); |
| 323 | + createStyles(); |
| 324 | + t.__rl__ = {}; |
| 325 | + t.__rl__ = []; |
| 326 | + (t.__rt__ = document.createElement("div")).className = "r-t"; |
| 327 | + t.__rt__.innerHTML = '<div class="e-t"><div></div></div>' + '<div class="c-t"></div>'; |
| 328 | + t.appendChild(t.__rt__); |
| 329 | + resetTriggers(t); |
| 330 | + t.addEventListener("scroll", scrollListener, true) |
| 331 | + } |
| 332 | + t.__rl__.push(n) |
| 333 | + } |
| 334 | + }; |
| 335 | + window.removeResizeListener = function(t, n) { |
| 336 | + if (attachEvent) t.detachEvent("onresize", n); |
| 337 | + else { |
| 338 | + t.__rl__.splice(t.__rl__.indexOf(n), 1); |
| 339 | + if (!t.__rl__.length) { |
| 340 | + t.removeEventListener("scroll", scrollListener); |
| 341 | + t.__rt__ = !t.removeChild(t.__rt__) |
| 342 | + } |
| 343 | + } |
| 344 | + } |
| 345 | +})(jQuery, window, document); |
0 commit comments