scrollmagicjs-rails 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (27) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +21 -0
  3. data/README.md +37 -0
  4. data/lib/scrollmagicjs/rails/version.rb +5 -0
  5. data/lib/scrollmagicjs/rails.rb +8 -0
  6. data/vendor/assets/javascripts/greensock/TimelineLite.min.js +12 -0
  7. data/vendor/assets/javascripts/greensock/TimelineMax.min.js +12 -0
  8. data/vendor/assets/javascripts/greensock/TweenLite.min.js +12 -0
  9. data/vendor/assets/javascripts/greensock/TweenMax.min.js +17 -0
  10. data/vendor/assets/javascripts/greensock/easing/EasePack.min.js +12 -0
  11. data/vendor/assets/javascripts/greensock/jquery.gsap.min.js +14 -0
  12. data/vendor/assets/javascripts/greensock/plugins/AttrPlugin.min.js +12 -0
  13. data/vendor/assets/javascripts/greensock/plugins/BezierPlugin.min.js +12 -0
  14. data/vendor/assets/javascripts/greensock/plugins/CSSPlugin.min.js +13 -0
  15. data/vendor/assets/javascripts/greensock/plugins/CSSRulePlugin.min.js +12 -0
  16. data/vendor/assets/javascripts/greensock/plugins/ColorPropsPlugin.min.js +12 -0
  17. data/vendor/assets/javascripts/greensock/plugins/DirectionalRotationPlugin.min.js +12 -0
  18. data/vendor/assets/javascripts/greensock/plugins/EaselPlugin.min.js +12 -0
  19. data/vendor/assets/javascripts/greensock/plugins/EndArrayPlugin.min.js +12 -0
  20. data/vendor/assets/javascripts/greensock/plugins/KineticPlugin.min.js +12 -0
  21. data/vendor/assets/javascripts/greensock/plugins/RaphaelPlugin.min.js +12 -0
  22. data/vendor/assets/javascripts/greensock/plugins/RoundPropsPlugin.min.js +12 -0
  23. data/vendor/assets/javascripts/greensock/plugins/ScrollToPlugin.min.js +12 -0
  24. data/vendor/assets/javascripts/greensock/plugins/TextPlugin.min.js +12 -0
  25. data/vendor/assets/javascripts/greensock/utils/Draggable.min.js +14 -0
  26. data/vendor/assets/javascripts/jquery.scrollmagic.js +2428 -0
  27. metadata +90 -0
@@ -0,0 +1,2428 @@
1
+ /*
2
+ ScrollMagic v1.1.2
3
+ The jQuery plugin for doing magical scroll interactions.
4
+ (c) 2014 Jan Paepke (@janpaepke)
5
+ License & Info: http://janpaepke.github.io/ScrollMagic
6
+
7
+ Inspired by and partially based on SUPERSCROLLORAMA by John Polacek (@johnpolacek)
8
+ http://johnpolacek.github.com/superscrollorama/
9
+
10
+ Powered by the Greensock Tweening Platform (GSAP): http://www.greensock.com/js
11
+ Greensock License info at http://www.greensock.com/licensing/
12
+ */
13
+ /**
14
+ @overview ##Info
15
+ @version 1.1.2
16
+ @license Dual licensed under MIT license and GPL.
17
+ @author Jan Paepke - e-mail@janpaepke.de
18
+
19
+ @todo: enhancement: remove dependencies and move to plugins -> 2.0
20
+ @todo: bug: when cascading pins (pinning one element multiple times) and later removing them without reset, positioning errors occur.
21
+ @todo: bug: having multiple scroll directions with cascaded pins doesn't work (one scroll vertical, one horizontal)
22
+ @todo: feature: optimize performance on debug plugin (huge drawbacks, when using many scenes)
23
+ */
24
+ (function($, window) {
25
+
26
+ "use strict";
27
+
28
+ /**
29
+ * The main class that is needed once per scroll container.
30
+ *
31
+ * @class
32
+ * @global
33
+ *
34
+ * @example
35
+ * // basic initialization
36
+ * var controller = new ScrollMagic();
37
+ *
38
+ * // passing options
39
+ * var controller = new ScrollMagic({container: "#myContainer", loglevel: 3});
40
+ *
41
+ * @param {object} [options] - An object containing one or more options for the controller.
42
+ * @param {(string|object)} [options.container=window] - A selector, DOM object or a jQuery object that references the main container for scrolling.
43
+ * @param {boolean} [options.vertical=true] - Sets the scroll mode to vertical (`true`) or horizontal (`false`) scrolling.
44
+ * @param {object} [options.globalSceneOptions={}] - These options will be passed to every Scene that is added to the controller using the addScene method. For more information on Scene options see {@link ScrollScene}.
45
+ * @param {number} [options.loglevel=2] Loglevel for debugging. Note that logging is disabled in the minified version of ScrollMagic.
46
+ ** `0` => silent
47
+ ** `1` => errors
48
+ ** `2` => errors, warnings
49
+ ** `3` => errors, warnings, debuginfo
50
+ * @param {boolean} [options._refreshInterval=100] - Some changes don't call events by default, like changing the container size or moving a scene trigger element.
51
+ This interval polls these parameters to fire the necessary events.
52
+ If you don't use custom containers, trigger elements or have static layouts, where the positions of the trigger elements don't change, you can set this to 0 disable interval checking and improve performance.
53
+ *
54
+ */
55
+ var ScrollMagic = function(options) {
56
+
57
+ /*
58
+ * ----------------------------------------------------------------
59
+ * settings
60
+ * ----------------------------------------------------------------
61
+ */
62
+ var
63
+ NAMESPACE = "ScrollMagic",
64
+ DEFAULT_OPTIONS = {
65
+ container: window,
66
+ vertical: true,
67
+ globalSceneOptions: {},
68
+ loglevel: 2,
69
+ refreshInterval: 100
70
+ };
71
+
72
+ /*
73
+ * ----------------------------------------------------------------
74
+ * private vars
75
+ * ----------------------------------------------------------------
76
+ */
77
+
78
+ var
79
+ ScrollMagic = this,
80
+ _options = $.extend({}, DEFAULT_OPTIONS, options),
81
+ _sceneObjects = [],
82
+ _updateScenesOnNextTick = false, // can be boolean (true => all scenes) or an array of scenes to be updated
83
+ _scrollPos = 0,
84
+ _scrollDirection = "PAUSED",
85
+ _isDocument = true,
86
+ _viewPortSize = 0,
87
+ _tickerUsed = false,
88
+ _enabled = true,
89
+ _refreshInterval;
90
+
91
+ /*
92
+ * ----------------------------------------------------------------
93
+ * private functions
94
+ * ----------------------------------------------------------------
95
+ */
96
+
97
+ /**
98
+ * Internal constructor function of ScrollMagic
99
+ * @private
100
+ */
101
+ var construct = function () {
102
+ $.each(_options, function (key, value) {
103
+ if (!DEFAULT_OPTIONS.hasOwnProperty(key)) {
104
+ log(2, "WARNING: Unknown option \"" + key + "\"");
105
+ delete _options[key];
106
+ }
107
+ });
108
+ _options.container = $(_options.container).first();
109
+ // check ScrollContainer
110
+ if (_options.container.length === 0) {
111
+ log(1, "ERROR creating object " + NAMESPACE + ": No valid scroll container supplied");
112
+ throw NAMESPACE + " init failed."; // cancel
113
+ }
114
+ _isDocument = !$.contains(document, _options.container.get(0));
115
+ // update container size immediately
116
+ _viewPortSize = _options.vertical ? _options.container.height() : _options.container.width();
117
+ // set event handlers
118
+ _options.container.on("scroll resize", onChange);
119
+ try {
120
+ TweenLite.ticker.addEventListener("tick", onTick); // prefer TweenMax Ticker, but don't rely on it for basic functionality
121
+ _tickerUsed = true;
122
+ } catch (e) {
123
+ _options.container.on("scroll resize", onTick); // okay then just update on scroll/resize...
124
+ _tickerUsed = false;
125
+ }
126
+
127
+ _options.refreshInterval = parseInt(_options.refreshInterval);
128
+ if (_options.refreshInterval > 0) {
129
+ _refreshInterval = window.setInterval(refresh, _options.refreshInterval);
130
+ }
131
+
132
+ log(3, "added new " + NAMESPACE + " controller (v" + ScrollMagic.version + ")");
133
+ };
134
+
135
+ /**
136
+ * Default function to get scroll pos - overwriteable using `ScrollMagic.scrollPos(newFunction)`
137
+ * @private
138
+ */
139
+ var getScrollPos = function () {
140
+ return _options.vertical ? _options.container.scrollTop() : _options.container.scrollLeft();
141
+ };
142
+ /**
143
+ * Default function to set scroll pos - overwriteable using `ScrollMagic.scrollTo(newFunction)`
144
+ * @private
145
+ */
146
+ var setScrollPos = function (pos) {
147
+ if (_options.vertical) {
148
+ _options.container.scrollTop(pos);
149
+ } else {
150
+ _options.container.scrollLeft(pos);
151
+ }
152
+ };
153
+
154
+ /**
155
+ * Handle updates on tick instead of on scroll (performance)
156
+ * @private
157
+ */
158
+ var onTick = function (e) {
159
+ if (_updateScenesOnNextTick && _enabled) {
160
+ var
161
+ scenesToUpdate = $.isArray(_updateScenesOnNextTick) ? _updateScenesOnNextTick : _sceneObjects.slice(0),
162
+ oldScrollPos = _scrollPos;
163
+ // update scroll pos & direction
164
+ _scrollPos = ScrollMagic.scrollPos();
165
+ var deltaScroll = _scrollPos - oldScrollPos;
166
+ _scrollDirection = (deltaScroll === 0) ? "PAUSED" : (deltaScroll > 0) ? "FORWARD" : "REVERSE";
167
+ if (deltaScroll < 0) { // reverse order if scrolling reverse
168
+ scenesToUpdate.reverse();
169
+ }
170
+ // update scenes
171
+ $.each(scenesToUpdate, function (index, scene) {
172
+ log(3, "updating Scene " + (index + 1) + "/" + scenesToUpdate.length + " (" + _sceneObjects.length + " total)");
173
+ scene.update(true);
174
+ });
175
+ if (scenesToUpdate.length === 0 && _options.loglevel >= 3) {
176
+ log(3, "updating 0 Scenes (nothing added to controller)");
177
+ }
178
+ _updateScenesOnNextTick = false;
179
+ }
180
+ };
181
+
182
+ /**
183
+ * Handles Container changes
184
+ * @private
185
+ */
186
+ var onChange = function (e) {
187
+ if (e.type == "resize") {
188
+ _viewPortSize = _options.vertical ? _options.container.height() : _options.container.width();
189
+ }
190
+ _updateScenesOnNextTick = true;
191
+ };
192
+
193
+ var refresh = function () {
194
+ if (!_isDocument) {
195
+ if (_viewPortSize != (_options.vertical ? _options.container.height() : _options.container.width())) {
196
+ _options.container.trigger("resize");
197
+ }
198
+ }
199
+ $.each(_sceneObjects, function (index, scene) {// refresh all scenes
200
+ scene.refresh();
201
+ });
202
+ };
203
+
204
+ /**
205
+ * Send a debug message to the console.
206
+ * @private
207
+ *
208
+ * @param {number} loglevel - The loglevel required to initiate output for the message.
209
+ * @param {...mixed} output - One or more variables that should be passed to the console.
210
+ */
211
+ var log = function (loglevel, output) {
212
+ if (_options.loglevel >= loglevel) {
213
+ var
214
+ prefix = "(" + NAMESPACE + ") ->",
215
+ args = Array.prototype.splice.call(arguments, 1);
216
+ args.unshift(loglevel, prefix);
217
+ debug.apply(window, args);
218
+ }
219
+ };
220
+
221
+ /**
222
+ * Sort scenes in ascending order of their start offset.
223
+ * @private
224
+ *
225
+ * @param {array} ScrollScenesArray - an array of ScrollScenes that should be sorted
226
+ * @return {array} The sorted array of ScrollScenes.
227
+ */
228
+ var sortScenes = function (ScrollScenesArray) {
229
+ if (ScrollScenesArray.length <= 1) {
230
+ return ScrollScenesArray;
231
+ } else {
232
+ var scenes = ScrollScenesArray.slice(0);
233
+ scenes.sort(function(a, b) {
234
+ return a.scrollOffset() > b.scrollOffset() ? 1 : -1;
235
+ });
236
+ return scenes;
237
+ }
238
+ };
239
+
240
+ /*
241
+ * ----------------------------------------------------------------
242
+ * public functions
243
+ * ----------------------------------------------------------------
244
+ */
245
+
246
+ /**
247
+ * Add one ore more scene(s) to the controller.
248
+ * This is the equivalent to `ScrollScene.addTo(controller)`.
249
+ * @public
250
+ * @example
251
+ * // with a previously defined scene
252
+ * controller.addScene(scene);
253
+ *
254
+ * // with a newly created scene.
255
+ * controller.addScene(new ScrollScene({duration : 0}));
256
+ *
257
+ * // adding multiple scenes
258
+ * controller.addScene([scene, scene2, new ScrollScene({duration : 0})]);
259
+ *
260
+ * @param {(ScrollScene|array)} ScrollScene - ScrollScene or Array of ScrollScenes to be added to the controller.
261
+ * @return {ScrollMagic} Parent object for chaining.
262
+ */
263
+ this.addScene = function (newScene) {
264
+ if ($.isArray(newScene)) {
265
+ $.each(newScene, function (index, scene) {
266
+ ScrollMagic.addScene(scene);
267
+ });
268
+ } else if (newScene instanceof ScrollScene) {
269
+ if (newScene.parent() != ScrollMagic) {
270
+ newScene.addTo(ScrollMagic);
271
+ } else if ($.inArray(newScene, _sceneObjects) < 0){
272
+ // new scene
273
+ _sceneObjects.push(newScene); // add to array
274
+ _sceneObjects = sortScenes(_sceneObjects); // sort
275
+ newScene.on("shift." + NAMESPACE + "_sort", function() { // resort whenever scene moves
276
+ _sceneObjects = sortScenes(_sceneObjects);
277
+ });
278
+ // insert Global defaults.
279
+ $.each(_options.globalSceneOptions, function (key, value) {
280
+ if (newScene[key]) {
281
+ newScene[key].call(newScene, value);
282
+ }
283
+ });
284
+ log(3, "added Scene (" + _sceneObjects.length + " total)");
285
+ }
286
+ } else {
287
+ log(1, "ERROR: invalid argument supplied for '.addScene()'");
288
+ }
289
+ return ScrollMagic;
290
+ };
291
+
292
+ /**
293
+ * Remove one ore more scene(s) from the controller.
294
+ * This is the equivalent to `ScrollScene.remove()`.
295
+ * @public
296
+ * @example
297
+ * // remove a scene from the controller
298
+ * controller.removeScene(scene);
299
+ *
300
+ * // remove multiple scenes from the controller
301
+ * controller.removeScene([scene, scene2, scene3]);
302
+ *
303
+ * @param {(ScrollScene|array)} ScrollScene - ScrollScene or Array of ScrollScenes to be removed from the controller.
304
+ * @returns {ScrollMagic} Parent object for chaining.
305
+ */
306
+ this.removeScene = function (ScrollScene) {
307
+ if ($.isArray(ScrollScene)) {
308
+ $.each(ScrollScene, function (index, scene) {
309
+ ScrollMagic.removeScene(scene);
310
+ });
311
+ } else {
312
+ var index = $.inArray(ScrollScene, _sceneObjects);
313
+ if (index > -1) {
314
+ ScrollScene.off("shift." + NAMESPACE + "_sort");
315
+ _sceneObjects.splice(index, 1);
316
+ ScrollScene.remove();
317
+ log(3, "removed Scene (" + _sceneObjects.length + " total)");
318
+ }
319
+ }
320
+ return ScrollMagic;
321
+ };
322
+
323
+ /**
324
+ * Update one ore more scene(s) according to the scroll position of the container.
325
+ * This is the equivalent to `ScrollScene.update()`.
326
+ * The update method calculates the scene's start and end position (based on the trigger element, trigger hook, duration and offset) and checks it against the current scroll position of the container.
327
+ * It then updates the current scene state accordingly (or does nothing, if the state is already correct) – Pins will be set to their correct position and tweens will be updated to their correct progress.
328
+ * _**Note:** This method gets called constantly whenever ScrollMagic detects a change. The only application for you is if you change something outside of the realm of ScrollMagic, like moving the trigger or changing tween parameters._
329
+ * @public
330
+ * @example
331
+ * // update a specific scene on next tick
332
+ * controller.updateScene(scene);
333
+ *
334
+ * // update a specific scene immediately
335
+ * controller.updateScene(scene, true);
336
+ *
337
+ * // update multiple scenes scene on next tick
338
+ * controller.updateScene([scene1, scene2, scene3]);
339
+ *
340
+ * @param {ScrollScene} ScrollScene - ScrollScene or Array of ScrollScenes that is/are supposed to be updated.
341
+ * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next tweenmax tick.
342
+ This is useful when changing multiple properties of the scene - this way it will only be updated once all new properties are set (onTick).
343
+ * @return {ScrollMagic} Parent object for chaining.
344
+ */
345
+ this.updateScene = function (ScrollScene, immediately) {
346
+ if ($.isArray(ScrollScene)) {
347
+ $.each(ScrollScene, function (index, scene) {
348
+ ScrollMagic.updateScene(scene, immediately);
349
+ });
350
+ } else {
351
+ if (immediately) {
352
+ ScrollScene.update(true);
353
+ } else {
354
+ // prep array for next update cycle
355
+ if (!$.isArray(_updateScenesOnNextTick)) {
356
+ _updateScenesOnNextTick = [];
357
+ }
358
+ if ($.inArray(ScrollScene, _updateScenesOnNextTick) == -1) {
359
+ _updateScenesOnNextTick.push(ScrollScene);
360
+ }
361
+ _updateScenesOnNextTick = sortScenes(_updateScenesOnNextTick); // sort
362
+ }
363
+ }
364
+ return ScrollMagic;
365
+ };
366
+
367
+ /**
368
+ * Updates the controller params and calls updateScene on every scene, that is attached to the controller.
369
+ * See `ScrollMagic.updateScene()` for more information about what this means.
370
+ * In most cases you will not need this function, as it is called constantly, whenever ScrollMagic detects a state change event, like resize or scroll.
371
+ * The only application for this method is when ScrollMagic fails to detect these events.
372
+ * One application is with some external scroll libraries (like iScroll) that move an internal container to a negative offset instead of actually scrolling. In this case the update on the controller needs to be called whenever the child container's position changes.
373
+ * For this case there will also be the need to provide a custom function to calculate the correct scroll position. See `ScrollMagic.scrollPos()` for details.
374
+ * @public
375
+ * @example
376
+ * // update the controller on next tick (saves performance)
377
+ * controller.update();
378
+ *
379
+ * // update the controller immediately
380
+ * controller.update(true);
381
+ *
382
+ * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next tweenmax tick (better performance)
383
+ * @return {ScrollMagic} Parent object for chaining.
384
+ */
385
+ this.update = function (immediately) {
386
+ onChange({type: "resize"}); // will update size and set _updateScenesOnNextTick to true
387
+ if (immediately) {
388
+ onTick();
389
+ }
390
+ return ScrollMagic;
391
+ };
392
+
393
+ /**
394
+ * Scroll to a numeric scroll offset, a DOM element, the start of a scene or provide an alternate method for scrolling.
395
+ * For vertical controllers it will change the top scroll offset and for horizontal applications it will change the left offset.
396
+ * @public
397
+ *
398
+ * @since 1.1.0
399
+ * @example
400
+ * // scroll to an offset of 100
401
+ * controller.scrollTo(100);
402
+ *
403
+ * // scroll to a DOM element
404
+ * controller.scrollTo("#anchor");
405
+ *
406
+ * // scroll to the beginning of a scene
407
+ * var scene = new ScrollScene({offset: 200});
408
+ * controller.scrollTo(scene);
409
+ *
410
+ * // define a new scroll position modification function (animate instead of jump)
411
+ * controller.scrollTo(function (newScrollPos) {
412
+ * $("body").animate({scrollTop: newScrollPos});
413
+ * });
414
+ *
415
+ * @param {mixed} [scrollTarget] - The supplied argument can be one of these types:
416
+ * 1. `number` -> The container will scroll to this new scroll offset.
417
+ * 2. `string` or `object` -> Can be a selector, a DOM object or a jQuery element.
418
+ * The container will scroll to the position of this element.
419
+ * 3. `ScrollScene` -> The container will scroll to the start of this scene.
420
+ * 4. `function` -> This function will be used as a callback for future scroll position modifications.
421
+ * This provides a way for you to change the behaviour of scrolling and adding new behaviour like animation. The callback receives the new scroll position as a parameter and a reference to the container element using `this`.
422
+ * _**NOTE:** All other options will still work as expected, using the new function to scroll._
423
+ * @returns {ScrollMagic} Parent object for chaining.
424
+ */
425
+ this.scrollTo = function (scrollTarget) {
426
+ if (scrollTarget instanceof ScrollScene) {
427
+ if (scrollTarget.parent() === ScrollMagic) { // check if this controller is the parent
428
+ ScrollMagic.scrollTo(scrollTarget.scrollOffset());
429
+ } else {
430
+ log (2, "scrollTo(): The supplied scene does not belong to this controller. Scroll cancelled.", scrollTarget);
431
+ }
432
+ } else if ($.type(scrollTarget) === "string" || isDomElement(scrollTarget) || scrollTarget instanceof $) {
433
+ var $elm = $(scrollTarget).first();
434
+ if ($elm[0]) {
435
+ var
436
+ param = _options.vertical ? "top" : "left", // which param is of interest ?
437
+ containerOffset = getOffset(_options.container), // container position is needed because element offset is returned in relation to document, not in relation to container.
438
+ elementOffset = getOffset($elm);
439
+
440
+ if (!_isDocument) { // container is not the document root, so substract scroll Position to get correct trigger element position relative to scrollcontent
441
+ containerOffset[param] -= ScrollMagic.scrollPos();
442
+ }
443
+
444
+ ScrollMagic.scrollTo(elementOffset[param] - containerOffset[param]);
445
+ } else {
446
+ log (2, "scrollTo(): The supplied element could not be found. Scroll cancelled.", scrollTarget);
447
+ }
448
+ } else if ($.isFunction(scrollTarget)) {
449
+ setScrollPos = scrollTarget;
450
+ } else {
451
+ setScrollPos.call(_options.container[0], scrollTarget);
452
+ }
453
+ return ScrollMagic;
454
+ };
455
+
456
+ /**
457
+ * **Get** the current scrollPosition or **Set** a new method to calculate it.
458
+ * -> **GET**:
459
+ * When used as a getter this function will return the current scroll position.
460
+ * To get a cached value use ScrollMagic.info("scrollPos"), which will be updated on tick to save on performance.
461
+ * For vertical controllers it will return the top scroll offset and for horizontal applications it will return the left offset.
462
+ *
463
+ * -> **SET**:
464
+ * When used as a setter this method prodes a way to permanently overwrite the controller's scroll position calculation.
465
+ * A typical usecase is when the scroll position is not reflected by the containers scrollTop or scrollLeft values, but for example by the inner offset of a child container.
466
+ * Moving a child container inside a parent is a commonly used method for several scrolling frameworks, including iScroll.
467
+ * By providing an alternate calculation function you can make sure ScrollMagic receives the correct scroll position.
468
+ * Please also bear in mind that your function should return y values for vertical scrolls an x for horizontals.
469
+ *
470
+ * To change the current scroll position please use `ScrollMagic.scrollTo()`.
471
+ * @public
472
+ *
473
+ * @example
474
+ * // get the current scroll Position
475
+ * var scrollPos = controller.scrollPos();
476
+ *
477
+ * // set a new scroll position calculation method
478
+ * controller.scrollPos(function () {
479
+ * return this.info("vertical") ? -$mychildcontainer.y : -$mychildcontainer.x
480
+ * });
481
+ *
482
+ * @param {function} [scrollPosMethod] - The function to be used for the scroll position calculation of the container.
483
+ * @returns {(number|ScrollMagic)} Current scroll position or parent object for chaining.
484
+ */
485
+ this.scrollPos = function (scrollPosMethod) {
486
+ if (!arguments.length) { // get
487
+ return getScrollPos.call(ScrollMagic);
488
+ } else { // set
489
+ if ($.isFunction(scrollPosMethod)) {
490
+ getScrollPos = scrollPosMethod;
491
+ } else {
492
+ log(2, "Provided value for method 'scrollPos' is not a function. To change the current scroll position use 'scrollTo()'.");
493
+ }
494
+ }
495
+ return ScrollMagic;
496
+ };
497
+
498
+ /**
499
+ * **Get** all infos or one in particular about the controller.
500
+ * @public
501
+ * @example
502
+ * // returns the current scroll position (number)
503
+ * var scrollPos = controller.info("scrollPos");
504
+ *
505
+ * // returns all infos as an object
506
+ * var infos = controller.info();
507
+ *
508
+ * @param {string} [about] - If passed only this info will be returned instead of an object containing all.
509
+ Valid options are:
510
+ ** `"size"` => the current viewport size of the container
511
+ ** `"vertical"` => true if vertical scrolling, otherwise false
512
+ ** `"scrollPos"` => the current scroll position
513
+ ** `"scrollDirection"` => the last known direction of the scroll
514
+ ** `"container"` => the container element
515
+ ** `"isDocument"` => true if container element is the document.
516
+ * @returns {(mixed|object)} The requested info(s).
517
+ */
518
+ this.info = function (about) {
519
+ var values = {
520
+ size: _viewPortSize, // contains height or width (in regard to orientation);
521
+ vertical: _options.vertical,
522
+ scrollPos: _scrollPos,
523
+ scrollDirection: _scrollDirection,
524
+ container: _options.container,
525
+ isDocument: _isDocument
526
+ };
527
+ if (!arguments.length) { // get all as an object
528
+ return values;
529
+ } else if (values[about] !== undefined) {
530
+ return values[about];
531
+ } else {
532
+ log(1, "ERROR: option \"" + about + "\" is not available");
533
+ return;
534
+ }
535
+ };
536
+
537
+ /**
538
+ * **Get** or **Set** the current loglevel option value.
539
+ * @public
540
+ *
541
+ * @example
542
+ * // get the current value
543
+ * var loglevel = controller.loglevel();
544
+ *
545
+ * // set a new value
546
+ * controller.loglevel(3);
547
+ *
548
+ * @param {number} [newLoglevel] - The new loglevel setting of the ScrollMagic controller. `[0-3]`
549
+ * @returns {(number|ScrollMagic)} Current loglevel or parent object for chaining.
550
+ */
551
+ this.loglevel = function (newLoglevel) {
552
+ if (!arguments.length) { // get
553
+ return _options.loglevel;
554
+ } else if (_options.loglevel != newLoglevel) { // set
555
+ _options.loglevel = newLoglevel;
556
+ }
557
+ return ScrollMagic;
558
+ };
559
+
560
+ /**
561
+ * **Get** or **Set** the current enabled state of the controller.
562
+ * This can be used to disable all Scenes connected to the controller without destroying or removing them.
563
+ * @public
564
+ *
565
+ * @example
566
+ * // get the current value
567
+ * var enabled = controller.enabled();
568
+ *
569
+ * // disable the controller
570
+ * controller.enabled(false);
571
+ *
572
+ * @param {boolean} [newState] - The new enabled state of the controller `true` or `false`.
573
+ * @returns {(boolean|ScrollMagic)} Current enabled state or parent object for chaining.
574
+ */
575
+ this.enabled = function (newState) {
576
+ if (!arguments.length) { // get
577
+ return _enabled;
578
+ } else if (_enabled != newState) { // set
579
+ _enabled = !!newState;
580
+ ScrollMagic.updateScene(_sceneObjects, true);
581
+ }
582
+ return ScrollMagic;
583
+ };
584
+
585
+ /**
586
+ * Destroy the Controller, all Scenes and everything.
587
+ * @public
588
+ *
589
+ * @example
590
+ * // without resetting the scenes
591
+ * controller = controller.destroy();
592
+ *
593
+ * // with scene reset
594
+ * controller = controller.destroy(true);
595
+ *
596
+ * @param {boolean} [resetScenes=false] - If `true` the pins and tweens (if existent) of all scenes will be reset.
597
+ * @returns {null} Null to unset handler variables.
598
+ */
599
+ this.destroy = function (resetScenes) {
600
+ window.clearTimeout(_refreshInterval);
601
+ var i = _sceneObjects.length;
602
+ while (i--) {
603
+ _sceneObjects[i].destroy(resetScenes);
604
+ }
605
+ _options.container.off("scroll resize", onChange);
606
+ if (_tickerUsed) {
607
+ TweenLite.ticker.removeEventListener("tick", onTick);
608
+ } else {
609
+ _options.container.off("scroll resize", onTick);
610
+ }
611
+ log(3, "destroyed " + NAMESPACE + " (reset: " + (resetScenes ? "true" : "false") + ")");
612
+ return null;
613
+ };
614
+
615
+ // INIT
616
+ construct();
617
+ return ScrollMagic;
618
+ };
619
+
620
+ /**
621
+ * A ScrollScene defines where the controller should react and how.
622
+ *
623
+ * @class
624
+ * @global
625
+ *
626
+ * @example
627
+ * // create a standard scene and add it to a controller
628
+ * new ScrollScene()
629
+ * .addTo(controller);
630
+ *
631
+ * // create a scene with custom options and assign a handler to it.
632
+ * var scene = new ScrollScene({
633
+ * duration: 100,
634
+ * offset: 200,
635
+ * triggerHook: "onEnter",
636
+ * reverse: false
637
+ * });
638
+ *
639
+ * @param {object} [options] - Options for the Scene. The options can be updated at any time.
640
+ Instead of setting the options for each scene individually you can also set them globally in the controller as the controllers `globalSceneOptions` option. The object accepts the same properties as the ones below.
641
+ When a scene is added to the controller the options defined using the ScrollScene constructor will be overwritten by those set in `globalSceneOptions`.
642
+ * @param {(number|function)} [options.duration=0] - The duration of the scene.
643
+ If `0` tweens will auto-play when reaching the scene start point, pins will be pinned indefinetly starting at the start position.
644
+ A function retuning the duration value is also supported. Please see `ScrollScene.duration()` for details.
645
+ * @param {number} [options.offset=0] - Offset Value for the Trigger Position. If no triggerElement is defined this will be the scroll distance from the start of the page, after which the scene will start.
646
+ * @param {(string|object)} [options.triggerElement=null] - Selector, DOM object or jQuery Object that defines the start of the scene. If undefined the scene will start right at the start of the page (unless an offset is set).
647
+ * @param {(number|string)} [options.triggerHook="onCenter"] - Can be a number between 0 and 1 defining the position of the trigger Hook in relation to the viewport.
648
+ Can also be defined using a string:
649
+ ** `"onEnter"` => `1`
650
+ ** `"onCenter"` => `0.5`
651
+ ** `"onLeave"` => `0`
652
+ * @param {boolean} [options.reverse=true] - Should the scene reverse, when scrolling up?
653
+ * @param {boolean} [options.tweenChanges=false] - Tweens Animation to the progress target instead of setting it.
654
+ Does not affect animations where duration is `0`.
655
+ * @param {number} [options.loglevel=2] - Loglevel for debugging. Note that logging is disabled in the minified version of ScrollMagic.
656
+ ** `0` => silent
657
+ ** `1` => errors
658
+ ** `2` => errors, warnings
659
+ ** `3` => errors, warnings, debuginfo
660
+ *
661
+ */
662
+ var ScrollScene = function (options) {
663
+
664
+ /*
665
+ * ----------------------------------------------------------------
666
+ * settings
667
+ * ----------------------------------------------------------------
668
+ */
669
+
670
+ var
671
+ TRIGGER_HOOK_VALUES = {"onCenter" : 0.5, "onEnter" : 1, "onLeave" : 0},
672
+ NAMESPACE = "ScrollScene",
673
+ DEFAULT_OPTIONS = {
674
+ duration: 0,
675
+ offset: 0,
676
+ triggerElement: null,
677
+ triggerHook: "onCenter",
678
+ reverse: true,
679
+ tweenChanges: false,
680
+ loglevel: 2
681
+ };
682
+
683
+ /*
684
+ * ----------------------------------------------------------------
685
+ * private vars
686
+ * ----------------------------------------------------------------
687
+ */
688
+
689
+ var
690
+ ScrollScene = this,
691
+ _options = $.extend({}, DEFAULT_OPTIONS, options),
692
+ _state = 'BEFORE',
693
+ _progress = 0,
694
+ _scrollOffset = {start: 0, end: 0}, // reflects the parent's scroll position for the start and end of the scene respectively
695
+ _triggerPos = 0,
696
+ _enabled = true,
697
+ _durationUpdateMethod,
698
+ _parent,
699
+ _tween,
700
+ _pin,
701
+ _pinOptions,
702
+ _cssClasses,
703
+ _cssClassElm;
704
+
705
+ // object containing validator functions for various options
706
+ var _validate = {
707
+ "unknownOptionSupplied" : function () {
708
+ $.each(_options, function (key, value) {
709
+ if (!DEFAULT_OPTIONS.hasOwnProperty(key)) {
710
+ log(2, "WARNING: Unknown option \"" + key + "\"");
711
+ delete _options[key];
712
+ }
713
+ });
714
+ },
715
+ "duration" : function () {
716
+ if ($.isFunction(_options.duration)) {
717
+ _durationUpdateMethod = _options.duration;
718
+ try {
719
+ _options.duration = parseFloat(_durationUpdateMethod());
720
+ } catch (e) {
721
+ log(1, "ERROR: Invalid return value of supplied function for option \"duration\":", _options.duration);
722
+ _durationUpdateMethod = undefined;
723
+ _options.duration = DEFAULT_OPTIONS.duration;
724
+ }
725
+ } else {
726
+ _options.duration = parseFloat(_options.duration);
727
+ if (!$.isNumeric(_options.duration) || _options.duration < 0) {
728
+ log(1, "ERROR: Invalid value for option \"duration\":", _options.duration);
729
+ _options.duration = DEFAULT_OPTIONS.duration;
730
+ }
731
+ }
732
+ },
733
+ "offset" : function () {
734
+ _options.offset = parseFloat(_options.offset);
735
+ if (!$.isNumeric(_options.offset)) {
736
+ log(1, "ERROR: Invalid value for option \"offset\":", _options.offset);
737
+ _options.offset = DEFAULT_OPTIONS.offset;
738
+ }
739
+ },
740
+ "triggerElement" : function () {
741
+ if (_options.triggerElement !== null && $(_options.triggerElement).length === 0) {
742
+ log(1, "ERROR: Element defined in option \"triggerElement\" was not found:", _options.triggerElement);
743
+ _options.triggerElement = DEFAULT_OPTIONS.triggerElement;
744
+ }
745
+ },
746
+ "triggerHook" : function () {
747
+ if (!(_options.triggerHook in TRIGGER_HOOK_VALUES)) {
748
+ if ($.isNumeric(_options.triggerHook)) {
749
+ _options.triggerHook = Math.max(0, Math.min(parseFloat(_options.triggerHook), 1)); // make sure its betweeen 0 and 1
750
+ } else {
751
+ log(1, "ERROR: Invalid value for option \"triggerHook\": ", _options.triggerHook);
752
+ _options.triggerHook = DEFAULT_OPTIONS.triggerHook;
753
+ }
754
+ }
755
+ },
756
+ "reverse" : function () {
757
+ _options.reverse = !!_options.reverse; // force boolean
758
+ },
759
+ "tweenChanges" : function () {
760
+ _options.tweenChanges = !!_options.tweenChanges; // force boolean
761
+ },
762
+ "loglevel" : function () {
763
+ _options.loglevel = parseInt(_options.loglevel);
764
+ if (!$.isNumeric(_options.loglevel) || _options.loglevel < 0 || _options.loglevel > 3) {
765
+ var wrongval = _options.loglevel;
766
+ _options.loglevel = DEFAULT_OPTIONS.loglevel;
767
+ log(1, "ERROR: Invalid value for option \"loglevel\":", wrongval);
768
+ }
769
+ },
770
+ "checkIfTriggerElementIsTweened" : function () {
771
+ // check if there are position tweens defined for the trigger and warn about it :)
772
+ if (_tween && _parent && _options.triggerElement && _options.loglevel >= 2) {// parent is needed to know scroll direction.
773
+ var
774
+ triggerTweens = _tween.getTweensOf($(_options.triggerElement)),
775
+ vertical = _parent.info("vertical");
776
+ $.each(triggerTweens, function (index, value) {
777
+ var
778
+ tweenvars = value.vars.css || value.vars,
779
+ condition = vertical ? (tweenvars.top !== undefined || tweenvars.bottom !== undefined) : (tweenvars.left !== undefined || tweenvars.right !== undefined);
780
+ if (condition) {
781
+ log(2, "WARNING: Tweening the position of the trigger element affects the scene timing and should be avoided!");
782
+ return false;
783
+ }
784
+ });
785
+ }
786
+ },
787
+ };
788
+
789
+ /*
790
+ * ----------------------------------------------------------------
791
+ * private functions
792
+ * ----------------------------------------------------------------
793
+ */
794
+
795
+ /**
796
+ * Internal constructor function of ScrollMagic
797
+ * @private
798
+ */
799
+ var construct = function () {
800
+ validateOption();
801
+
802
+ // event listeners
803
+ ScrollScene
804
+ .on("change.internal", function (e) {
805
+ if (e.what !== "loglevel" && e.what !== "tweenChanges") { // no need for a scene update scene with these options...
806
+ if (e.what === "triggerElement") {
807
+ updateTriggerElementPosition();
808
+ } else if (e.what === "reverse") { // the only property left that may have an impact on the current scene state. Everything else is handled by the shift event.
809
+ ScrollScene.update();
810
+ }
811
+ }
812
+ })
813
+ .on("shift.internal", function (e) {
814
+ updateScrollOffset();
815
+ ScrollScene.update(); // update scene to reflect new position
816
+ if ((_state === "AFTER" && e.reason === "duration") || (_state === 'DURING' && _options.duration === 0)) {
817
+ // if [duration changed after a scene (inside scene progress updates pin position)] or [duration is 0, we are in pin phase and some other value changed].
818
+ updatePinState();
819
+ }
820
+ })
821
+ .on("progress.internal", function (e) {
822
+ updateTweenProgress();
823
+ updatePinState();
824
+ })
825
+ .on("destroy", function (e) {
826
+ e.preventDefault(); // otherwise jQuery would call target.destroy() by default.
827
+ });
828
+ };
829
+
830
+ /**
831
+ * Send a debug message to the console.
832
+ * @private
833
+ *
834
+ * @param {number} loglevel - The loglevel required to initiate output for the message.
835
+ * @param {...mixed} output - One or more variables that should be passed to the console.
836
+ */
837
+ var log = function (loglevel, output) {
838
+ if (_options.loglevel >= loglevel) {
839
+ var
840
+ prefix = "(" + NAMESPACE + ") ->",
841
+ args = Array.prototype.splice.call(arguments, 1);
842
+ args.unshift(loglevel, prefix);
843
+ debug.apply(window, args);
844
+ }
845
+ };
846
+
847
+ /**
848
+ * Checks the validity of a specific or all options and reset to default if neccessary.
849
+ * @private
850
+ */
851
+ var validateOption = function (check) {
852
+ if (!arguments.length) {
853
+ check = [];
854
+ for (var key in _validate){
855
+ check.push(key);
856
+ }
857
+ } else if (!$.isArray(check)) {
858
+ check = [check];
859
+ }
860
+ $.each(check, function (key, value) {
861
+ if (_validate[value]) {
862
+ _validate[value]();
863
+ }
864
+ });
865
+ };
866
+
867
+ /**
868
+ * Helper used by the setter/getters for scene options
869
+ * @private
870
+ */
871
+ var changeOption = function(varname, newval) {
872
+ var
873
+ changed = false,
874
+ oldval = _options[varname];
875
+ if (_options[varname] != newval) {
876
+ _options[varname] = newval;
877
+ validateOption(varname); // resets to default if necessary
878
+ changed = oldval != _options[varname];
879
+ }
880
+ return changed;
881
+ };
882
+
883
+ /**
884
+ * Update the start and end scrollOffset of the container.
885
+ * The positions reflect what the parent's scroll position will be at the start and end respectively.
886
+ * Is called, when:
887
+ * - ScrollScene event "change" is called with: offset, triggerHook, duration
888
+ * - scroll container event "resize" is called
889
+ * - the position of the triggerElement changes
890
+ * - the parent changes -> addTo()
891
+ * @private
892
+ */
893
+ var updateScrollOffset = function () {
894
+ _scrollOffset = {start: _triggerPos + _options.offset};
895
+ if (_parent && _options.triggerElement) {
896
+ // take away triggerHook portion to get relative to top
897
+ _scrollOffset.start -= _parent.info("size") * ScrollScene.triggerHook();
898
+ }
899
+ _scrollOffset.end = _scrollOffset.start + _options.duration;
900
+ };
901
+
902
+ /**
903
+ * Updates the duration if set to a dynamic function.
904
+ * This method is called when the scene is added to a controller and in regular intervals from the controller through scene.refresh().
905
+ *
906
+ * @fires {@link ScrollScene.change}, if the duration changed
907
+ * @fires {@link ScrollScene.shift}, if the duration changed
908
+ *
909
+ * @param {boolean} [suppressEvents=false] - If true the shift event will be suppressed.
910
+ * @private
911
+ */
912
+ var updateDuration = function (suppressEvents) {
913
+ // update duration
914
+ if (_durationUpdateMethod) {
915
+ var varname = "duration";
916
+ if (changeOption(varname, _durationUpdateMethod.call(ScrollScene)) && !suppressEvents) { // set
917
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
918
+ ScrollScene.trigger("shift", {reason: varname});
919
+ }
920
+ }
921
+ };
922
+
923
+ /**
924
+ * Updates the position of the triggerElement, if present.
925
+ * This method is called ...
926
+ * - ... when the triggerElement is changed
927
+ * - ... when the scene is added to a (new) controller
928
+ * - ... in regular intervals from the controller through scene.refresh().
929
+ *
930
+ * @fires {@link ScrollScene.shift}, if the position changed
931
+ *
932
+ * @param {boolean} [suppressEvents=false] - If true the shift event will be suppressed.
933
+ * @private
934
+ */
935
+ var updateTriggerElementPosition = function (suppressEvents) {
936
+ var elementPos = 0;
937
+ if (_parent && _options.triggerElement) {
938
+ var
939
+ element = $(_options.triggerElement).first(),
940
+ controllerInfo = _parent.info(),
941
+ containerOffset = getOffset(controllerInfo.container), // container position is needed because element offset is returned in relation to document, not in relation to container.
942
+ param = controllerInfo.vertical ? "top" : "left"; // which param is of interest ?
943
+
944
+ // if parent is spacer, use spacer position instead so correct start position is returned for pinned elements.
945
+ while (element.parent().data("ScrollMagicPinSpacer")) {
946
+ element = element.parent();
947
+ }
948
+
949
+ var elementOffset = getOffset(element);
950
+
951
+ if (!controllerInfo.isDocument) { // container is not the document root, so substract scroll Position to get correct trigger element position relative to scrollcontent
952
+ containerOffset[param] -= _parent.scrollPos();
953
+ }
954
+
955
+ elementPos = elementOffset[param] - containerOffset[param];
956
+ }
957
+ var changed = elementPos != _triggerPos;
958
+ _triggerPos = elementPos;
959
+ if (changed && !suppressEvents) {
960
+ ScrollScene.trigger("shift", {reason: "triggerElementPosition"});
961
+ }
962
+ };
963
+
964
+ /**
965
+ * Update the tween progress.
966
+ * @private
967
+ *
968
+ * @param {number} [to] - If not set the scene Progress will be used. (most cases)
969
+ * @return {boolean} true if the Tween was updated.
970
+ */
971
+ var updateTweenProgress = function (to) {
972
+ if (_tween) {
973
+ var progress = (to >= 0 && to <= 1) ? to : _progress;
974
+ if (_tween.repeat() === -1) {
975
+ // infinite loop, so not in relation to progress
976
+ if (_state === "DURING" && _tween.paused()) {
977
+ _tween.play();
978
+ } else if (_state !== "DURING" && !_tween.paused()) {
979
+ _tween.pause();
980
+ } else {
981
+ return false;
982
+ }
983
+ } else if (progress != _tween.progress()) { // do we even need to update the progress?
984
+ // no infinite loop - so should we just play or go to a specific point in time?
985
+ if (_options.duration === 0) {
986
+ // play the animation
987
+ if (_state === "DURING") { // play from 0 to 1
988
+ _tween.play();
989
+ } else { // play from 1 to 0
990
+ _tween.reverse();
991
+ }
992
+ } else {
993
+ // go to a specific point in time
994
+ if (_options.tweenChanges) {
995
+ // go smooth
996
+ _tween.tweenTo(progress * _tween.duration());
997
+ } else {
998
+ // just hard set it
999
+ _tween.progress(progress).pause();
1000
+ }
1001
+ }
1002
+ } else {
1003
+ return false;
1004
+ }
1005
+ return true;
1006
+ } else {
1007
+ return false;
1008
+ }
1009
+ };
1010
+
1011
+ /**
1012
+ * Update the pin state.
1013
+ * @private
1014
+ */
1015
+ var updatePinState = function (forceUnpin) {
1016
+ if (_pin && _parent) {
1017
+ var
1018
+ containerInfo = _parent.info();
1019
+
1020
+ if (!forceUnpin && _state === "DURING") { // during scene or if duration is 0 and we are past the trigger
1021
+ // pinned state
1022
+ if (_pin.css("position") != "fixed") {
1023
+ // change state before updating pin spacer (position changes due to fixed collapsing might occur.)
1024
+ _pin.css("position", "fixed");
1025
+ // update pin spacer
1026
+ updatePinSpacerSize();
1027
+ // add pinned class
1028
+ _pin.addClass(_pinOptions.pinnedClass);
1029
+ }
1030
+
1031
+ var
1032
+ fixedPos = getOffset(_pinOptions.spacer, true), // get viewport position of spacer
1033
+ scrollDistance = _options.reverse || _options.duration === 0 ?
1034
+ containerInfo.scrollPos - _scrollOffset.start // quicker
1035
+ : Math.round(_progress * _options.duration * 10)/10; // if no reverse and during pin the position needs to be recalculated using the progress
1036
+
1037
+ // remove spacer margin to get real position (in case marginCollapse mode)
1038
+ fixedPos.top -= parseFloat(_pinOptions.spacer.css("margin-top"));
1039
+
1040
+ // add scrollDistance
1041
+ fixedPos[containerInfo.vertical ? "top" : "left"] += scrollDistance;
1042
+
1043
+ // set new values
1044
+ _pin.css({
1045
+ top: fixedPos.top,
1046
+ left: fixedPos.left
1047
+ });
1048
+ } else {
1049
+ // unpinned state
1050
+ var
1051
+ newCSS = {
1052
+ position: _pinOptions.inFlow ? "relative" : "absolute",
1053
+ top: 0,
1054
+ left: 0
1055
+ },
1056
+ change = _pin.css("position") != newCSS.position;
1057
+
1058
+ if (!_pinOptions.pushFollowers) {
1059
+ newCSS[containerInfo.vertical ? "top" : "left"] = _options.duration * _progress;
1060
+ } else if (_options.duration > 0) { // only concerns scenes with duration
1061
+ if (_state === "AFTER" && parseFloat(_pinOptions.spacer.css("padding-top")) === 0) {
1062
+ change = true; // if in after state but havent updated spacer yet (jumped past pin)
1063
+ } else if (_state === "BEFORE" && parseFloat(_pinOptions.spacer.css("padding-bottom")) === 0) { // before
1064
+ change = true; // jumped past fixed state upward direction
1065
+ }
1066
+ }
1067
+ // set new values
1068
+ _pin.css(newCSS);
1069
+ if (change) {
1070
+ // remove pinned class
1071
+ _pin.removeClass(_pinOptions.pinnedClass);
1072
+ // update pin spacer if state changed
1073
+ updatePinSpacerSize();
1074
+ }
1075
+ }
1076
+ }
1077
+ };
1078
+
1079
+ /**
1080
+ * Update the pin spacer size.
1081
+ * The size of the spacer needs to be updated whenever the duration of the scene changes, if it is to push down following elements.
1082
+ * @private
1083
+ */
1084
+ var updatePinSpacerSize = function () {
1085
+ if (_pin && _parent && _pinOptions.inFlow) { // no spacerresize, if original position is absolute
1086
+ var
1087
+ after = (_state === "AFTER"),
1088
+ before = (_state === "BEFORE"),
1089
+ during = (_state === "DURING"),
1090
+ pinned = (_pin.css("position") == "fixed"),
1091
+ vertical = _parent.info("vertical"),
1092
+ $spacercontent = _pinOptions.spacer.children().first(), // usually the pined element but can also be another spacer (cascaded pins)
1093
+ marginCollapse = isMarginCollapseType(_pinOptions.spacer.css("display")),
1094
+ css = {};
1095
+
1096
+ if (marginCollapse) {
1097
+ css["margin-top"] = before || (during && pinned) ? _pin.css("margin-top") : "auto";
1098
+ css["margin-bottom"] = after || (during && pinned) ? _pin.css("margin-bottom") : "auto";
1099
+ } else {
1100
+ css["margin-top"] = css["margin-bottom"] = "auto";
1101
+ }
1102
+
1103
+ // set new size
1104
+ // if relsize: spacer -> pin | else: pin -> spacer
1105
+ if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) {
1106
+ if (pinned) {
1107
+ if ($(window).width() == _pinOptions.spacer.parent().width()) {
1108
+ // relative to body
1109
+ _pin.css("width", _pinOptions.relSize.autoFullWidth ? "100%" : "inherit");
1110
+ } else {
1111
+ // not relative to body -> need to calculate
1112
+ _pin.css("width", _pinOptions.spacer.width());
1113
+ }
1114
+ } else {
1115
+ _pin.css("width", "100%");
1116
+ }
1117
+ } else {
1118
+ // minwidth is needed for cascading pins.
1119
+ // margin is only included if it's a cascaded pin to resolve an IE9 bug
1120
+ css["min-width"] = $spacercontent.outerWidth(!$spacercontent.is(_pin));
1121
+ css.width = pinned ? css["min-width"] : "auto";
1122
+ }
1123
+ if (_pinOptions.relSize.height) {
1124
+ if (pinned) {
1125
+ if ($(window).height() == _pinOptions.spacer.parent().height()) {
1126
+ // relative to body
1127
+ _pin.css("height", "inherit");
1128
+ } else {
1129
+ // not relative to body -> need to calculate
1130
+ _pin.css("height", _pinOptions.spacer.height());
1131
+ }
1132
+ } else {
1133
+ _pin.css("height", "100%");
1134
+ }
1135
+ } else {
1136
+ css["min-height"] = $spacercontent.outerHeight(!marginCollapse); // needed for cascading pins
1137
+ css.height = pinned ? css["min-height"] : "auto";
1138
+ }
1139
+
1140
+ // add space for duration if pushFollowers is true
1141
+ if (_pinOptions.pushFollowers) {
1142
+ css["padding" + (vertical ? "Top" : "Left")] = _options.duration * _progress;
1143
+ css["padding" + (vertical ? "Bottom" : "Right")] = _options.duration * (1 - _progress);
1144
+ }
1145
+ _pinOptions.spacer.css(css);
1146
+ }
1147
+ };
1148
+
1149
+ /**
1150
+ * Updates the Pin state (in certain scenarios)
1151
+ * If the controller container is not the document and we are mid-pin-phase scrolling or resizing the main document can result to wrong pin positions.
1152
+ * So this function is called on resize and scroll of the document.
1153
+ * @private
1154
+ */
1155
+ var updatePinInContainer = function () {
1156
+ if (_parent && _pin && _state === "DURING" && !_parent.info("isDocument")) {
1157
+ updatePinState();
1158
+ }
1159
+ };
1160
+
1161
+ /**
1162
+ * Updates the Pin spacer size state (in certain scenarios)
1163
+ * If container is resized during pin and relatively sized the size of the pin might need to be updated...
1164
+ * So this function is called on resize of the container.
1165
+ * @private
1166
+ */
1167
+ var updateRelativePinSpacer = function () {
1168
+ if ( _parent && _pin && // well, duh
1169
+ _state === "DURING" && // element in pinned state?
1170
+ ( // is width or height relatively sized, but not in relation to body? then we need to recalc.
1171
+ ((_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) && $(window).width() != _pinOptions.spacer.parent().width()) ||
1172
+ (_pinOptions.relSize.height && $(window).height() != _pinOptions.spacer.parent().height())
1173
+ )
1174
+ ) {
1175
+ updatePinSpacerSize();
1176
+ }
1177
+ };
1178
+
1179
+ /**
1180
+ * Is called, when the mousewhel is used while over a pinned element.
1181
+ * If the scene is in fixed state scroll events used to be ignored. This forwards the event to the scroll container.
1182
+ * @private
1183
+ */
1184
+ var onMousewheelOverPin = function (e) {
1185
+ if (_parent && _pin && _state === "DURING") { // in pin state
1186
+ _parent.scrollTo(_parent.info("scrollPos") - (e.originalEvent.wheelDelta/3 || -e.originalEvent.detail*30));
1187
+ }
1188
+ };
1189
+
1190
+
1191
+ /*
1192
+ * ----------------------------------------------------------------
1193
+ * public functions (getters/setters)
1194
+ * ----------------------------------------------------------------
1195
+ */
1196
+
1197
+ /**
1198
+ * **Get** the parent controller.
1199
+ * @public
1200
+ * @example
1201
+ * // get the parent controller of a scene
1202
+ * var controller = scene.parent();
1203
+ *
1204
+ * @returns {ScrollMagic} Parent controller or `undefined`
1205
+ */
1206
+ this.parent = function () {
1207
+ return _parent;
1208
+ };
1209
+
1210
+
1211
+ /**
1212
+ * **Get** or **Set** the duration option value.
1213
+ * As a setter it also accepts a function returning a numeric value.
1214
+ * This is particularly useful for responsive setups.
1215
+ *
1216
+ * The duration is updated using the supplied function every time `ScrollScene.refresh()` is called, which happens periodically from the controller (see ScrollMagic option `refreshInterval`).
1217
+ * _**NOTE:** Be aware that it's an easy way to kill performance, if you supply a function that has high CPU demand.
1218
+ * Even for size and position calculations it is recommended to use a variable to cache the value. (see example)
1219
+ * This counts double if you use the same function for multiple scenes._
1220
+ *
1221
+ * @public
1222
+ * @example
1223
+ * // get the current duration value
1224
+ * var duration = scene.duration();
1225
+ *
1226
+ * // set a new duration
1227
+ * scene.duration(300);
1228
+ *
1229
+ * // use a function to automatically adjust the duration to the window height.
1230
+ * var durationValueCache;
1231
+ * function getDuration () {
1232
+ * return durationValueCache;
1233
+ * }
1234
+ * function updateDuration (e) {
1235
+ * durationValueCache = $(window).innerHeight();
1236
+ * }
1237
+ * $(window).on("resize", updateDuration); // update the duration when the window size changes
1238
+ * $(window).triggerHandler("resize"); // set to initial value
1239
+ * scene.duration(getDuration); // supply duration method
1240
+ *
1241
+ * @fires {@link ScrollScene.change}, when used as setter
1242
+ * @fires {@link ScrollScene.shift}, when used as setter
1243
+ * @param {(number|function)} [newDuration] - The new duration of the scene.
1244
+ * @returns {number} `get` - Current scene duration.
1245
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1246
+ */
1247
+ this.duration = function (newDuration) {
1248
+ var varname = "duration";
1249
+ if (!arguments.length) { // get
1250
+ return _options[varname];
1251
+ } else {
1252
+ if (!$.isFunction(newDuration)) {
1253
+ _durationUpdateMethod = undefined;
1254
+ }
1255
+ if (changeOption(varname, newDuration)) { // set
1256
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
1257
+ ScrollScene.trigger("shift", {reason: varname});
1258
+ }
1259
+ }
1260
+ return ScrollScene;
1261
+ };
1262
+
1263
+ /**
1264
+ * **Get** or **Set** the offset option value.
1265
+ * @public
1266
+ * @example
1267
+ * // get the current offset
1268
+ * var offset = scene.offset();
1269
+ *
1270
+ * // set a new offset
1271
+ * scene.offset(100);
1272
+ *
1273
+ * @fires {@link ScrollScene.change}, when used as setter
1274
+ * @fires {@link ScrollScene.shift}, when used as setter
1275
+ * @param {number} [newOffset] - The new offset of the scene.
1276
+ * @returns {number} `get` - Current scene offset.
1277
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1278
+ */
1279
+ this.offset = function (newOffset) {
1280
+ var varname = "offset";
1281
+ if (!arguments.length) { // get
1282
+ return _options[varname];
1283
+ } else if (changeOption(varname, newOffset)) { // set
1284
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
1285
+ ScrollScene.trigger("shift", {reason: varname});
1286
+ }
1287
+ return ScrollScene;
1288
+ };
1289
+
1290
+ /**
1291
+ * **Get** or **Set** the triggerElement option value.
1292
+ * Does **not** fire `ScrollScene.shift`, because changing the trigger Element doesn't necessarily mean the start position changes. This will be determined in `ScrollScene.refresh()`, which is automatically triggered.
1293
+ * @public
1294
+ * @example
1295
+ * // get the current triggerElement
1296
+ * var triggerElement = scene.triggerElement();
1297
+ *
1298
+ * // set a new triggerElement using a selector
1299
+ * scene.triggerElement("#trigger");
1300
+ * // set a new triggerElement using a jQuery Object
1301
+ * scene.triggerElement($("#trigger"));
1302
+ * // set a new triggerElement using a DOM object
1303
+ * scene.triggerElement(document.getElementById("trigger"));
1304
+ *
1305
+ * @fires {@link ScrollScene.change}, when used as setter
1306
+ * @param {(string|object)} [newTriggerElement] - The new trigger element for the scene.
1307
+ * @returns {(string|object)} `get` - Current triggerElement.
1308
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1309
+ */
1310
+ this.triggerElement = function (newTriggerElement) {
1311
+ var varname = "triggerElement";
1312
+ if (!arguments.length) { // get
1313
+ return _options[varname];
1314
+ } else if (changeOption(varname, newTriggerElement)) { // set
1315
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
1316
+ }
1317
+ return ScrollScene;
1318
+ };
1319
+
1320
+ /**
1321
+ * **Get** or **Set** the triggerHook option value.
1322
+ * @public
1323
+ * @example
1324
+ * // get the current triggerHook value
1325
+ * var triggerHook = scene.triggerHook();
1326
+ *
1327
+ * // set a new triggerHook using a string
1328
+ * scene.triggerHook("onLeave");
1329
+ * // set a new triggerHook using a number
1330
+ * scene.triggerHook(0.7);
1331
+ *
1332
+ * @fires {@link ScrollScene.change}, when used as setter
1333
+ * @fires {@link ScrollScene.shift}, when used as setter
1334
+ * @param {(number|string)} [newTriggerHook] - The new triggerHook of the scene. See {@link ScrollScene} parameter description for value options.
1335
+ * @returns {number} `get` - Current triggerHook (ALWAYS numerical).
1336
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1337
+ */
1338
+ this.triggerHook = function (newTriggerHook) {
1339
+ var varname = "triggerHook";
1340
+ if (!arguments.length) { // get
1341
+ return $.isNumeric(_options[varname]) ? _options[varname] : TRIGGER_HOOK_VALUES[_options[varname]];
1342
+ } else if (changeOption(varname, newTriggerHook)) { // set
1343
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
1344
+ ScrollScene.trigger("shift", {reason: varname});
1345
+ }
1346
+ return ScrollScene;
1347
+ };
1348
+
1349
+ /**
1350
+ * **Get** or **Set** the reverse option value.
1351
+ * @public
1352
+ * @example
1353
+ * // get the current reverse option
1354
+ * var reverse = scene.reverse();
1355
+ *
1356
+ * // set new reverse option
1357
+ * scene.reverse(false);
1358
+ *
1359
+ * @fires {@link ScrollScene.change}, when used as setter
1360
+ * @param {boolean} [newReverse] - The new reverse setting of the scene.
1361
+ * @returns {boolean} `get` - Current reverse option value.
1362
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1363
+ */
1364
+ this.reverse = function (newReverse) {
1365
+ var varname = "reverse";
1366
+ if (!arguments.length) { // get
1367
+ return _options[varname];
1368
+ } else if (changeOption(varname, newReverse)) { // set
1369
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
1370
+ }
1371
+ return ScrollScene;
1372
+ };
1373
+
1374
+ /**
1375
+ * **Get** or **Set** the tweenChanges option value.
1376
+ * @public
1377
+ * @example
1378
+ * // get the current tweenChanges option
1379
+ * var tweenChanges = scene.tweenChanges();
1380
+ *
1381
+ * // set new tweenChanges option
1382
+ * scene.tweenChanges(true);
1383
+ *
1384
+ * @fires {@link ScrollScene.change}, when used as setter
1385
+ * @param {boolean} [newTweenChanges] - The new tweenChanges setting of the scene.
1386
+ * @returns {boolean} `get` - Current tweenChanges option value.
1387
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1388
+ */
1389
+ this.tweenChanges = function (newTweenChanges) {
1390
+ var varname = "tweenChanges";
1391
+ if (!arguments.length) { // get
1392
+ return _options[varname];
1393
+ } else if (changeOption(varname, newTweenChanges)) { // set
1394
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
1395
+ }
1396
+ return ScrollScene;
1397
+ };
1398
+
1399
+ /**
1400
+ * **Get** or **Set** the loglevel option value.
1401
+ * @public
1402
+ * @example
1403
+ * // get the current loglevel
1404
+ * var loglevel = scene.loglevel();
1405
+ *
1406
+ * // set new loglevel
1407
+ * scene.loglevel(3);
1408
+ *
1409
+ * @fires {@link ScrollScene.change}, when used as setter
1410
+ * @param {number} [newLoglevel] - The new loglevel setting of the scene. `[0-3]`
1411
+ * @returns {number} `get` - Current loglevel.
1412
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1413
+ */
1414
+ this.loglevel = function (newLoglevel) {
1415
+ var varname = "loglevel";
1416
+ if (!arguments.length) { // get
1417
+ return _options[varname];
1418
+ } else if (changeOption(varname, newLoglevel)) { // set
1419
+ ScrollScene.trigger("change", {what: varname, newval: _options[varname]});
1420
+ }
1421
+ return ScrollScene;
1422
+ };
1423
+
1424
+ /**
1425
+ * **Get** the current state.
1426
+ * @public
1427
+ * @example
1428
+ * // get the current state
1429
+ * var state = scene.state();
1430
+ *
1431
+ * @returns {string} `"BEFORE"`, `"DURING"` or `"AFTER"`
1432
+ */
1433
+ this.state = function () {
1434
+ return _state;
1435
+ };
1436
+
1437
+ /**
1438
+ * **Get** the trigger position of the scene (including the value of the `offset` option).
1439
+ * @public
1440
+ * @example
1441
+ * // get the scene's trigger position
1442
+ * var triggerPosition = scene.triggerPosition();
1443
+ *
1444
+ * @returns {number} Start position of the scene. Top position value for vertical and left position value for horizontal scrolls.
1445
+ */
1446
+ this.triggerPosition = function () {
1447
+ var pos = _options.offset; // the offset is the basis
1448
+ if (_parent) {
1449
+ // get the trigger position
1450
+ if (_options.triggerElement) {
1451
+ // Element as trigger
1452
+ pos += _triggerPos;
1453
+ } else {
1454
+ // return the height of the triggerHook to start at the beginning
1455
+ pos += _parent.info("size") * ScrollScene.triggerHook();
1456
+ }
1457
+ }
1458
+ return pos;
1459
+ };
1460
+
1461
+ /**
1462
+ * **Get** the trigger offset of the scene (including the value of the `offset` option).
1463
+ * @public
1464
+ * @deprecated Method is deprecated since 1.1.0. You should now use {@link ScrollScene.triggerPosition}
1465
+ */
1466
+ this.triggerOffset = function () {
1467
+ return ScrollScene.triggerPosition();
1468
+ };
1469
+
1470
+ /**
1471
+ * **Get** the current scroll offset for the start of the scene.
1472
+ * Mind, that the scrollOffset is related to the size of the container, if `triggerHook` is bigger than `0` (or `"onLeave"`).
1473
+ * This means, that resizing the container or changing the `triggerHook` will influence the scene's start offset.
1474
+ * @public
1475
+ * @example
1476
+ * // get the current scroll offset for the start and end of the scene.
1477
+ * var start = scene.scrollOffset();
1478
+ * var end = scene.scrollOffset() + scene.duration();
1479
+ * console.log("the scene starts at", start, "and ends at", end);
1480
+ *
1481
+ * @returns {number} The scroll offset (of the container) at which the scene will trigger. Y value for vertical and X value for horizontal scrolls.
1482
+ */
1483
+ this.scrollOffset = function () {
1484
+ return _scrollOffset.start;
1485
+ };
1486
+
1487
+ /*
1488
+ * ----------------------------------------------------------------
1489
+ * public functions (scene modification)
1490
+ * ----------------------------------------------------------------
1491
+ */
1492
+
1493
+ /**
1494
+ * Updates the Scene in the parent Controller to reflect the current state.
1495
+ * This is the equivalent to `ScrollMagic.updateScene(scene, immediately)`.
1496
+ * The update method calculates the scene's start and end position (based on the trigger element, trigger hook, duration and offset) and checks it against the current scroll position of the container.
1497
+ * It then updates the current scene state accordingly (or does nothing, if the state is already correct) – Pins will be set to their correct position and tweens will be updated to their correct progress.
1498
+ * This means an update doesn't necessarily result in a progress change. The `progress` event will be fired if the progress has indeed changed between this update and the last.
1499
+ * _**NOTE:** This method gets called constantly whenever ScrollMagic detects a change. The only application for you is if you change something outside of the realm of ScrollMagic, like moving the trigger or changing tween parameters._
1500
+ * @public
1501
+ * @example
1502
+ * // update the scene on next tick
1503
+ * scene.update();
1504
+ *
1505
+ * // update the scene immediately
1506
+ * scene.update(true);
1507
+ *
1508
+ * @fires ScrollScene.update
1509
+ *
1510
+ * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next tweenmax tick (better performance).
1511
+ * @returns {ScrollScene} Parent object for chaining.
1512
+ */
1513
+ this.update = function (immediately) {
1514
+ if (_parent) {
1515
+ if (immediately) {
1516
+ if (_parent.enabled() && _enabled) {
1517
+ var
1518
+ scrollPos = _parent.info("scrollPos"),
1519
+ newProgress;
1520
+
1521
+ if (_options.duration > 0) {
1522
+ newProgress = (scrollPos - _scrollOffset.start)/(_scrollOffset.end - _scrollOffset.start);
1523
+ } else {
1524
+ newProgress = scrollPos >= _scrollOffset.start ? 1 : 0;
1525
+ }
1526
+
1527
+ ScrollScene.trigger("update", {startPos: _scrollOffset.start, endPos: _scrollOffset.end, scrollPos: scrollPos});
1528
+
1529
+ ScrollScene.progress(newProgress);
1530
+ } else if (_pin && _state === "DURING") {
1531
+ updatePinState(true); // unpin in position
1532
+ }
1533
+ } else {
1534
+ _parent.updateScene(ScrollScene, false);
1535
+ }
1536
+ }
1537
+ return ScrollScene;
1538
+ };
1539
+
1540
+ /**
1541
+ * Updates dynamic scene variables like the trigger element position or the duration.
1542
+ * This method is automatically called in regular intervals from the controller. See {@link ScrollMagic} option `refreshInterval`.
1543
+ *
1544
+ * You can call it to minimize lag, for example when you intentionally change the position of the triggerElement.
1545
+ * If you don't it will simply be updated in the next refresh interval of the container, which is usually sufficient.
1546
+ *
1547
+ * @public
1548
+ * @since 1.1.0
1549
+ * @example
1550
+ * scene = new ScrollScene({triggerElement: "#trigger"});
1551
+ *
1552
+ * // change the position of the trigger
1553
+ * $("#trigger").css("top", 500);
1554
+ * // immediately let the scene know of this change
1555
+ * scene.refresh();
1556
+ *
1557
+ * @fires {@link ScrollScene.shift}, if the trigger element position or the duration changed
1558
+ * @fires {@link ScrollScene.change}, if the duration changed
1559
+ *
1560
+ * @returns {ScrollScene} Parent object for chaining.
1561
+ */
1562
+ this.refresh = function () {
1563
+ updateDuration();
1564
+ updateTriggerElementPosition();
1565
+ // update trigger element position
1566
+ return ScrollScene;
1567
+ };
1568
+
1569
+ /**
1570
+ * **Get** or **Set** the scene's progress.
1571
+ * Usually it shouldn't be necessary to use this as a setter, as it is set automatically by scene.update().
1572
+ * The order in which the events are fired depends on the duration of the scene:
1573
+ * 1. Scenes with `duration == 0`:
1574
+ * Scenes that have no duration by definition have no ending. Thus the `end` event will never be fired.
1575
+ * When the trigger position of the scene is passed the events are always fired in this order:
1576
+ * `enter`, `start`, `progress` when scrolling forward
1577
+ * and
1578
+ * `progress`, `start`, `leave` when scrolling in reverse
1579
+ * 2. Scenes with `duration > 0`:
1580
+ * Scenes with a set duration have a defined start and end point.
1581
+ * When scrolling past the start position of the scene it will fire these events in this order:
1582
+ * `enter`, `start`, `progress`
1583
+ * When continuing to scroll and passing the end point it will fire these events:
1584
+ * `progress`, `end`, `leave`
1585
+ * When reversing through the end point these events are fired:
1586
+ * `enter`, `end`, `progress`
1587
+ * And when continuing to scroll past the start position in reverse it will fire:
1588
+ * `progress`, `start`, `leave`
1589
+ * In between start and end the `progress` event will be called constantly, whenever the progress changes.
1590
+ *
1591
+ * In short:
1592
+ * `enter` events will always trigger **before** the progress update and `leave` envents will trigger **after** the progress update.
1593
+ * `start` and `end` will always trigger at their respective position.
1594
+ *
1595
+ * Please review the event descriptions for details on the events and the event object that is passed to the callback.
1596
+ *
1597
+ * @public
1598
+ * @example
1599
+ * // get the current scene progress
1600
+ * var progress = scene.progress();
1601
+ *
1602
+ * // set new scene progress
1603
+ * scene.progress(0.3);
1604
+ *
1605
+ * @fires {@link ScrollScene.enter}, when used as setter
1606
+ * @fires {@link ScrollScene.start}, when used as setter
1607
+ * @fires {@link ScrollScene.progress}, when used as setter
1608
+ * @fires {@link ScrollScene.end}, when used as setter
1609
+ * @fires {@link ScrollScene.leave}, when used as setter
1610
+ *
1611
+ * @param {number} [progress] - The new progress value of the scene `[0-1]`.
1612
+ * @returns {number} `get` - Current scene progress.
1613
+ * @returns {ScrollScene} `set` - Parent object for chaining.
1614
+ */
1615
+ this.progress = function (progress) {
1616
+ if (!arguments.length) { // get
1617
+ return _progress;
1618
+ } else { // set
1619
+ var
1620
+ doUpdate = false,
1621
+ oldState = _state,
1622
+ scrollDirection = _parent ? _parent.info("scrollDirection") : 'PAUSED',
1623
+ reverseOrForward = _options.reverse || progress >= _progress;
1624
+ if (_options.duration === 0) {
1625
+ // zero duration scenes
1626
+ doUpdate = _progress != progress;
1627
+ _progress = progress < 1 && reverseOrForward ? 0 : 1;
1628
+ _state = _progress === 0 ? 'BEFORE' : 'DURING';
1629
+ } else {
1630
+ // scenes with start and end
1631
+ if (progress <= 0 && _state !== 'BEFORE' && reverseOrForward) {
1632
+ // go back to initial state
1633
+ _progress = 0;
1634
+ _state = 'BEFORE';
1635
+ doUpdate = true;
1636
+ } else if (progress > 0 && progress < 1 && reverseOrForward) {
1637
+ _progress = progress;
1638
+ _state = 'DURING';
1639
+ doUpdate = true;
1640
+ } else if (progress >= 1 && _state !== 'AFTER') {
1641
+ _progress = 1;
1642
+ _state = 'AFTER';
1643
+ doUpdate = true;
1644
+ } else if (_state === 'DURING' && !reverseOrForward) {
1645
+ updatePinState(); // in case we scrolled backwards mid-scene and reverse is disabled => update the pin position, so it doesn't move back as well.
1646
+ }
1647
+ }
1648
+ if (doUpdate) {
1649
+ // fire events
1650
+ var
1651
+ eventVars = {progress: _progress, state: _state, scrollDirection: scrollDirection},
1652
+ stateChanged = _state != oldState;
1653
+
1654
+ var trigger = function (eventName) { // tmp helper to simplify code
1655
+ ScrollScene.trigger(eventName, eventVars);
1656
+ };
1657
+
1658
+ if (stateChanged) { // enter events
1659
+ if (oldState !== 'DURING') {
1660
+ trigger("enter");
1661
+ trigger(oldState === 'BEFORE' ? "start" : "end");
1662
+ }
1663
+ }
1664
+ trigger("progress");
1665
+ if (stateChanged) { // leave events
1666
+ if (_state !== 'DURING') {
1667
+ trigger(_state === 'BEFORE' ? "start" : "end");
1668
+ trigger("leave");
1669
+ }
1670
+ }
1671
+ }
1672
+
1673
+ return ScrollScene;
1674
+ }
1675
+ };
1676
+
1677
+ /**
1678
+ * Add a tween to the scene.
1679
+ * If you want to add multiple tweens, wrap them into one TimelineMax object and add it.
1680
+ * The duration of the tween is streched to the scroll duration of the scene, unless the scene has a duration of `0`.
1681
+ * @public
1682
+ * @example
1683
+ * // add a single tween
1684
+ * scene.setTween(TweenMax.to("obj"), 1, {x: 100});
1685
+ *
1686
+ * // add multiple tweens, wrapped in a timeline.
1687
+ * var timeline = new TimelineMax();
1688
+ * var tween1 = TweenMax.from("obj1", 1, {x: 100});
1689
+ * var tween2 = TweenMax.to("obj2", 1, {y: 100});
1690
+ * timeline
1691
+ * .add(tween1)
1692
+ * .add(tween2);
1693
+ * scene.addTween(timeline);
1694
+ *
1695
+ * @param {object} TweenMaxObject - A TweenMax, TweenLite, TimelineMax or TimelineLite object that should be animated in the scene.
1696
+ * @returns {ScrollScene} Parent object for chaining.
1697
+ */
1698
+ this.setTween = function (TweenMaxObject) {
1699
+ if (_tween) { // kill old tween?
1700
+ ScrollScene.removeTween();
1701
+ }
1702
+ try {
1703
+ // wrap Tween into a TimelineMax Object to include delay and repeats in the duration and standardize methods.
1704
+ _tween = new TimelineMax({smoothChildTiming: true})
1705
+ .add(TweenMaxObject)
1706
+ .pause();
1707
+ } catch (e) {
1708
+ log(1, "ERROR calling method 'setTween()': Supplied argument is not a valid TweenMaxObject");
1709
+ } finally {
1710
+ // some propertties need to be transferred it to the wrapper, otherwise they would get lost.
1711
+ if (TweenMaxObject.repeat) { // TweenMax or TimelineMax Object?
1712
+ if (TweenMaxObject.repeat() === -1) {
1713
+ _tween.repeat(-1);
1714
+ _tween.yoyo(TweenMaxObject.yoyo());
1715
+ }
1716
+ }
1717
+ validateOption("checkIfTriggerElementIsTweened");
1718
+ log(3, "added tween");
1719
+ updateTweenProgress();
1720
+ return ScrollScene;
1721
+ }
1722
+ };
1723
+
1724
+ /**
1725
+ * Remove the tween from the scene.
1726
+ * @public
1727
+ * @example
1728
+ * // remove the tween from the scene without resetting it
1729
+ * scene.removeTween();
1730
+ *
1731
+ * // remove the tween from the scene and reset it to initial position
1732
+ * scene.removeTween(true);
1733
+ *
1734
+ * @param {boolean} [reset=false] - If `true` the tween will be reset to its initial values.
1735
+ * @returns {ScrollScene} Parent object for chaining.
1736
+ */
1737
+ this.removeTween = function (reset) {
1738
+ if (_tween) {
1739
+ if (reset) {
1740
+ updateTweenProgress(0);
1741
+ }
1742
+ _tween.kill();
1743
+ _tween = undefined;
1744
+ log(3, "removed tween (reset: " + (reset ? "true" : "false") + ")");
1745
+ }
1746
+ return ScrollScene;
1747
+ };
1748
+
1749
+ /**
1750
+ * Pin an element for the duration of the tween.
1751
+ * If the scene duration is 0 the element will only be unpinned, if the user scrolls back past the start position.
1752
+ * _**NOTE:** The option `pushFollowers` has no effect, when the scene duration is 0._
1753
+ * @public
1754
+ * @example
1755
+ * // pin element and push all following elements down by the amount of the pin duration.
1756
+ * scene.setPin("#pin");
1757
+ *
1758
+ * // pin element and keeping all following elements in their place. The pinned element will move past them.
1759
+ * scene.setPin("#pin", {pushFollowers: false});
1760
+ *
1761
+ * @param {(string|object)} element - A Selector targeting an element, a DOM object or a jQuery object that is supposed to be pinned.
1762
+ * @param {object} [settings] - settings for the pin
1763
+ * @param {boolean} [settings.pushFollowers=true] - If `true` following elements will be "pushed" down for the duration of the pin, if `false` the pinned element will just scroll past them.
1764
+ Ignored, when duration is `0`.
1765
+ * @param {string} [settings.spacerClass="scrollmagic-pin-spacer"] - Classname of the pin spacer element, which is used to replace the element.
1766
+ * @param {string} [settings.pinnedClass=""] - Classname that should be added to the pinned element during pin phase (and removed after).
1767
+ *
1768
+ * @returns {ScrollScene} Parent object for chaining.
1769
+ */
1770
+ this.setPin = function (element, settings) {
1771
+ var
1772
+ defaultSettings = {
1773
+ pushFollowers: true,
1774
+ spacerClass: "scrollmagic-pin-spacer",
1775
+ pinnedClass: ""
1776
+ };
1777
+ settings = $.extend({}, defaultSettings, settings);
1778
+
1779
+ // validate Element
1780
+ element = $(element).first();
1781
+ if (element.length === 0) {
1782
+ log(1, "ERROR calling method 'setPin()': Invalid pin element supplied.");
1783
+ return ScrollScene; // cancel
1784
+ } else if (element.css("position") == "fixed") {
1785
+ log(1, "ERROR calling method 'setPin()': Pin does not work with elements that are positioned 'fixed'.");
1786
+ return ScrollScene; // cancel
1787
+ }
1788
+
1789
+ if (_pin) { // preexisting pin?
1790
+ if (_pin === element) {
1791
+ // same pin we already have -> do nothing
1792
+ return ScrollScene; // cancel
1793
+ } else {
1794
+ // kill old pin
1795
+ ScrollScene.removePin();
1796
+ }
1797
+
1798
+ }
1799
+ _pin = element;
1800
+
1801
+ _pin.parent().hide(); // hack start to force jQuery css to return stylesheet values instead of calculated px values.
1802
+ var
1803
+ inFlow = _pin.css("position") != "absolute",
1804
+ pinCSS = _pin.css(["display", "top", "left", "bottom", "right"]),
1805
+ sizeCSS = _pin.css(["width", "height"]);
1806
+ _pin.parent().show(); // hack end.
1807
+
1808
+ if (sizeCSS.width === "0px" && inFlow && isMarginCollapseType(pinCSS.display)) {
1809
+ // log (2, "WARNING: Your pinned element probably needs a defined width or it might collapse during pin.");
1810
+ }
1811
+ if (!inFlow && settings.pushFollowers) {
1812
+ log(2, "WARNING: If the pinned element is positioned absolutely pushFollowers is disabled.");
1813
+ settings.pushFollowers = false;
1814
+ }
1815
+
1816
+ // create spacer
1817
+ var spacer = $("<div></div>")
1818
+ .addClass(settings.spacerClass)
1819
+ .css(pinCSS)
1820
+ .data("ScrollMagicPinSpacer", true)
1821
+ .css({
1822
+ position: inFlow ? "relative" : "absolute",
1823
+ "margin-left": "auto",
1824
+ "margin-right": "auto",
1825
+ "box-sizing": "content-box"
1826
+ });
1827
+
1828
+ // set the pin Options
1829
+ var pinInlineCSS = _pin[0].style;
1830
+ _pinOptions = {
1831
+ spacer: spacer,
1832
+ relSize: { // save if size is defined using % values. if so, handle spacer resize differently...
1833
+ width: sizeCSS.width.slice(-1) === "%",
1834
+ height: sizeCSS.height.slice(-1) === "%",
1835
+ autoFullWidth: sizeCSS.width === "0px" && inFlow && isMarginCollapseType(pinCSS.display)
1836
+ },
1837
+ pushFollowers: settings.pushFollowers,
1838
+ inFlow: inFlow, // stores if the element takes up space in the document flow
1839
+ origStyle: {
1840
+ width: pinInlineCSS.width || "",
1841
+ position: pinInlineCSS.position || "",
1842
+ top: pinInlineCSS.top || "",
1843
+ left: pinInlineCSS.left || "",
1844
+ bottom: pinInlineCSS.bottom || "",
1845
+ right: pinInlineCSS.right || "",
1846
+ "box-sizing": pinInlineCSS["box-sizing"] || "",
1847
+ "-moz-box-sizing": pinInlineCSS["-moz-box-sizing"] || "",
1848
+ "-webkit-box-sizing": pinInlineCSS["-webkit-box-sizing"] || ""
1849
+ }, // save old styles (for reset)
1850
+ pinnedClass: settings.pinnedClass // the class that should be added to the element when pinned
1851
+ };
1852
+
1853
+ // if relative size, transfer it to spacer and make pin calculate it...
1854
+ if (_pinOptions.relSize.width) {
1855
+ spacer.css("width", sizeCSS.width);
1856
+ }
1857
+ if (_pinOptions.relSize.height) {
1858
+ spacer.css("height", sizeCSS.height);
1859
+ }
1860
+
1861
+ // now place the pin element inside the spacer
1862
+ _pin.before(spacer)
1863
+ .appendTo(spacer)
1864
+ // and set new css
1865
+ .css({
1866
+ position: inFlow ? "relative" : "absolute",
1867
+ top: "auto",
1868
+ left: "auto",
1869
+ bottom: "auto",
1870
+ right: "auto"
1871
+ });
1872
+
1873
+ if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) {
1874
+ _pin.css("box-sizing", "border-box");
1875
+ }
1876
+
1877
+ // add listener to document to update pin position in case controller is not the document.
1878
+ $(window).on("scroll." + NAMESPACE + "_pin resize." + NAMESPACE + "_pin", updatePinInContainer);
1879
+ // add mousewheel listener to catch scrolls over fixed elements
1880
+ _pin.on("mousewheel DOMMouseScroll", onMousewheelOverPin);
1881
+
1882
+ log(3, "added pin");
1883
+
1884
+ // finally update the pin to init
1885
+ updatePinState();
1886
+
1887
+ return ScrollScene;
1888
+ };
1889
+
1890
+ /**
1891
+ * Remove the pin from the scene.
1892
+ * @public
1893
+ * @example
1894
+ * // remove the pin from the scene without resetting it (the spacer is not removed)
1895
+ * scene.removePin();
1896
+ *
1897
+ * // remove the pin from the scene and reset the pin element to its initial position (spacer is removed)
1898
+ * scene.removePin(true);
1899
+ *
1900
+ * @param {boolean} [reset=false] - If `false` the spacer will not be removed and the element's position will not be reset.
1901
+ * @returns {ScrollScene} Parent object for chaining.
1902
+ */
1903
+ this.removePin = function (reset) {
1904
+ if (_pin) {
1905
+ if (reset || !_parent) { // if there's no parent no progress was made anyway...
1906
+ _pin.insertBefore(_pinOptions.spacer)
1907
+ .css(_pinOptions.origStyle);
1908
+ _pinOptions.spacer.remove();
1909
+ } else {
1910
+ if (_state === "DURING") {
1911
+ updatePinState(true); // force unpin at position
1912
+ }
1913
+ }
1914
+ $(window).off("scroll." + NAMESPACE + "_pin resize." + NAMESPACE + "_pin");
1915
+ _pin.off("mousewheel DOMMouseScroll", onMousewheelOverPin);
1916
+ _pin = undefined;
1917
+ log(3, "removed pin (reset: " + (reset ? "true" : "false") + ")");
1918
+ }
1919
+ return ScrollScene;
1920
+ };
1921
+
1922
+ /**
1923
+ * Define a css class modification while the scene is active.
1924
+ * When the scene triggers the classes will be added to the supplied element and removed, when the scene is over.
1925
+ * If the scene duration is 0 the classes will only be removed if the user scrolls back past the start position.
1926
+ * @public
1927
+ * @example
1928
+ * // add the class 'myclass' to the element with the id 'my-elem' for the duration of the scene
1929
+ * scene.setClassToggle("#my-elem", "myclass");
1930
+ *
1931
+ * // add multiple classes to multiple elements defined by the selector '.classChange'
1932
+ * scene.setClassToggle(".classChange", "class1 class2 class3");
1933
+ *
1934
+ * @param {(string|object)} element - A Selector targeting one or more elements, a DOM object or a jQuery object that is supposed to be modified.
1935
+ * @param {string} classes - One or more Classnames (separated by space) that should be added to the element during the scene.
1936
+ *
1937
+ * @returns {ScrollScene} Parent object for chaining.
1938
+ */
1939
+ this.setClassToggle = function (element, classes) {
1940
+ var $elm = $(element);
1941
+ if ($elm.length === 0 || $.type(classes) !== "string") {
1942
+ log(1, "ERROR calling method 'setClassToggle()': Invalid " + ($elm.length === 0 ? "element" : "classes") + " supplied.");
1943
+ return ScrollScene;
1944
+ }
1945
+ _cssClasses = classes;
1946
+ _cssClassElm = $elm;
1947
+ ScrollScene.on("enter.internal_class leave.internal_class", function (e) {
1948
+ _cssClassElm.toggleClass(_cssClasses, e.type === "enter");
1949
+ });
1950
+ return ScrollScene;
1951
+ };
1952
+
1953
+ /**
1954
+ * Remove the class binding from the scene.
1955
+ * @public
1956
+ * @example
1957
+ * // remove class binding from the scene without reset
1958
+ * scene.removeClassToggle();
1959
+ *
1960
+ * // remove class binding and remove the changes it caused
1961
+ * scene.removeClassToggle(true);
1962
+ *
1963
+ * @param {boolean} [reset=false] - If `false` and the classes are currently active, they will remain on the element. If `true` they will be removed.
1964
+ * @returns {ScrollScene} Parent object for chaining.
1965
+ */
1966
+ this.removeClassToggle = function (reset) {
1967
+ if (_cssClassElm && reset) {
1968
+ _cssClassElm.removeClass(_cssClasses);
1969
+ }
1970
+ ScrollScene.off("start.internal_class end.internal_class");
1971
+ _cssClasses = undefined;
1972
+ _cssClassElm = undefined;
1973
+ return ScrollScene;
1974
+ };
1975
+
1976
+ /**
1977
+ * Add the scene to a controller.
1978
+ * This is the equivalent to `ScrollMagic.addScene(scene)`.
1979
+ * @public
1980
+ * @example
1981
+ * // add a scene to a ScrollMagic controller
1982
+ * scene.addTo(controller);
1983
+ *
1984
+ * @param {ScrollMagic} controller - The controller to which the scene should be added.
1985
+ * @returns {ScrollScene} Parent object for chaining.
1986
+ */
1987
+ this.addTo = function (controller) {
1988
+ if (!(controller instanceof ScrollMagic)) {
1989
+ log(1, "ERROR: supplied argument of 'addTo()' is not a valid ScrollMagic controller");
1990
+ } else if (_parent != controller) {
1991
+ // new parent
1992
+ if (_parent) { // I had a parent before, so remove it...
1993
+ _parent.removeScene(ScrollScene);
1994
+ }
1995
+ _parent = controller;
1996
+ validateOption();
1997
+ updateDuration(true);
1998
+ updateTriggerElementPosition(true);
1999
+ updateScrollOffset();
2000
+ updatePinSpacerSize();
2001
+ _parent.info("container").on("resize." + NAMESPACE, function () {
2002
+ updateRelativePinSpacer();
2003
+ if (ScrollScene.triggerHook() > 0) {
2004
+ ScrollScene.trigger("shift", {reason: "containerSize"});
2005
+ }
2006
+ });
2007
+ log(3, "added " + NAMESPACE + " to controller");
2008
+ controller.addScene(ScrollScene);
2009
+ ScrollScene.update();
2010
+ }
2011
+ return ScrollScene;
2012
+ };
2013
+
2014
+ /**
2015
+ * **Get** or **Set** the current enabled state of the scene.
2016
+ * This can be used to disable this scene without removing or destroying it.
2017
+ * @public
2018
+ *
2019
+ * @example
2020
+ * // get the current value
2021
+ * var enabled = scene.enabled();
2022
+ *
2023
+ * // disable the scene
2024
+ * scene.enabled(false);
2025
+ *
2026
+ * @param {boolean} [newState] - The new enabled state of the scene `true` or `false`.
2027
+ * @returns {(boolean|ScrollScene)} Current enabled state or parent object for chaining.
2028
+ */
2029
+ this.enabled = function (newState) {
2030
+ if (!arguments.length) { // get
2031
+ return _enabled;
2032
+ } else if (_enabled != newState) { // set
2033
+ _enabled = !!newState;
2034
+ ScrollScene.update(true);
2035
+ }
2036
+ return ScrollScene;
2037
+ };
2038
+
2039
+ /**
2040
+ * Remove the scene from its parent controller.
2041
+ * This is the equivalent to `ScrollMagic.removeScene(scene)`.
2042
+ * The scene will not be updated anymore until you readd it to a controller.
2043
+ * To remove the pin or the tween you need to call removeTween() or removePin() respectively.
2044
+ * @public
2045
+ * @example
2046
+ * // remove the scene from its parent controller
2047
+ * scene.remove();
2048
+ *
2049
+ * @returns {ScrollScene} Parent object for chaining.
2050
+ */
2051
+ this.remove = function () {
2052
+ if (_parent) {
2053
+ _parent.info("container").off("resize." + NAMESPACE);
2054
+ var tmpParent = _parent;
2055
+ _parent = undefined;
2056
+ log(3, "removed " + NAMESPACE + " from controller");
2057
+ tmpParent.removeScene(ScrollScene);
2058
+ }
2059
+ return ScrollScene;
2060
+ };
2061
+
2062
+ /**
2063
+ * Destroy the scene and everything.
2064
+ * @public
2065
+ * @example
2066
+ * // destroy the scene without resetting the pin and tween to their initial positions
2067
+ * scene = scene.destroy();
2068
+ *
2069
+ * // destroy the scene and reset the pin and tween
2070
+ * scene = scene.destroy(true);
2071
+ *
2072
+ * @param {boolean} [reset=false] - If `true` the pin and tween (if existent) will be reset.
2073
+ * @returns {null} Null to unset handler variables.
2074
+ */
2075
+ this.destroy = function (reset) {
2076
+ ScrollScene.removeTween(reset);
2077
+ ScrollScene.removePin(reset);
2078
+ ScrollScene.removeClassToggle(reset);
2079
+ ScrollScene.trigger("destroy", {reset: reset});
2080
+ ScrollScene.remove();
2081
+ ScrollScene.off("start end enter leave progress change update shift destroy shift.internal change.internal progress.internal");
2082
+ log(3, "destroyed " + NAMESPACE + " (reset: " + (reset ? "true" : "false") + ")");
2083
+ return null;
2084
+ };
2085
+
2086
+ /*
2087
+ * ----------------------------------------------------------------
2088
+ * EVENTS
2089
+ * ----------------------------------------------------------------
2090
+ */
2091
+
2092
+ /**
2093
+ * Scene start event.
2094
+ * Fires whenever the scroll position its the starting point of the scene.
2095
+ * It will also fire when scrolling back up going over the start position of the scene. If you want something to happen only when scrolling down/right, use the scrollDirection parameter passed to the callback.
2096
+ *
2097
+ * For details on this event and the order in which it is fired, please review the {@link ScrollScene.progress} method.
2098
+ *
2099
+ * @event ScrollScene.start
2100
+ *
2101
+ * @example
2102
+ * scene.on("start", function (event) {
2103
+ * alert("Hit start point of scene.");
2104
+ * });
2105
+ *
2106
+ * @property {object} event - The event Object passed to each callback
2107
+ * @property {string} event.type - The name of the event
2108
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2109
+ * @property {number} event.progress - Reflects the current progress of the scene
2110
+ * @property {string} event.state - The current state of the scene `"BEFORE"`, `"DURING"` or `"AFTER"`
2111
+ * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
2112
+ */
2113
+ /**
2114
+ * Scene end event.
2115
+ * Fires whenever the scroll position its the ending point of the scene.
2116
+ * It will also fire when scrolling back up from after the scene and going over its end position. If you want something to happen only when scrolling down/right, use the scrollDirection parameter passed to the callback.
2117
+ *
2118
+ * For details on this event and the order in which it is fired, please review the {@link ScrollScene.progress} method.
2119
+ *
2120
+ * @event ScrollScene.end
2121
+ *
2122
+ * @example
2123
+ * scene.on("end", function (event) {
2124
+ * alert("Hit end point of scene.");
2125
+ * });
2126
+ *
2127
+ * @property {object} event - The event Object passed to each callback
2128
+ * @property {string} event.type - The name of the event
2129
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2130
+ * @property {number} event.progress - Reflects the current progress of the scene
2131
+ * @property {string} event.state - The current state of the scene `"BEFORE"`, `"DURING"` or `"AFTER"`
2132
+ * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
2133
+ */
2134
+ /**
2135
+ * Scene enter event.
2136
+ * Fires whenever the scene enters the "DURING" state.
2137
+ * Keep in mind that it doesn't matter if the scene plays forward or backward: This event always fires when the scene enters its active scroll timeframe, regardless of the scroll-direction.
2138
+ *
2139
+ * For details on this event and the order in which it is fired, please review the {@link ScrollScene.progress} method.
2140
+ *
2141
+ * @event ScrollScene.enter
2142
+ *
2143
+ * @example
2144
+ * scene.on("enter", function (event) {
2145
+ * alert("Entered a scene.");
2146
+ * });
2147
+ *
2148
+ * @property {object} event - The event Object passed to each callback
2149
+ * @property {string} event.type - The name of the event
2150
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2151
+ * @property {number} event.progress - Reflects the current progress of the scene
2152
+ * @property {string} event.state - The current state of the scene `"BEFORE"`, `"DURING"` or `"AFTER"`
2153
+ * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
2154
+ */
2155
+ /**
2156
+ * Scene leave event.
2157
+ * Fires whenever the scene's state goes from "DURING" to either "BEFORE" or "AFTER".
2158
+ * Keep in mind that it doesn't matter if the scene plays forward or backward: This event always fires when the scene leaves its active scroll timeframe, regardless of the scroll-direction.
2159
+ *
2160
+ * For details on this event and the order in which it is fired, please review the {@link ScrollScene.progress} method.
2161
+ *
2162
+ * @event ScrollScene.leave
2163
+ *
2164
+ * @example
2165
+ * scene.on("leave", function (event) {
2166
+ * alert("Left a scene.");
2167
+ * });
2168
+ *
2169
+ * @property {object} event - The event Object passed to each callback
2170
+ * @property {string} event.type - The name of the event
2171
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2172
+ * @property {number} event.progress - Reflects the current progress of the scene
2173
+ * @property {string} event.state - The current state of the scene `"BEFORE"`, `"DURING"` or `"AFTER"`
2174
+ * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
2175
+ */
2176
+ /**
2177
+ * Scene update event.
2178
+ * Fires whenever the scene is updated (but not necessarily changes the progress).
2179
+ *
2180
+ * @event ScrollScene.update
2181
+ *
2182
+ * @example
2183
+ * scene.on("update", function (event) {
2184
+ * console.log("Scene updated.");
2185
+ * });
2186
+ *
2187
+ * @property {object} event - The event Object passed to each callback
2188
+ * @property {string} event.type - The name of the event
2189
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2190
+ * @property {number} event.startPos - The starting position of the scene (in relation to the conainer)
2191
+ * @property {number} event.endPos - The ending position of the scene (in relation to the conainer)
2192
+ * @property {number} event.scrollPos - The current scroll position of the container
2193
+ */
2194
+ /**
2195
+ * Scene progress event.
2196
+ * Fires whenever the progress of the scene changes.
2197
+ *
2198
+ * For details on this event and the order in which it is fired, please review the {@link ScrollScene.progress} method.
2199
+ *
2200
+ * @event ScrollScene.progress
2201
+ *
2202
+ * @example
2203
+ * scene.on("progress", function (event) {
2204
+ * console.log("Scene progress changed.");
2205
+ * });
2206
+ *
2207
+ * @property {object} event - The event Object passed to each callback
2208
+ * @property {string} event.type - The name of the event
2209
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2210
+ * @property {number} event.progress - Reflects the current progress of the scene
2211
+ * @property {string} event.state - The current state of the scene `"BEFORE"`, `"DURING"` or `"AFTER"`
2212
+ * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"`
2213
+ */
2214
+ /**
2215
+ * Scene change event.
2216
+ * Fires whenvever a property of the scene is changed.
2217
+ *
2218
+ * @event ScrollScene.change
2219
+ *
2220
+ * @example
2221
+ * scene.on("change", function (event) {
2222
+ * console.log("Scene Property \"" + event.what + "\" changed to " + event.newval);
2223
+ * });
2224
+ *
2225
+ * @property {object} event - The event Object passed to each callback
2226
+ * @property {string} event.type - The name of the event
2227
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2228
+ * @property {string} event.what - Indicates what value has been changed
2229
+ * @property {mixed} event.newval - The new value of the changed property
2230
+ */
2231
+ /**
2232
+ * Scene shift event.
2233
+ * Fires whenvever the start or end **scroll offset** of the scene change.
2234
+ * This happens explicitely, when one of these values change: `offset`, `duration` or `triggerHook`.
2235
+ * It will fire implicitly when the `triggerElement` changes, if the new element has a different position (most cases).
2236
+ * It will also fire implicitly when the size of the container changes and the triggerHook is anything other than `onLeave`.
2237
+ *
2238
+ * @event ScrollScene.shift
2239
+ * @since 1.1.0
2240
+ *
2241
+ * @example
2242
+ * scene.on("shift", function (event) {
2243
+ * console.log("Scene moved, because the " + event.reason + " has changed.)");
2244
+ * });
2245
+ *
2246
+ * @property {object} event - The event Object passed to each callback
2247
+ * @property {string} event.type - The name of the event
2248
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2249
+ * @property {string} event.reason - Indicates why the scene has shifted
2250
+ */
2251
+ /**
2252
+ * Scene destroy event.
2253
+ * Fires whenvever the scene is destroyed.
2254
+ * This can be used to tidy up custom behaviour used in events.
2255
+ *
2256
+ * @event ScrollScene.destroy
2257
+ * @since 1.1.0
2258
+ *
2259
+ * @example
2260
+ * scene.on("enter", function (event) {
2261
+ * // add custom action
2262
+ * $("#my-elem").left("200");
2263
+ * })
2264
+ * .on("destroy", function (event) {
2265
+ * // reset my element to start position
2266
+ * if (event.reset) {
2267
+ * $("#my-elem").left("0");
2268
+ * }
2269
+ * });
2270
+ *
2271
+ * @property {object} event - The event Object passed to each callback
2272
+ * @property {string} event.type - The name of the event
2273
+ * @property {ScrollScene} event.target - The ScrollScene object that triggered this event
2274
+ * @property {boolean} event.reset - Indicates if the destroy method was called with reset `true` or `false`.
2275
+ */
2276
+
2277
+ /**
2278
+ * Add one ore more event listener.
2279
+ * The callback function will be fired at the respective event, and an object containing relevant data will be passed to the callback.
2280
+ * @public
2281
+ *
2282
+ * @example
2283
+ * function callback (event) {
2284
+ * console.log("Event fired! (" + event.type + ")");
2285
+ * }
2286
+ * // add listeners
2287
+ * scene.on("change update progress start end enter leave", callback);
2288
+ *
2289
+ * @param {string} name - The name or names of the event the callback should be attached to.
2290
+ * @param {function} callback - A function that should be executed, when the event is dispatched. An event object will be passed to the callback.
2291
+ * @returns {ScrollScene} Parent object for chaining.
2292
+ */
2293
+ this.on = function (name, callback) {
2294
+ if ($.isFunction(callback)) {
2295
+ var names = $.trim(name).toLowerCase()
2296
+ .replace(/(\w+)\.(\w+)/g, '$1.' + NAMESPACE + '_$2') // add custom namespace, if one is defined
2297
+ .replace(/( |^)(\w+)(?= |$)/g, '$1$2.' + NAMESPACE ); // add namespace to regulars.
2298
+ $(ScrollScene).on(names, callback);
2299
+ } else {
2300
+ log(1, "ERROR calling method 'on()': Supplied argument is not a valid callback!");
2301
+ }
2302
+ return ScrollScene;
2303
+ };
2304
+
2305
+ /**
2306
+ * Remove one or more event listener.
2307
+ * @public
2308
+ *
2309
+ * @example
2310
+ * function callback (event) {
2311
+ * console.log("Event fired! (" + event.type + ")");
2312
+ * }
2313
+ * // add listeners
2314
+ * scene.on("change update", callback);
2315
+ * // remove listeners
2316
+ * scene.off("change update", callback);
2317
+ *
2318
+ * @param {string} name - The name or names of the event that should be removed.
2319
+ * @param {function} [callback] - A specific callback function that should be removed. If none is passed all callbacks to the event listener will be removed.
2320
+ * @returns {ScrollScene} Parent object for chaining.
2321
+ */
2322
+ this.off = function (name, callback) {
2323
+ var names = $.trim(name).toLowerCase()
2324
+ .replace(/(\w+)\.(\w+)/g, '$1.' + NAMESPACE + '_$2') // add custom namespace, if one is defined
2325
+ .replace(/( |^)(\w+)(?= |$)/g, '$1$2.' + NAMESPACE + '$3'); // add namespace to regulars.
2326
+ $(ScrollScene).off(names, callback);
2327
+ return ScrollScene;
2328
+ };
2329
+
2330
+ /**
2331
+ * Trigger an event.
2332
+ * @public
2333
+ *
2334
+ * @example
2335
+ * this.trigger("change");
2336
+ *
2337
+ * @param {string} name - The name of the event that should be triggered.
2338
+ * @param {object} [vars] - An object containing info that should be passed to the callback.
2339
+ * @returns {ScrollScene} Parent object for chaining.
2340
+ */
2341
+ this.trigger = function (name, vars) {
2342
+ log(3, 'event fired:', name, "->", vars);
2343
+ var event = $.Event($.trim(name).toLowerCase(), vars);
2344
+ $(ScrollScene).trigger(event);
2345
+ return ScrollScene;
2346
+ };
2347
+
2348
+ // INIT
2349
+ construct();
2350
+ return ScrollScene;
2351
+ };
2352
+
2353
+ // store version
2354
+ ScrollMagic.prototype.version = "1.1.2";
2355
+ // make global references available
2356
+ window.ScrollScene = ScrollScene;
2357
+ window.ScrollMagic = ScrollMagic;
2358
+
2359
+ /*
2360
+ * ----------------------------------------------------------------
2361
+ * global logging functions and making sure no console errors occur
2362
+ * ----------------------------------------------------------------
2363
+ */
2364
+
2365
+ var
2366
+ console = (window.console = window.console || {}),
2367
+ loglevels = [
2368
+ "error",
2369
+ "warn",
2370
+ "log"
2371
+ ];
2372
+ if (!console.log) {
2373
+ console.log = $.noop; // no console log, well - do nothing then...
2374
+ }
2375
+ $.each(loglevels, function (index, method) { // make sure methods for all levels exist.
2376
+ if (!console[method]) {
2377
+ console[method] = console.log; // prefer .log over nothing
2378
+ }
2379
+ });
2380
+ // debugging function
2381
+ var debug = function (loglevel) {
2382
+ if (loglevel > loglevels.length || loglevel <= 0) loglevel = loglevels.length;
2383
+ var now = new Date(),
2384
+ time = ("0"+now.getHours()).slice(-2) + ":" + ("0"+now.getMinutes()).slice(-2) + ":" + ("0"+now.getSeconds()).slice(-2) + ":" + ("00"+now.getMilliseconds()).slice(-3),
2385
+ method = loglevels[loglevel-1],
2386
+ args = Array.prototype.splice.call(arguments, 1),
2387
+ func = Function.prototype.bind.call(console[method], console);
2388
+
2389
+ args.unshift(time);
2390
+ func.apply(console, args);
2391
+ };
2392
+ // a helper function that should generally be faster than jQuery.offset() and can also return position in relation to viewport.
2393
+ var getOffset = function ($elem, relativeToViewport) {
2394
+ var offset = {
2395
+ top: 0,
2396
+ left: 0
2397
+ },
2398
+ elem = $elem[0];
2399
+ if (elem) {
2400
+ if (elem.getBoundingClientRect) { // check if available
2401
+ var rect = elem.getBoundingClientRect();
2402
+ offset.top = rect.top;
2403
+ offset.left = rect.left;
2404
+ if (!relativeToViewport) { // clientRect is by default relative to viewport...
2405
+ offset.top += $(document).scrollTop();
2406
+ offset.left += $(document).scrollLeft();
2407
+ }
2408
+ } else { // fall back to jquery
2409
+ offset = $elem.offset() || offset; // if element has offset undefined (i.e. document) use 0 for top and left
2410
+ if (relativeToViewport) { // jquery.offset is by default NOT relative to viewport...
2411
+ offset.top -= $(document).scrollTop();
2412
+ offset.left -= $(document).scrollLeft();
2413
+ }
2414
+ }
2415
+ }
2416
+ return offset;
2417
+ };
2418
+ var isDomElement = function (o){
2419
+ return (
2420
+ typeof HTMLElement === "object" ? o instanceof HTMLElement : //DOM2
2421
+ o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName==="string"
2422
+ );
2423
+ };
2424
+ var isMarginCollapseType = function (str) {
2425
+ return ["block", "flex", "list-item", "table", "-webkit-box"].indexOf(str) > -1;
2426
+ };
2427
+
2428
+ })(jQuery, window);