1/*! 2 * ScrollMagic v2.0.7 (2019-05-07) 3 * The javascript library for magical scroll interactions. 4 * (c) 2019 Jan Paepke (@janpaepke) 5 * Project Website: http://scrollmagic.io 6 * 7 * @version 2.0.7 8 * @license Dual licensed under MIT license and GPL. 9 * @author Jan Paepke - [email protected] 10 * 11 * @file ScrollMagic main library. 12 */ 13/** 14 * @namespace ScrollMagic 15 */ 16(function (root, factory) { 17 if (typeof define === 'function' && define.amd) { 18 // AMD. Register as an anonymous module. 19 define(factory); 20 } else if (typeof exports === 'object') { 21 // CommonJS 22 module.exports = factory(); 23 } else { 24 // Browser global 25 root.ScrollMagic = factory(); 26 } 27}(this, function () { 28 "use strict"; 29 30 var ScrollMagic = function () { 31 _util.log(2, '(COMPATIBILITY NOTICE) -> As of ScrollMagic 2.0.0 you need to use \'new ScrollMagic.Controller()\' to create a new controller instance. Use \'new ScrollMagic.Scene()\' to instance a scene.'); 32 }; 33 34 ScrollMagic.version = "2.0.7"; 35 36 // TODO: temporary workaround for chrome's scroll jitter bug 37 window.addEventListener("mousewheel", function () {}); 38 39 // global const 40 var PIN_SPACER_ATTRIBUTE = "data-scrollmagic-pin-spacer"; 41 42 /** 43 * The main class that is needed once per scroll container. 44 * 45 * @class 46 * 47 * @example 48 * // basic initialization 49 * var controller = new ScrollMagic.Controller(); 50 * 51 * // passing options 52 * var controller = new ScrollMagic.Controller({container: "#myContainer", loglevel: 3}); 53 * 54 * @param {object} [options] - An object containing one or more options for the controller. 55 * @param {(string|object)} [options.container=window] - A selector, DOM object that references the main container for scrolling. 56 * @param {boolean} [options.vertical=true] - Sets the scroll mode to vertical (`true`) or horizontal (`false`) scrolling. 57 * @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 ScrollMagic.Scene}. 58 * @param {number} [options.loglevel=2] Loglevel for debugging. Note that logging is disabled in the minified version of ScrollMagic. 59 ** `0` => silent 60 ** `1` => errors 61 ** `2` => errors, warnings 62 ** `3` => errors, warnings, debuginfo 63 * @param {boolean} [options.refreshInterval=100] - Some changes don't call events by default, like changing the container size or moving a scene trigger element. 64 This interval polls these parameters to fire the necessary events. 65 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. 66 * 67 */ 68 ScrollMagic.Controller = function (options) { 69 /* 70 * ---------------------------------------------------------------- 71 * settings 72 * ---------------------------------------------------------------- 73 */ 74 var 75 NAMESPACE = 'ScrollMagic.Controller', 76 SCROLL_DIRECTION_FORWARD = 'FORWARD', 77 SCROLL_DIRECTION_REVERSE = 'REVERSE', 78 SCROLL_DIRECTION_PAUSED = 'PAUSED', 79 DEFAULT_OPTIONS = CONTROLLER_OPTIONS.defaults; 80 81 /* 82 * ---------------------------------------------------------------- 83 * private vars 84 * ---------------------------------------------------------------- 85 */ 86 var 87 Controller = this, 88 _options = _util.extend({}, DEFAULT_OPTIONS, options), 89 _sceneObjects = [], 90 _updateScenesOnNextCycle = false, // can be boolean (true => all scenes) or an array of scenes to be updated 91 _scrollPos = 0, 92 _scrollDirection = SCROLL_DIRECTION_PAUSED, 93 _isDocument = true, 94 _viewPortSize = 0, 95 _enabled = true, 96 _updateTimeout, 97 _refreshTimeout; 98 99 /* 100 * ---------------------------------------------------------------- 101 * private functions 102 * ---------------------------------------------------------------- 103 */ 104 105 /**
106 * Internal constructor function of the ScrollMagic Controller 107 * @private 108 */ 109 var construct = function () { 110 for (var key in _options) { 111 if (!DEFAULT_OPTIONS.hasOwnProperty(key)) { 112 log(2, "WARNING: Unknown option \"" + key + "\""); 113 delete _options[key]; 114 } 115 } 116 _options.container = _util.get.elements(_options.container)[0]; 117 // check ScrollContainer 118 if (!_options.container) { 119 log(1, "ERROR creating object " + NAMESPACE + ": No valid scroll container supplied"); 120 throw NAMESPACE + " init failed."; // cancel 121 } 122 _isDocument = _options.container === window || _options.container === document.body || !document.body.contains(_options.container); 123 // normalize to window 124 if (_isDocument) { 125 _options.container = window; 126 } 127 // update container size immediately 128 _viewPortSize = getViewportSize(); 129 // set event handlers 130 _options.container.addEventListener("resize", onChange); 131 _options.container.addEventListener("scroll", onChange); 132 133 var ri = parseInt(_options.refreshInterval, 10); 134 _options.refreshInterval = _util.type.Number(ri) ? ri : DEFAULT_OPTIONS.refreshInterval; 135 scheduleRefresh(); 136 137 log(3, "added new " + NAMESPACE + " controller (v" + ScrollMagic.version + ")"); 138 }; 139 140 /** 141 * Schedule the next execution of the refresh function 142 * @private 143 */ 144 var scheduleRefresh = function () { 145 if (_options.refreshInterval > 0) { 146 _refreshTimeout = window.setTimeout(refresh, _options.refreshInterval); 147 } 148 }; 149 150 /** 151 * Default function to get scroll pos - overwriteable using `Controller.scrollPos(newFunction)` 152 * @private 153 */ 154 var getScrollPos = function () { 155 return _options.vertical ? _util.get.scrollTop(_options.container) : _util.get.scrollLeft(_options.container); 156 }; 157 158 /** 159 * Returns the current viewport Size (width vor horizontal, height for vertical) 160 * @private 161 */ 162 var getViewportSize = function () { 163 return _options.vertical ? _util.get.height(_options.container) : _util.get.width(_options.container); 164 }; 165 166 /** 167 * Default function to set scroll pos - overwriteable using `Controller.scrollTo(newFunction)` 168 * Make available publicly for pinned mousewheel workaround. 169 * @private 170 */ 171 var setScrollPos = this._setScrollPos = function (pos) { 172 if (_options.vertical) { 173 if (_isDocument) { 174 window.scrollTo(_util.get.scrollLeft(), pos); 175 } else { 176 _options.container.scrollTop = pos; 177 } 178 } else { 179 if (_isDocument) { 180 window.scrollTo(pos, _util.get.scrollTop()); 181 } else { 182 _options.container.scrollLeft = pos; 183 } 184 } 185 }; 186 187 /** 188 * Handle updates in cycles instead of on scroll (performance) 189 * @private 190 */ 191 var updateScenes = function () { 192 if (_enabled && _updateScenesOnNextCycle) { 193 // determine scenes to update 194 var scenesToUpdate = _util.type.Array(_updateScenesOnNextCycle) ? _updateScenesOnNextCycle : _sceneObjects.slice(0); 195 // reset scenes 196 _updateScenesOnNextCycle = false; 197 var oldScrollPos = _scrollPos; 198 // update scroll pos now instead of onChange, as it might have changed since scheduling (i.e. in-browser smooth scroll) 199 _scrollPos = Controller.scrollPos(); 200 var deltaScroll = _scrollPos - oldScrollPos; 201 if (deltaScroll !== 0) { // scroll position changed? 202 _scrollDirection = (deltaScroll > 0) ? SCROLL_DIRECTION_FORWARD : SCROLL_DIRECTION_REVERSE; 203 } 204 // reverse order of scenes if scrolling reverse 205 if (_scrollDirection === SCROLL_DIRECTION_REVERSE) { 206 scenesToUpdate.reverse(); 207 } 208 // update scenes 209 scenesToUpdate.forEach(function (scene, index) { 210 log(3, "updating Scene " + (index + 1) + "/" + scenesToUpdate.length + " (" + _sceneObjects.length + " total)"); 211 scene.update(true); 212 }); 213 if (scenesToUpdate.length === 0 && _options.loglevel >= 3) { 214 log(3, "updating 0 Scenes (nothing added to controller)"); 215 } 216 } 217 }; 218 219 /** 220 * Initializes rAF callback 221 * @private 222 */ 223 var debounceUpdate = function () { 224 _updateTimeout = _util.rAF(updateScenes); 225 }; 226 227 /** 228 * Handles Container changes 229 * @private 230 */ 231 var onChange = function (e) { 232 log(3, "event fired causing an update:", e.type); 233 if (e.type == "resize") { 234 // resize 235 _viewPortSize = getViewportSize(); 236 _scrollDirection = SCROLL_DIRECTION_PAUSED; 237 }
238 // schedule update 239 if (_updateScenesOnNextCycle !== true) { 240 _updateScenesOnNextCycle = true; 241 debounceUpdate(); 242 } 243 }; 244 245 var refresh = function () { 246 if (!_isDocument) { 247 // simulate resize event. Only works for viewport relevant param (performance) 248 if (_viewPortSize != getViewportSize()) { 249 var resizeEvent; 250 try { 251 resizeEvent = new Event('resize', { 252 bubbles: false, 253 cancelable: false 254 }); 255 } catch (e) { // stupid IE 256 resizeEvent = document.createEvent("Event"); 257 resizeEvent.initEvent("resize", false, false); 258 } 259 _options.container.dispatchEvent(resizeEvent); 260 } 261 } 262 _sceneObjects.forEach(function (scene, index) { // refresh all scenes 263 scene.refresh(); 264 }); 265 scheduleRefresh(); 266 }; 267 268 /** 269 * Send a debug message to the console. 270 * provided publicly with _log for plugins 271 * @private 272 * 273 * @param {number} loglevel - The loglevel required to initiate output for the message. 274 * @param {...mixed} output - One or more variables that should be passed to the console. 275 */ 276 var log = this._log = function (loglevel, output) { 277 if (_options.loglevel >= loglevel) { 278 Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ") ->"); 279 _util.log.apply(window, arguments); 280 } 281 }; 282 // for scenes we have getters for each option, but for the controller we don't, so we need to make it available externally for plugins 283 this._options = _options; 284 285 /** 286 * Sort scenes in ascending order of their start offset. 287 * @private 288 * 289 * @param {array} ScenesArray - an array of ScrollMagic Scenes that should be sorted 290 * @return {array} The sorted array of Scenes. 291 */ 292 var sortScenes = function (ScenesArray) { 293 if (ScenesArray.length <= 1) { 294 return ScenesArray; 295 } else { 296 var scenes = ScenesArray.slice(0); 297 scenes.sort(function (a, b) { 298 return a.scrollOffset() > b.scrollOffset() ? 1 : -1; 299 }); 300 return scenes; 301 } 302 }; 303 304 /** 305 * ---------------------------------------------------------------- 306 * public functions 307 * ---------------------------------------------------------------- 308 */ 309 310 /** 311 * Add one ore more scene(s) to the controller. 312 * This is the equivalent to `Scene.addTo(controller)`. 313 * @public 314 * @example 315 * // with a previously defined scene 316 * controller.addScene(scene); 317 * 318 * // with a newly created scene. 319 * controller.addScene(new ScrollMagic.Scene({duration : 0})); 320 * 321 * // adding multiple scenes 322 * controller.addScene([scene, scene2, new ScrollMagic.Scene({duration : 0})]); 323 * 324 * @param {(ScrollMagic.Scene|array)} newScene - ScrollMagic Scene or Array of Scenes to be added to the controller. 325 * @return {Controller} Parent object for chaining. 326 */ 327 this.addScene = function (newScene) { 328 if (_util.type.Array(newScene)) { 329 newScene.forEach(function (scene, index) { 330 Controller.addScene(scene); 331 }); 332 } else if (newScene instanceof ScrollMagic.Scene) { 333 if (newScene.controller() !== Controller) { 334 newScene.addTo(Controller); 335 } else if (_sceneObjects.indexOf(newScene) < 0) { 336 // new scene 337 _sceneObjects.push(newScene); // add to array 338 _sceneObjects = sortScenes(_sceneObjects); // sort 339 newScene.on("shift.controller_sort", function () { // resort whenever scene moves 340 _sceneObjects = sortScenes(_sceneObjects); 341 }); 342 // insert Global defaults. 343 for (var key in _options.globalSceneOptions) { 344 if (newScene[key]) { 345 newScene[key].call(newScene, _options.globalSceneOptions[key]); 346 } 347 } 348 log(3, "adding Scene (now " + _sceneObjects.length + " total)"); 349 } 350 } else { 351 log(1, "ERROR: invalid argument supplied for '.addScene()'"); 352 } 353 return Controller; 354 }; 355 356 /** 357 * Remove one ore more scene(s) from the controller. 358 * This is the equivalent to `Scene.remove()`. 359 * @public 360 * @example 361 * // remove a scene from the controller 362 * controller.removeScene(scene); 363 * 364 * // remove multiple scenes from the controller 365 * controller.removeScene([scene, scene2, scene3]); 366 * 367 * @param {(ScrollMagic.Scene|array)}
367 Scene - ScrollMagic Scene or Array of Scenes to be removed from the controller. 368 * @returns {Controller} Parent object for chaining. 369 */ 370 this.removeScene = function (Scene) { 371 if (_util.type.Array(Scene)) { 372 Scene.forEach(function (scene, index) { 373 Controller.removeScene(scene); 374 }); 375 } else { 376 var index = _sceneObjects.indexOf(Scene); 377 if (index > -1) { 378 Scene.off("shift.controller_sort"); 379 _sceneObjects.splice(index, 1); 380 log(3, "removing Scene (now " + _sceneObjects.length + " left)"); 381 Scene.remove(); 382 } 383 } 384 return Controller; 385 }; 386 387 /** 388 * Update one ore more scene(s) according to the scroll position of the container. 389 * This is the equivalent to `Scene.update()`. 390 * 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. 391 * 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. 392 * _**Note:** This method gets called constantly whenever Controller 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._ 393 * @public 394 * @example 395 * // update a specific scene on next cycle 396 * controller.updateScene(scene); 397 * 398 * // update a specific scene immediately 399 * controller.updateScene(scene, true); 400 * 401 * // update multiple scenes scene on next cycle 402 * controller.updateScene([scene1, scene2, scene3]); 403 * 404 * @param {ScrollMagic.Scene} Scene - ScrollMagic Scene or Array of Scenes that is/are supposed to be updated. 405 * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next update cycle. 406 This is useful when changing multiple properties of the scene - this way it will only be updated once all new properties are set (updateScenes). 407 * @return {Controller} Parent object for chaining. 408 */ 409 this.updateScene = function (Scene, immediately) { 410 if (_util.type.Array(Scene)) { 411 Scene.forEach(function (scene, index) { 412 Controller.updateScene(scene, immediately); 413 }); 414 } else { 415 if (immediately) { 416 Scene.update(true); 417 } else if (_updateScenesOnNextCycle !== true && Scene instanceof ScrollMagic.Scene) { // if _updateScenesOnNextCycle is true, all connected scenes are already scheduled for update 418 // prep array for next update cycle 419 _updateScenesOnNextCycle = _updateScenesOnNextCycle || []; 420 if (_updateScenesOnNextCycle.indexOf(Scene) == -1) { 421 _updateScenesOnNextCycle.push(Scene); 422 } 423 _updateScenesOnNextCycle = sortScenes(_updateScenesOnNextCycle); // sort 424 debounceUpdate(); 425 } 426 } 427 return Controller; 428 }; 429 430 /** 431 * Updates the controller params and calls updateScene on every scene, that is attached to the controller. 432 * See `Controller.updateScene()` for more information about what this means. 433 * 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. 434 * The only application for this method is when ScrollMagic fails to detect these events. 435 * 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. 436 * For this case there will also be the need to provide a custom function to calculate the correct scroll position. See `Controller.scrollPos()` for details. 437 * @public 438 * @example 439 * // update the controller on next cycle (saves performance due to elimination of redundant updates) 440 * controller.update(); 441 * 442 * // update the controller immediately 443 * controller.update(true); 444 * 445 * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next update cycle (better performance) 446 * @return {Controller} Parent object for chaining. 447 */ 448 this.update = function (immediately) { 449 onChange({ 450 type: "resize" 451 }); // will update size and set _updateScenesOnNextCycle to true 452 if (immediately) { 453 updateScenes(); 454 } 455 return Controller; 456 }; 457 458 /** 459 * Scroll to a numeric scroll offset, a DOM element, the start of a scene or provide an alternate method for scrolling. 460 * For vertical controllers it will change the top scroll offset and for horizontal applications it will change the left offset. 461 * @public 462 * 463 * @since 1.1.0 464 * @example 465 * // scroll to an offset of 100 466 * controller.scrollTo(100); 467 * 468 * // scroll to a DOM element 469 * controller.scrollTo("#anchor"); 470 * 471 * // scroll to the beginning of a scene 472 * var scene = new ScrollMagic.Scene({offset: 200}); 473 * controller.scrollTo(scene); 474 * 475 * // define a new scroll position modification function (jQuery animate instead of jump) 476 * controller.scrollTo(function (newScrollPos) { 477 * $("html, body").animate({scrollTop: newScrollPos}); 478 * }); 479 * controller.scrollTo(100); // call as usual, but the new function will be used instead 480 * 481 * // define a new scroll function with an additional parameter 482 * controller.scrollTo(function (newScrollPos, message) { 483 * console.log(message); 484 * $(this).animate({scrollTop: newScrollPos}); 485 * }); 486 * // call as usual, but supply an extra parameter to the defined custom function 487 * controller.scrollTo(100, "my message"); 488 * 489 * // define a new scroll function with an additional parameter containing multiple variables 490 * controller.scrollTo(function (newScrollPos, options) { 491 * someGlobalVar = options.a + options.b; 492 * $(this).animate({scrollTop: newScrollPos}); 493 * }); 494 * // call as usual, but supply an extra parameter containing multiple options 495 * controller.scrollTo(100, {a: 1, b: 2}); 496 * 497 * // define a new scroll function with a callback supplied as an additional parameter 498 * controller.scrollTo(function (newScrollPos, callback) { 499 * $(this).animate({scrollTop: newScrollPos}, 400, "swing", callback); 500 * }); 501 * // call as usual, but supply an extra parameter, which is used as a callback in the previously defined custom scroll function 502 * controller.scrollTo(100, function() { 503 * console.log("scroll has finished."); 504 * }); 505 * 506 * @param {mixed} scrollTarget - The supplied argument can be one of these types: 507 * 1. `number` -> The container will scroll to this new scroll offset. 508 * 2. `string` or `object` -> Can be a selector or a DOM object. 509 * The container will scroll to the position of this element. 510 * 3. `ScrollMagic Scene` -> The container will scroll to the start of this scene. 511 * 4. `function` -> This function will be used for future scroll position modifications. 512 * This provides a way for you to change the behaviour of scrolling and adding new behaviour like animation. The function receives the new scroll position as a parameter and a reference to the container element using `this`.
513 * It may also optionally receive an optional additional parameter (see below) 514 * _**NOTE:** 515 * All other options will still work as expected, using the new function to scroll._ 516 * @param {mixed} [additionalParameter] - If a custom scroll function was defined (see above 4.), you may want to supply additional parameters to it, when calling it. You can do this using this parameter â see examples for details. Please note, that this parameter will have no effect, if you use the default scrolling function. 517 * @returns {Controller} Parent object for chaining. 518 */ 519 this.scrollTo = function (scrollTarget, additionalParameter) { 520 if (_util.type.Number(scrollTarget)) { // excecute 521 setScrollPos.call(_options.container, scrollTarget, additionalParameter); 522 } else if (scrollTarget instanceof ScrollMagic.Scene) { // scroll to scene 523 if (scrollTarget.controller() === Controller) { // check if the controller is associated with this scene 524 Controller.scrollTo(scrollTarget.scrollOffset(), additionalParameter); 525 } else { 526 log(2, "scrollTo(): The supplied scene does not belong to this controller. Scroll cancelled.", scrollTarget); 527 } 528 } else if (_util.type.Function(scrollTarget)) { // assign new scroll function 529 setScrollPos = scrollTarget; 530 } else { // scroll to element 531 var elem = _util.get.elements(scrollTarget)[0]; 532 if (elem) { 533 // if parent is pin spacer, use spacer position instead so correct start position is returned for pinned elements. 534 while (elem.parentNode.hasAttribute(PIN_SPACER_ATTRIBUTE)) { 535 elem = elem.parentNode; 536 } 537 538 var 539 param = _options.vertical ? "top" : "left", // which param is of interest ? 540 containerOffset = _util.get.offset(_options.container), // container position is needed because element offset is returned in relation to document, not in relation to container. 541 elementOffset = _util.get.offset(elem); 542 543 if (!_isDocument) { // container is not the document root, so substract scroll Position to get correct trigger element position relative to scrollcontent 544 containerOffset[param] -= Controller.scrollPos(); 545 } 546 547 Controller.scrollTo(elementOffset[param] - containerOffset[param], additionalParameter); 548 } else { 549 log(2, "scrollTo(): The supplied argument is invalid. Scroll cancelled.", scrollTarget); 550 } 551 } 552 return Controller; 553 }; 554 555 /** 556 * **Get** the current scrollPosition or **Set** a new method to calculate it. 557 * -> **GET**: 558 * When used as a getter this function will return the current scroll position. 559 * To get a cached value use Controller.info("scrollPos"), which will be updated in the update cycle. 560 * For vertical controllers it will return the top scroll offset and for horizontal applications it will return the left offset. 561 * 562 * -> **SET**: 563 * When used as a setter this method prodes a way to permanently overwrite the controller's scroll position calculation. 564 * 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. 565 * Moving a child container inside a parent is a commonly used method for several scrolling frameworks, including iScroll. 566 * By providing an alternate calculation function you can make sure ScrollMagic receives the correct scroll position. 567 * Please also bear in mind that your function should return y values for vertical scrolls an x for horizontals. 568 * 569 * To change the current scroll position please use `Controller.scrollTo()`. 570 * @public 571 * 572 * @example 573 * // get the current scroll Position 574 * var scrollPos = controller.scrollPos(); 575 * 576 * // set a new scroll position calculation method 577 * controller.scrollPos(function () { 578 * return this.info("vertical") ? -mychildcontainer.y : -mychildcontainer.x 579 * }); 580 * 581 * @param {function} [scrollPosMethod] - The function to be used for the scroll position calculation of the container. 582 * @returns {(number|Controller)} Current scroll position or parent object for chaining. 583 */ 584 this.scrollPos = function (scrollPosMethod) { 585 if (!arguments.length) { // get 586 return getScrollPos.call(Controller); 587 } else { // set 588 if (_util.type.Function(scrollPosMethod)) { 589 getScrollPos = scrollPosMethod; 590 } else { 591 log(2, "Provided value for method 'scrollPos' is not a function. To change the current scroll position use 'scrollTo()'."); 592 } 593 } 594 return Controller; 595 }; 596 597 /** 598 * **Get** all infos or one in particular about the controller. 599 * @public 600 * @example 601 * // returns the current scroll position (number) 602 * var scrollPos = controller.info("scrollPos"); 603 * 604 * // returns all infos as an object 605 * var infos = controller.info(); 606 * 607 * @param {string} [about] - If passed only this info will be returned instead of an object c
607ontaining all. 608 Valid options are: 609 ** `"size"` => the current viewport size of the container 610 ** `"vertical"` => true if vertical scrolling, otherwise false 611 ** `"scrollPos"` => the current scroll position 612 ** `"scrollDirection"` => the last known direction of the scroll 613 ** `"container"` => the container element 614 ** `"isDocument"` => true if container element is the document. 615 * @returns {(mixed|object)} The requested info(s). 616 */ 617 this.info = function (about) { 618 var values = { 619 size: _viewPortSize, // contains height or width (in regard to orientation); 620 vertical: _options.vertical, 621 scrollPos: _scrollPos, 622 scrollDirection: _scrollDirection, 623 container: _options.container, 624 isDocument: _isDocument 625 }; 626 if (!arguments.length) { // get all as an object 627 return values; 628 } else if (values[about] !== undefined) { 629 return values[about]; 630 } else { 631 log(1, "ERROR: option \"" + about + "\" is not available"); 632 return; 633 } 634 }; 635 636 /** 637 * **Get** or **Set** the current loglevel option value. 638 * @public 639 * 640 * @example 641 * // get the current value 642 * var loglevel = controller.loglevel(); 643 * 644 * // set a new value 645 * controller.loglevel(3); 646 * 647 * @param {number} [newLoglevel] - The new loglevel setting of the Controller. `[0-3]` 648 * @returns {(number|Controller)} Current loglevel or parent object for chaining. 649 */ 650 this.loglevel = function (newLoglevel) { 651 if (!arguments.length) { // get 652 return _options.loglevel; 653 } else if (_options.loglevel != newLoglevel) { // set 654 _options.loglevel = newLoglevel; 655 } 656 return Controller; 657 }; 658 659 /** 660 * **Get** or **Set** the current enabled state of the controller. 661 * This can be used to disable all Scenes connected to the controller without destroying or removing them. 662 * @public 663 * 664 * @example 665 * // get the current value 666 * var enabled = controller.enabled(); 667 * 668 * // disable the controller 669 * controller.enabled(false); 670 * 671 * @param {boolean} [newState] - The new enabled state of the controller `true` or `false`. 672 * @returns {(boolean|Controller)} Current enabled state or parent object for chaining. 673 */ 674 this.enabled = function (newState) { 675 if (!arguments.length) { // get 676 return _enabled; 677 } else if (_enabled != newState) { // set 678 _enabled = !!newState; 679 Controller.updateScene(_sceneObjects, true); 680 } 681 return Controller; 682 }; 683 684 /** 685 * Destroy the Controller, all Scenes and everything. 686 * @public 687 * 688 * @example 689 * // without resetting the scenes 690 * controller = controller.destroy(); 691 * 692 * // with scene reset 693 * controller = controller.destroy(true); 694 * 695 * @param {boolean} [resetScenes=false] - If `true` the pins and tweens (if existent) of all scenes will be reset. 696 * @returns {null} Null to unset handler variables. 697 */ 698 this.destroy = function (resetScenes) { 699 window.clearTimeout(_refreshTimeout); 700 var i = _sceneObjects.length; 701 while (i--) { 702 _sceneObjects[i].destroy(resetScenes); 703 } 704 _options.container.removeEventListener("resize", onChange); 705 _options.container.removeEventListener("scroll", onChange); 706 _util.cAF(_updateTimeout); 707 log(3, "destroyed " + NAMESPACE + " (reset: " + (resetScenes ? "true" : "false") + ")"); 708 return null; 709 }; 710 711 // INIT 712 construct(); 713 return Controller; 714 }; 715 716 // store pagewide controller options 717 var CONTROLLER_OPTIONS = { 718 defaults: { 719 container: window, 720 vertical: true, 721 globalSceneOptions: {}, 722 loglevel: 2, 723 refreshInterval: 100 724 } 725 }; 726 /* 727 * method used to add an option to ScrollMagic Scenes. 728 */ 729 ScrollMagic.Controller.addOption = function (name, defaultValue) { 730 CONTROLLER_OPTIONS.defaults[name] = defaultValue; 731 }; 732 // instance extension function for plugins 733 ScrollMagic.Controller.extend = function (extension) { 734 var oldClass = this; 735 ScrollMagic.Controller = function () { 736 oldClass.apply(this, arguments); 737 this.$super = _util.extend({}, this); // copy parent state 738 return extension.apply(this, arguments) || this; 739 }; 740 _util.extend(ScrollMagic.Controller, oldClass); // copy properties 741 ScrollMagic.Controller.prototype = oldClass.prototype; // copy prototype 742 ScrollMagic.Controller.prototype.constructor = ScrollMagic.Controller; // restore constructor 743 }; 744 745 746 /** 747 * A Scene defines where the controller should react and how. 748 * 749 * @class 750 * 751 * @example 752 * // create a standard scene and add it to a controller 753 * new ScrollMagic.Scene() 754 * .addTo(controller); 755 * 756 * // create a scene with custom options and assign a handler to it. 757 * var scene = new ScrollMagic.Scene({ 758 * duration: 100, 759 * offset: 200, 760 * triggerHook: "onEnter", 761 * reverse: false 762 * }); 763 * 764 * @param {object} [options] - Options for the Scene. The options can be updated at any time. 765 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. 766 When a scene is added to the controller the options defined using the Scene constructor will be overwritten by those set in `globalSceneOptions`. 767 * @param {(number|string|function)} [options.duration=0] - The duration of the scene. 768 Please see `Scene.duration()` for details. 769 * @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. 770 * @param {(string|object)} [options.triggerElement=null] - Selector or DOM 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). 771 * @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. 772 Can also be defined using a string: 773 ** `"onEnter"` => `1` 774 ** `"onCenter"` => `0.5` 775 ** `"onLeave"` => `0` 776 * @param {boolean} [options.reverse=true] - Should the scene reverse, when scrolling up? 777 * @param {number} [options.loglevel=2] - Loglevel for debugging. Note that logging is disabled in the minified version of ScrollMagic. 778 ** `0` => silent 779 ** `1` => errors 780 ** `2` => errors, warnings 781 ** `3` => errors, warnings, debuginfo 782 * 783 */ 784 ScrollMagic.Scene = function (options) { 785 786 /* 787 * ---------------------------------------------------------------- 788 * settings 789 * ---------------------------------------------------------------- 790 */ 791 792 var 793 NAMESPACE = 'ScrollMagic.Scene', 794 SCENE_STATE_BEFORE = 'BEFORE', 795 SCENE_STATE_DURING = 'DURING', 796 SCENE_STATE_AFTER = 'AFTER', 797 DEFAULT_OPTIONS = SCENE_OPTIONS.defaults; 798 799 /* 800 * ---------------------------------------------------------------- 801 * private vars 802 * ---------------------------------------------------------------- 803 */ 804 805 var 806 Scene = this, 807 _options = _util.extend({}, DEFAULT_OPTIONS, options), 808 _state = SCENE_STATE_BEFORE, 809 _progress = 0, 810 _scrollOffset = { 811 start: 0, 812 end: 0 813 }, // reflects the controllers's scroll position for the start and end of the scene respectively 814 _triggerPos = 0, 815 _enabled = true, 816 _durationUpdateMethod, 817 _controller; 818 819 /**
820 * Internal constructor function of the ScrollMagic Scene 821 * @private 822 */ 823 var construct = function () { 824 for (var key in _options) { // check supplied options 825 if (!DEFAULT_OPTIONS.hasOwnProperty(key)) { 826 log(2, "WARNING: Unknown option \"" + key + "\""); 827 delete _options[key]; 828 } 829 } 830 // add getters/setters for all possible options 831 for (var optionName in DEFAULT_OPTIONS) { 832 addSceneOption(optionName); 833 } 834 // validate all options 835 validateOption(); 836 }; 837 838 /* 839 * ---------------------------------------------------------------- 840 * Event Management 841 * ---------------------------------------------------------------- 842 */ 843 844 var _listeners = {}; 845 /** 846 * Scene start event. 847 * Fires whenever the scroll position its the starting point of the scene. 848 * 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. 849 * 850 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method. 851 * 852 * @event ScrollMagic.Scene#start 853 * 854 * @example 855 * scene.on("start", function (event) { 856 * console.log("Hit start point of scene."); 857 * }); 858 * 859 * @property {object} event - The event Object passed to each callback 860 * @property {string} event.type - The name of the event 861 * @property {Scene} event.target - The Scene object that triggered this event 862 * @property {number} event.progress - Reflects the current progress of the scene 863 * @property {string} event.state - The current state of the scene `"BEFORE"` or `"DURING"` 864 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"` 865 */ 866 /** 867 * Scene end event. 868 * Fires whenever the scroll position its the ending point of the scene. 869 * 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. 870 * 871 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method. 872 * 873 * @event ScrollMagic.Scene#end 874 * 875 * @example 876 * scene.on("end", function (event) { 877 * console.log("Hit end point of scene."); 878 * }); 879 * 880 * @property {object} event - The event Object passed to each callback 881 * @property {string} event.type - The name of the event 882 * @property {Scene} event.target - The Scene object that triggered this event 883 * @property {number} event.progress - Reflects the current progress of the scene 884 * @property {string} event.state - The current state of the scene `"DURING"` or `"AFTER"` 885 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"` 886 */ 887 /** 888 * Scene enter event. 889 * Fires whenever the scene enters the "DURING" state. 890 * 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. 891 * 892 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method. 893 * 894 * @event ScrollMagic.Scene#enter 895 * 896 * @example 897 * scene.on("enter", function (event) { 898 * console.log("Scene entered."); 899 * }); 900 * 901 * @property {object} event - The event Object passed to each callback 902 * @property {string} event.type - The name of the event 903 * @property {Scene} event.target - The Scene object that triggered this event 904 * @property {number} event.progress - Reflects the current progress of the scene 905 * @property {string} event.state - The current state of the scene - always `"DURING"` 906 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"` 907 */ 908 /** 909 * Scene leave event. 910 * Fires whenever the scene's state goes from "DURING" to either "BEFORE" or "AFTER". 911 * Keep in mind that it doesn't matter if the scene plays forward or backward: This event al
911ways fires when the scene leaves its active scroll timeframe, regardless of the scroll-direction. 912 * 913 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method. 914 * 915 * @event ScrollMagic.Scene#leave 916 * 917 * @example 918 * scene.on("leave", function (event) { 919 * console.log("Scene left."); 920 * }); 921 * 922 * @property {object} event - The event Object passed to each callback 923 * @property {string} event.type - The name of the event 924 * @property {Scene} event.target - The Scene object that triggered this event 925 * @property {number} event.progress - Reflects the current progress of the scene 926 * @property {string} event.state - The current state of the scene `"BEFORE"` or `"AFTER"` 927 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"` 928 */ 929 /** 930 * Scene update event. 931 * Fires whenever the scene is updated (but not necessarily changes the progress). 932 * 933 * @event ScrollMagic.Scene#update 934 * 935 * @example 936 * scene.on("update", function (event) { 937 * console.log("Scene updated."); 938 * }); 939 * 940 * @property {object} event - The event Object passed to each callback 941 * @property {string} event.type - The name of the event 942 * @property {Scene} event.target - The Scene object that triggered this event 943 * @property {number} event.startPos - The starting position of the scene (in relation to the conainer) 944 * @property {number} event.endPos - The ending position of the scene (in relation to the conainer) 945 * @property {number} event.scrollPos - The current scroll position of the container 946 */ 947 /** 948 * Scene progress event. 949 * Fires whenever the progress of the scene changes. 950 * 951 * For details on this event and the order in which it is fired, please review the {@link Scene.progress} method. 952 * 953 * @event ScrollMagic.Scene#progress 954 * 955 * @example 956 * scene.on("progress", function (event) { 957 * console.log("Scene progress changed to " + event.progress); 958 * }); 959 * 960 * @property {object} event - The event Object passed to each callback 961 * @property {string} event.type - The name of the event 962 * @property {Scene} event.target - The Scene object that triggered this event 963 * @property {number} event.progress - Reflects the current progress of the scene 964 * @property {string} event.state - The current state of the scene `"BEFORE"`, `"DURING"` or `"AFTER"` 965 * @property {string} event.scrollDirection - Indicates which way we are scrolling `"PAUSED"`, `"FORWARD"` or `"REVERSE"` 966 */ 967 /** 968 * Scene change event. 969 * Fires whenvever a property of the scene is changed. 970 * 971 * @event ScrollMagic.Scene#change 972 * 973 * @example 974 * scene.on("change", function (event) { 975 * console.log("Scene Property \"" + event.what + "\" changed to " + event.newval); 976 * }); 977 * 978 * @property {object} event - The event Object passed to each callback 979 * @property {string} event.type - The name of the event 980 * @property {Scene} event.target - The Scene object that triggered this event 981 * @property {string} event.what - Indicates what value has been changed 982 * @property {mixed} event.newval - The new value of the changed property 983 */ 984 /** 985 * Scene shift event. 986 * Fires whenvever the start or end **scroll offset** of the scene change. 987 * This happens explicitely, when one of these values change: `offset`, `duration` or `triggerHook`. 988 * It will fire implicitly when the `triggerElement` changes, if the new element has a different position (most cases). 989 * It will also fire implicitly when the size of the container changes and the triggerHook is anything other than `onLeave`. 990 * 991 * @event ScrollMagic.Scene#shift 992 * @since 1.1.0 993 * 994 * @example 995 * scene.on("shift", function (event) { 996 * console.log("Scene moved, because the " + event.reason + " has changed.)"); 997 * }); 998 * 999 * @property {object} event - The event Object passed to each callback 1000 * @property {string} event.type - The name of the event 1001 * @property {Scene} event.target - The Scene object that triggered this event 1002 * @property {string} event.reason - Indicates why the scene has shifted 1003 */ 1004 /** 1005 * Scene destroy event. 1006 * Fires whenvever the scene is destroyed. 1007 * This can be used to tidy up custom behaviour used in events. 1008 * 1009 * @event ScrollMagic.Scene#destroy 1010 * @since 1.1.0 1011 * 1012 * @example 1013 * scene.on("enter", function (event) { 1014 * // add custom action 1015 * $("#my-elem").left("200"); 1016 * }) 1017 * .on("destroy", function (event) { 1018 * // reset my element to start position 1019 * if (event.reset) { 1020 * $("#my-elem").left("0"); 1021 * } 1022 * }); 1023 * 1024 * @property {object} event - The event Object passed to each callback 1025 * @property {string} event.type - The name of the event 1026 * @property {Scene} event.target - The Scene object that triggered this event 1027 * @property {boolean} event.reset - Indicates if the destroy method was called with reset `true` or `false`. 1028 */ 1029 /** 1030 * Scene add event. 1031 * Fires when the scene is added to a controller. 1032 * This is mostly used by plugins to know that change might be due. 1033 * 1034 * @event ScrollMagic.Scene#add 1035 * @since 2.0.0 1036 * 1037 * @example 1038 * scene.on("add", function (event) { 1039 * console.log('Scene was added to a new controller.'); 1040 * }); 1041 * 1042 * @property {object} event - The event Object passed to each callback 1043 * @property {string} event.type - The name of the event 1044 * @property {Scene} event.target - The Scene object that triggered this event 1045 * @property {boolean} event.controller - The controller object the scene was added to. 1046 */ 1047 /** 1048 * Scene remove event. 1049 * Fires when the scene is removed from a controller. 1050 * This is mostly used by plugins to know that change might be due. 1051 * 1052 * @event ScrollMagic.Scene#remove 1053 * @since 2.0.0 1054 * 1055 * @example 1056 * scene.on("remove", function (event) { 1057 * console.log('Scene was removed from its controller.'); 1058 * }); 1059 * 1060 * @property {object} event - The event Object passed to each callback 1061 * @property {string} event.type - The name of the event 1062 * @property {Scene} event.target - The Scene object that triggered this event 1063 */ 1064 1065 /** 1066 * Add one ore more event listener. 1067 * The callback function will be fired at the respective event, and an object c
1067ontaining relevant data will be passed to the callback. 1068 * @method ScrollMagic.Scene#on 1069 * 1070 * @example 1071 * function callback (event) { 1072 * console.log("Event fired! (" + event.type + ")"); 1073 * } 1074 * // add listeners 1075 * scene.on("change update progress start end enter leave", callback); 1076 * 1077 * @param {string} names - The name or names of the event the callback should be attached to. 1078 * @param {function} callback - A function that should be executed, when the event is dispatched. An event object will be passed to the callback. 1079 * @returns {Scene} Parent object for chaining. 1080 */ 1081 this.on = function (names, callback) { 1082 if (_util.type.Function(callback)) { 1083 names = names.trim().split(' '); 1084 names.forEach(function (fullname) { 1085 var 1086 nameparts = fullname.split('.'), 1087 eventname = nameparts[0], 1088 namespace = nameparts[1]; 1089 if (eventname != "*") { // disallow wildcards 1090 if (!_listeners[eventname]) { 1091 _listeners[eventname] = []; 1092 } 1093 _listeners[eventname].push({ 1094 namespace: namespace || '', 1095 callback: callback 1096 }); 1097 } 1098 }); 1099 } else { 1100 log(1, "ERROR when calling '.on()': Supplied callback for '" + names + "' is not a valid function!"); 1101 } 1102 return Scene; 1103 }; 1104 1105 /** 1106 * Remove one or more event listener. 1107 * @method ScrollMagic.Scene#off 1108 * 1109 * @example 1110 * function callback (event) { 1111 * console.log("Event fired! (" + event.type + ")"); 1112 * } 1113 * // add listeners 1114 * scene.on("change update", callback); 1115 * // remove listeners 1116 * scene.off("change update", callback); 1117 * 1118 * @param {string} names - The name or names of the event that should be removed. 1119 * @param {function} [callback] - A specific callback function that should be removed. If none is passed all callbacks to the event listener will be removed. 1120 * @returns {Scene} Parent object for chaining. 1121 */ 1122 this.off = function (names, callback) { 1123 if (!names) { 1124 log(1, "ERROR: Invalid event name supplied."); 1125 return Scene; 1126 } 1127 names = names.trim().split(' '); 1128 names.forEach(function (fullname, key) { 1129 var 1130 nameparts = fullname.split('.'), 1131 eventname = nameparts[0], 1132 namespace = nameparts[1] || '', 1133 removeList = eventname === '*' ? Object.keys(_listeners) : [eventname]; 1134 removeList.forEach(function (remove) { 1135 var 1136 list = _listeners[remove] || [], 1137 i = list.length; 1138 while (i--) { 1139 var listener = list[i]; 1140 if (listener && (namespace === listener.namespace || namespace === '*') && (!callback || callback == listener.callback)) { 1141 list.splice(i, 1); 1142 } 1143 } 1144 if (!list.length) { 1145 delete _listeners[remove]; 1146 } 1147 }); 1148 }); 1149 return Scene; 1150 }; 1151 1152 /** 1153 * Trigger an event. 1154 * @method ScrollMagic.Scene#trigger 1155 * 1156 * @example 1157 * this.trigger("change"); 1158 * 1159 * @param {string} name - The name of the event that should be triggered. 1160 * @param {object} [vars] - An object containing info that should be passed to the callback. 1161 * @returns {Scene} Parent object for chaining. 1162 */ 1163 this.trigger = function (name, vars) { 1164 if (name) { 1165 var 1166 nameparts = name.trim().split('.'), 1167 eventname = nameparts[0], 1168 namespace = nameparts[1], 1169 listeners = _listeners[eventname]; 1170 log(3, 'event fired:', eventname, vars ? "->" : '', vars || ''); 1171 if (listeners) { 1172 listeners.forEach(function (listener, key) { 1173 if (!namespace || namespace === listener.namespace) { 1174 listener.callback.call(Scene, new ScrollMagic.Event(eventname, listener.namespace, Scene, vars)); 1175 } 1176 }); 1177 } 1178 } else { 1179 log(1, "ERROR: Invalid event name supplied."); 1180 } 1181 return Scene; 1182 }; 1183 1184 // set event listeners 1185 Scene 1186 .on("change.internal", function (e) { 1187 if (e.what !== "loglevel" && e.what !== "tweenChanges") { // no need for a scene update scene with these options... 1188 if (e.what === "triggerElement") { 1189 updateTriggerElementPosition(); 1190 } 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.
1191 Scene.update(); 1192 } 1193 } 1194 }) 1195 .on("shift.internal", function (e) { 1196 updateScrollOffset(); 1197 Scene.update(); // update scene to reflect new position 1198 }); 1199 1200 /** 1201 * Send a debug message to the console. 1202 * @private 1203 * but provided publicly with _log for plugins 1204 * 1205 * @param {number} loglevel - The loglevel required to initiate output for the message. 1206 * @param {...mixed} output - One or more variables that should be passed to the console. 1207 */ 1208 var log = this._log = function (loglevel, output) { 1209 if (_options.loglevel >= loglevel) { 1210 Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ") ->"); 1211 _util.log.apply(window, arguments); 1212 } 1213 }; 1214 1215 /** 1216 * Add the scene to a controller. 1217 * This is the equivalent to `Controller.addScene(scene)`. 1218 * @method ScrollMagic.Scene#addTo 1219 * 1220 * @example 1221 * // add a scene to a ScrollMagic Controller 1222 * scene.addTo(controller); 1223 * 1224 * @param {ScrollMagic.Controller} controller - The controller to which the scene should be added. 1225 * @returns {Scene} Parent object for chaining. 1226 */ 1227 this.addTo = function (controller) { 1228 if (!(controller instanceof ScrollMagic.Controller)) { 1229 log(1, "ERROR: supplied argument of 'addTo()' is not a valid ScrollMagic Controller"); 1230 } else if (_controller != controller) { 1231 // new controller 1232 if (_controller) { // was associated to a different controller before, so remove it... 1233 _controller.removeScene(Scene); 1234 } 1235 _controller = controller; 1236 validateOption(); 1237 updateDuration(true); 1238 updateTriggerElementPosition(true); 1239 updateScrollOffset(); 1240 _controller.info("container").addEventListener('resize', onContainerResize); 1241 controller.addScene(Scene); 1242 Scene.trigger("add", { 1243 controller: _controller 1244 }); 1245 log(3, "added " + NAMESPACE + " to controller"); 1246 Scene.update(); 1247 } 1248 return Scene; 1249 }; 1250 1251 /** 1252 * **Get** or **Set** the current enabled state of the scene. 1253 * This can be used to disable this scene without removing or destroying it. 1254 * @method ScrollMagic.Scene#enabled 1255 * 1256 * @example 1257 * // get the current value 1258 * var enabled = scene.enabled(); 1259 * 1260 * // disable the scene 1261 * scene.enabled(false); 1262 * 1263 * @param {boolean} [newState] - The new enabled state of the scene `true` or `false`. 1264 * @returns {(boolean|Scene)} Current enabled state or parent object for chaining. 1265 */ 1266 this.enabled = function (newState) { 1267 if (!arguments.length) { // get 1268 return _enabled; 1269 } else if (_enabled != newState) { // set 1270 _enabled = !!newState; 1271 Scene.update(true); 1272 } 1273 return Scene; 1274 }; 1275 1276 /** 1277 * Remove the scene from the controller. 1278 * This is the equivalent to `Controller.removeScene(scene)`. 1279 * The scene will not be updated anymore until you readd it to a controller. 1280 * To remove the pin or the tween you need to call removeTween() or removePin() respectively. 1281 * @method ScrollMagic.Scene#remove 1282 * @example 1283 * // remove the scene from its controller 1284 * scene.remove(); 1285 * 1286 * @returns {Scene} Parent object for chaining. 1287 */ 1288 this.remove = function () { 1289 if (_controller) { 1290 _controller.info("container").removeEventListener('resize', onContainerResize); 1291 var tmpParent = _controller; 1292 _controller = undefined; 1293 tmpParent.removeScene(Scene); 1294 Scene.trigger("remove"); 1295 log(3, "removed " + NAMESPACE + " from controller"); 1296 } 1297 return Scene; 1298 }; 1299 1300 /** 1301 * Destroy the scene and everything. 1302 * @method ScrollMagic.Scene#destroy 1303 * @example 1304 * // destroy the scene without resetting the pin and tween to their initial positions 1305 * scene = scene.destroy(); 1306 * 1307 * // destroy the scene and reset the pin and tween 1308 * scene = scene.destroy(true); 1309 * 1310 * @param {boolean} [reset=false] - If `true` the pin and tween (if existent) will be reset. 1311 * @returns {null} Null to unset handler variables. 1312 */ 1313 this.destroy = function (reset) { 1314 Scene.trigger("destroy", { 1315 reset: reset 1316 }); 1317 Scene.remove(); 1318 Scene.off("*.*"); 1319 log(3, "destroyed " + NAMESPACE + " (reset: " + (reset ? "true" : "false") + ")"); 1320 return null; 1321 }; 1322 1323 1324 /** 1325 * Updates the Scene to reflect the current state. 1326 * This is the equivalent to `Controller.updateScene(scene, immediately)`. 1327 * 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. 1328 * 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. 1329 * 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. 1330 * _**NOTE:** This method gets called constantly whenever ScrollMagic detects a change. The only application for you is
1330if you change something outside of the realm of ScrollMagic, like moving the trigger or changing tween parameters._ 1331 * @method ScrollMagic.Scene#update 1332 * @example 1333 * // update the scene on next tick 1334 * scene.update(); 1335 * 1336 * // update the scene immediately 1337 * scene.update(true); 1338 * 1339 * @fires Scene.update 1340 * 1341 * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next update cycle (better performance). 1342 * @returns {Scene} Parent object for chaining. 1343 */ 1344 this.update = function (immediately) { 1345 if (_controller) { 1346 if (immediately) { 1347 if (_controller.enabled() && _enabled) { 1348 var 1349 scrollPos = _controller.info("scrollPos"), 1350 newProgress; 1351 1352 if (_options.duration > 0) { 1353 newProgress = (scrollPos - _scrollOffset.start) / (_scrollOffset.end - _scrollOffset.start); 1354 } else { 1355 newProgress = scrollPos >= _scrollOffset.start ? 1 : 0; 1356 } 1357 1358 Scene.trigger("update", { 1359 startPos: _scrollOffset.start, 1360 endPos: _scrollOffset.end, 1361 scrollPos: scrollPos 1362 }); 1363 1364 Scene.progress(newProgress); 1365 } else if (_pin && _state === SCENE_STATE_DURING) { 1366 updatePinState(true); // unpin in position 1367 } 1368 } else { 1369 _controller.updateScene(Scene, false); 1370 } 1371 } 1372 return Scene; 1373 }; 1374 1375 /** 1376 * Updates dynamic scene variables like the trigger element position or the duration. 1377 * This method is automatically called in regular intervals from the controller. See {@link ScrollMagic.Controller} option `refreshInterval`. 1378 * 1379 * You can call it to minimize lag, for example when you intentionally change the position of the triggerElement. 1380 * If you don't it will simply be updated in the next refresh interval of the container, which is usually sufficient. 1381 * 1382 * @method ScrollMagic.Scene#refresh 1383 * @since 1.1.0 1384 * @example 1385 * scene = new ScrollMagic.Scene({triggerElement: "#trigger"}); 1386 * 1387 * // change the position of the trigger 1388 * $("#trigger").css("top", 500); 1389 * // immediately let the scene know of this change 1390 * scene.refresh(); 1391 * 1392 * @fires {@link Scene.shift}, if the trigger element position or the duration changed 1393 * @fires {@link Scene.change}, if the duration changed 1394 * 1395 * @returns {Scene} Parent object for chaining. 1396 */ 1397 this.refresh = function () { 1398 updateDuration(); 1399 updateTriggerElementPosition(); 1400 // update trigger element position 1401 return Scene; 1402 }; 1403 1404 /** 1405 * **Get** or **Set** the scene's progress. 1406 * Usually it shouldn't be necessary to use this as a setter, as it is set automatically by scene.update(). 1407 * The order in which the events are fired depends on the duration of the scene: 1408 * 1. Scenes with `duration == 0`: 1409 * Scenes that have no duration by definition have no ending. Thus the `end` event will never be fired. 1410 * When the trigger position of the scene is passed the events are always fired in this order: 1411 * `enter`, `start`, `progress` when scrolling forward 1412 * and 1413 * `progress`, `start`, `leave` when scrolling in reverse 1414 * 2. Scenes with `duration > 0`: 1415 * Scenes with a set duration have a defined start and end point. 1416 * When scrolling past the start position of the scene it will fire these events in this order: 1417 * `enter`, `start`, `progress` 1418 * When continuing to scroll and passing the end point it will fire these events: 1419 * `progress`, `end`, `leave` 1420 * When reversing through the end point these events are fired: 1421 * `enter`, `end`, `progress` 1422 * And when continuing to scroll past the start position in reverse it will fire: 1423 * `progress`, `start`, `leave` 1424 * In between start and end the `progress` event will be called constantly, whenever the progress changes. 1425 * 1426 * In short: 1427 * `enter` events will always trigger **before** the progress update and `leave` envents will trigger **after** the progress update. 1428 * `start` and `end` will always trigger at their respective position. 1429 * 1430 * Please review the event descriptions for details on the events and the event object that is passed to the callback. 1431 * 1432 * @method ScrollMagic.Scene#progress 1433 * @example 1434 * // get the current scene progress 1435 * var progress = scene.progress(); 1436 * 1437 * // set new scene progress 1438 * scene.progress(0.3); 1439 * 1440 * @fires {@link Scene.enter}, when used as setter 1441 * @fires {@link Scene.start}, when used as setter 1442 * @fires {@link Scene.progress}, when used as setter 1443 * @fires {@link Scene.end}, when used as setter 1444 * @fires {@link Scene.leave}, when used as setter 1445 * 1446 * @param {number} [progress] - The new progress value of the scene `[0-1]`. 1447 * @returns {number} `get` - Current scene progress. 1448 * @returns {Scene} `set` - Parent object for chaining. 1449 */ 1450 this.progress = function (progress) { 1451 if (!arguments.length) { // get 1452 return _progress; 1453 } else { // set 1454 var 1455 doUpdate = false, 1456 oldState = _state, 1457 scrollDirection = _controller ? _controller.info("scrollDirection") : 'PAUSED', 1458 reverseOrForward = _options.reverse || progress >= _progress; 1459 if (_options.duration === 0) { 1460 // zero duration scenes 1461 doUpdate = _progress != progress; 1462 _progress = progress < 1 && reverseOrForward ? 0 : 1; 1463 _state = _progress === 0 ? SCENE_STATE_BEFORE : SCENE_STATE_DURING; 1464 } else { 1465 // scenes with start and end 1466 if (progress < 0 && _state !== SCENE_STATE_BEFORE && reverseOrForward) { 1467 // go back to initial state 1468 _progress = 0; 1469 _state = SCENE_STATE_BEFORE; 1470 doUpdate = true; 1471 } else if (progress >= 0 && progress < 1 && reverseOrForward) { 1472 _progress = progress; 1473 _state = SCENE_STATE_DURING; 1474 doUpdate = true; 1475 } else if (progress >= 1 && _state !== SCENE_STATE_AFTER) { 1476 _progress = 1; 1477 _state = SCENE_STATE_AFTER; 1478 doUpdate = true; 1479 } else if (_state === SCENE_STATE_DURING && !reverseOrForward) { 1480 updatePinState(); // in case we scrolled backwards mid-scene and reverse is disabled => update the pin position, so it doesn't move back as well. 1481 } 1482 } 1483 if (doUpdate) { 1484 // fire events 1485 var 1486 eventVars = { 1487 progress: _progress, 1488 state: _state, 1489 scrollDirection: scrollDirection 1490 }, 1491 stateChanged = _state != oldState; 1492 1493 var trigger = function (eventName) { // tmp helper to simplify code 1494 Scene.trigger(eventName, eventVars); 1495 }; 1496 1497 if (stateChanged) { // enter events 1498 if (oldState !== SCENE_STATE_DURING) { 1499 trigger("enter"); 1500 trigger(oldState === SCENE_STATE_BEFORE ? "start" : "end"); 1501 } 1502 } 1503 trigger("progress"); 1504 if (stateChanged) { // leave events 1505 if (_state !== SCENE_STATE_DURING) { 1506 trigger(_state === SCENE_STATE_BEFORE ? "start" : "end"); 1507 trigger("leave"); 1508 } 1509 } 1510 } 1511 1512 return Scene; 1513 } 1514 }; 1515 1516 1517 /** 1518 * Update the start and end scrollOffset of the container. 1519 * The positions reflect what the controller's scroll position will be at the start and end respectively. 1520 * Is called, when: 1521 * - Scene event "change" is called with: offset, triggerHook, duration 1522 * - scroll container event "resize" is called 1523 * - the position of the triggerElement changes 1524 * - the controller changes -> addTo() 1525 * @private 1526 */ 1527 var updateScrollOffset = function () { 1528 _scrollOffset = { 1529 start: _triggerPos + _options.offset 1530 }; 1531 if (_controller && _options.triggerElement) { 1532 // take away triggerHook portion to get relative to top 1533 _scrollOffset.start -= _controller.info("size") * _options.triggerHook; 1534 } 1535 _scrollOffset.end = _scrollOffset.start + _options.duration; 1536 }; 1537 1538 /** 1539 * Updates the duration if set to a dynamic function. 1540 * This method is called when the scene is added to a controller and in regular intervals from the controller through scene.refresh(). 1541 * 1542 * @fires {@link Scene.change}, if the duration changed 1543 * @fires {@link Scene.shift}, if the duration changed 1544 * 1545 * @param {boolean} [suppressEvents=false] - If true the shift event will be suppressed. 1546 * @private 1547 */ 1548 var updateDuration = function (suppressEvents) { 1549 // update duration 1550 if (_durationUpdateMethod) { 1551 var varname = "duration"; 1552 if (changeOption(varname, _durationUpdateMethod.call(Scene)) && !suppressEvents) { // set 1553 Scene.trigger("change", { 1554 what: varname, 1555 newval: _options[varname] 1556 });
1557 Scene.trigger("shift", { 1558 reason: varname 1559 }); 1560 } 1561 } 1562 }; 1563 1564 /** 1565 * Updates the position of the triggerElement, if present. 1566 * This method is called ... 1567 * - ... when the triggerElement is changed 1568 * - ... when the scene is added to a (new) controller 1569 * - ... in regular intervals from the controller through scene.refresh(). 1570 * 1571 * @fires {@link Scene.shift}, if the position changed 1572 * 1573 * @param {boolean} [suppressEvents=false] - If true the shift event will be suppressed. 1574 * @private 1575 */ 1576 var updateTriggerElementPosition = function (suppressEvents) { 1577 var 1578 elementPos = 0, 1579 telem = _options.triggerElement; 1580 if (_controller && (telem || _triggerPos > 0)) { // either an element exists or was removed and the triggerPos is still > 0 1581 if (telem) { // there currently a triggerElement set 1582 if (telem.parentNode) { // check if element is still attached to DOM 1583 var 1584 controllerInfo = _controller.info(), 1585 containerOffset = _util.get.offset(controllerInfo.container), // container position is needed because element offset is returned in relation to document, not in relation to container. 1586 param = controllerInfo.vertical ? "top" : "left"; // which param is of interest ? 1587 1588 // if parent is spacer, use spacer position instead so correct start position is returned for pinned elements. 1589 while (telem.parentNode.hasAttribute(PIN_SPACER_ATTRIBUTE)) { 1590 telem = telem.parentNode; 1591 } 1592 1593 var elementOffset = _util.get.offset(telem); 1594 1595 if (!controllerInfo.isDocument) { // container is not the document root, so substract scroll Position to get correct trigger element position relative to scrollcontent 1596 containerOffset[param] -= _controller.scrollPos(); 1597 } 1598 1599 elementPos = elementOffset[param] - containerOffset[param]; 1600 1601 } else { // there was an element, but it was removed from DOM 1602 log(2, "WARNING: triggerElement was removed from DOM and will be reset to", undefined); 1603 Scene.triggerElement(undefined); // unset, so a change event is triggered 1604 } 1605 } 1606 1607 var changed = elementPos != _triggerPos; 1608 _triggerPos = elementPos; 1609 if (changed && !suppressEvents) { 1610 Scene.trigger("shift", { 1611 reason: "triggerElementPosition" 1612 }); 1613 } 1614 } 1615 }; 1616 1617 /** 1618 * Trigger a shift event, when the container is resized and the triggerHook is > 1. 1619 * @private 1620 */ 1621 var onContainerResize = function (e) { 1622 if (_options.triggerHook > 0) { 1623 Scene.trigger("shift", { 1624 reason: "containerResize" 1625 }); 1626 } 1627 }; 1628 1629 1630 var _validate = _util.extend(SCENE_OPTIONS.validate, { 1631 // validation for duration handled internally for reference to private var _durationMethod 1632 duration: function (val) { 1633 if (_util.type.String(val) && val.match(/^(\.|\d)*\d+%$/)) { 1634 // percentage value 1635 var perc = parseFloat(val) / 100; 1636 val = function () { 1637 return _controller ? _controller.info("size") * perc : 0; 1638 }; 1639 } 1640 if (_util.type.Function(val)) { 1641 // function 1642 _durationUpdateMethod = val; 1643 try { 1644 val = parseFloat(_durationUpdateMethod.call(Scene)); 1645 } catch (e) { 1646 val = -1; // will cause error below 1647 } 1648 } 1649 // val has to be float 1650 val = parseFloat(val); 1651 if (!_util.type.Number(val) || val < 0) { 1652 if (_durationUpdateMethod) { 1653 _durationUpdateMethod = undefined; 1654 throw ["Invalid return value of supplied function for option \"duration\":", val]; 1655 } else { 1656 throw ["Invalid value for option \"duration\":", val]; 1657 } 1658 } 1659 return val; 1660 } 1661 }); 1662 1663 /** 1664 * Checks the validity of a specific or all options and reset to default if neccessary. 1665 * @private 1666 */ 1667 var validateOption = function (check) { 1668 check = arguments.length ? [check] : Object.keys(_validate); 1669 check.forEach(function (optionName, key) { 1670 var value; 1671 if (_validate[optionName]) { // there is a validation method for this option 1672 try { // validate value 1673 value = _validate[optionName](_options[optionName]); 1674 } catch (e) { // validation failed -> reset to default 1675 value = DEFAULT_OPTIONS[optionName]; 1676 var logMSG = _util.type.String(e) ? [e] : e; 1677 if (_util.type.Array(logMSG)) { 1678 logMSG[0] = "ERROR: " + logMSG[0]; 1679 logMSG.unshift(1); // loglevel 1 for error msg 1680 log.apply(this, logMSG); 1681 } else { 1682 log(1, "ERROR: Problem executing validation callback for option '" + optionName + "':", e.message); 1683 } 1684 } finally { 1685 _options[optionName] = value; 1686 } 1687 } 1688 }); 1689 }; 1690 1691 /** 1692 * Helper used by the setter/getters for scene options 1693 * @private 1694 */ 1695 var changeOption = function (varname, newval) { 1696 var 1697 changed = false, 1698 oldval = _options[varname]; 1699 if (_options[varname] != newval) { 1700 _options[varname] = newval; 1701 validateOption(varname); // resets to default if necessary 1702 changed = oldval != _options[varname]; 1703 } 1704 return changed; 1705 }; 1706 1707 // generate getters/setters for all options 1708 var addSceneOption = function (optionName) { 1709 if (!Scene[optionName]) { 1710 Scene[optionName] = function (newVal) { 1711 if (!arguments.length) { // get 1712 return _options[optionName]; 1713 } else { 1714 if (optionName === "duration") { // new duration is set, so any previously set function must be unset 1715 _durationUpdateMethod = undefined; 1716 } 1717 if (changeOption(optionName, newVal)) { // set 1718 Scene.trigger("change", { 1719 what: optionName, 1720 newval: _options[optionName] 1721 });
1722 if (SCENE_OPTIONS.shifts.indexOf(optionName) > -1) { 1723 Scene.trigger("shift", { 1724 reason: optionName 1725 }); 1726 } 1727 } 1728 } 1729 return Scene; 1730 }; 1731 } 1732 }; 1733 1734 /** 1735 * **Get** or **Set** the duration option value. 1736 * 1737 * As a **setter** it accepts three types of parameters: 1738 * 1. `number`: Sets the duration of the scene to exactly this amount of pixels. 1739 * This means the scene will last for exactly this amount of pixels scrolled. Sub-Pixels are also valid. 1740 * A value of `0` means that the scene is 'open end' and no end will be triggered. Pins will never unpin and animations will play independently of scroll progress. 1741 * 2. `string`: Always updates the duration relative to parent scroll container. 1742 * For example `"100%"` will keep the duration always exactly at the inner height of the scroll container. 1743 * When scrolling vertically the width is used for reference respectively. 1744 * 3. `function`: The supplied function will be called to return the scene duration. 1745 * This is useful in setups where the duration depends on other elements who might change size. By supplying a function you can return a value instead of updating potentially multiple scene durations. 1746 * The scene can be referenced inside the callback using `this`. 1747 * _**WARNING:** This is an easy way to kill performance, as the callback will be executed every time `Scene.refresh()` is called, which happens a lot. The interval is defined by the controller (see ScrollMagic.Controller option `refreshInterval`). 1748 * It's recomended to avoid calculations within the function and use cached variables as return values. 1749 * This counts double if you use the same function for multiple scenes._ 1750 * 1751 * @method ScrollMagic.Scene#duration 1752 * @example 1753 * // get the current duration value 1754 * var duration = scene.duration(); 1755 * 1756 * // set a new duration 1757 * scene.duration(300); 1758 * 1759 * // set duration responsively to container size 1760 * scene.duration("100%"); 1761 * 1762 * // use a function to randomize the duration for some reason. 1763 * var durationValueCache; 1764 * function durationCallback () { 1765 * return durationValueCache; 1766 * } 1767 * function updateDuration () { 1768 * durationValueCache = Math.random() * 100; 1769 * } 1770 * updateDuration(); // set to initial value 1771 * scene.duration(durationCallback); // set duration callback 1772 * 1773 * @fires {@link Scene.change}, when used as setter 1774 * @fires {@link Scene.shift}, when used as setter 1775 * @param {(number|string|function)} [newDuration] - The new duration setting for the scene. 1776 * @returns {number} `get` - Current scene duration. 1777 * @returns {Scene} `set` - Parent object for chaining. 1778 */ 1779 1780 /** 1781 * **Get** or **Set** the offset option value. 1782 * @method ScrollMagic.Scene#offset 1783 * @example 1784 * // get the current offset 1785 * var offset = scene.offset(); 1786 * 1787 * // set a new offset 1788 * scene.offset(100); 1789 * 1790 * @fires {@link Scene.change}, when used as setter 1791 * @fires {@link Scene.shift}, when used as setter 1792 * @param {number} [newOffset] - The new offset of the scene. 1793 * @returns {number} `get` - Current scene offset. 1794 * @returns {Scene} `set` - Parent object for chaining. 1795 */ 1796 1797 /** 1798 * **Get** or **Set** the triggerElement option value. 1799 * Does **not** fire `Scene.shift`, because changing the trigger Element doesn't necessarily mean the start position changes. This will be determined in `Scene.refresh()`, which is automatically triggered. 1800 * @method ScrollMagic.Scene#triggerElement 1801 * @example 1802 * // get the current triggerElement 1803 * var triggerElement = scene.triggerElement(); 1804 * 1805 * // set a new triggerElement using a selector 1806 * scene.triggerElement("#trigger"); 1807 * // set a new triggerElement using a DOM object 1808 * scene.triggerElement(document.getElementById("trigger")); 1809 * 1810 * @fires {@link Scene.change}, when used as setter 1811 * @param {(string|object)} [newTriggerElement] - The new trigger element for the scene. 1812 * @returns {(string|object)} `get` - Current triggerElement. 1813 * @returns {Scene} `set` - Parent object for chaining. 1814 */ 1815 1816 /** 1817 * **Get** or **Set** the triggerHook option value. 1818 * @method ScrollMagic.Scene#triggerHook 1819 * @example 1820 * // get the current triggerHook value 1821 * var triggerHook = scene.triggerHook(); 1822 * 1823 * // set a new triggerHook using a string 1824 * scene.triggerHook("onLeave"); 1825 * // set a new triggerHook using a number 1826 * scene.triggerHook(0.7); 1827 * 1828 * @fires {@link Scene.change}, when used as setter 1829 * @fires {@link Scene.shift}, when used as setter 1830 * @param {(number|string)} [newTriggerHook] - The new triggerHook of the scene. See {@link Scene} parameter description for value options. 1831 * @returns {number} `get` - Current triggerHook (ALWAYS numerical). 1832 * @returns {Scene} `set` - Parent object for chaining. 1833 */ 1834 1835 /** 1836 * **Get** or **Set** the reverse option value. 1837 * @method ScrollMagic.Scene#reverse 1838 * @example 1839 * // get the current reverse option 1840 * var reverse = scene.reverse(); 1841 * 1842 * // set new reverse option 1843 * scene.reverse(false); 1844 * 1845 * @fires {@link Scene.change}, when used as setter 1846 * @param {boolean} [newReverse] - The new reverse setting of the scene. 1847 * @returns {boolean} `get` - Current reverse option value. 1848 * @returns {Scene} `set` - Parent object for chaining. 1849 */ 1850 1851 /** 1852 * **Get** or **Set** the loglevel option value. 1853 * @method ScrollMagic.Scene#loglevel 1854 * @example 1855 * // get the current loglevel 1856 * var loglevel = scene.loglevel(); 1857 * 1858 * // set new loglevel 1859 * scene.loglevel(3); 1860 * 1861 * @fires {@link Scene.change}, when used as setter 1862 * @param {number} [newLoglevel] - The new loglevel setting of the scene. `[0-3]` 1863 * @returns {number} `get` - Current loglevel. 1864 * @returns {Scene} `set` - Parent object for chaining. 1865 */ 1866 1867 /** 1868 * **Get** the associated controller. 1869 * @method ScrollMagic.Scene#controller 1870 * @example 1871 * // get the controller of a scene 1872 * var controller = scene.controller(); 1873 * 1874 * @returns {ScrollMagic.Controller} Parent controller or `undefined` 1875 */ 1876 this.controller = function () { 1877 return _controller; 1878 }; 1879 1880 /** 1881 * **Get** the current state. 1882 * @method ScrollMagic.Scene#state 1883 * @example 1884 * // get the current state 1885 * var state = scene.state(); 1886 * 1887 * @returns {string} `"BEFORE"`, `"DURING"` or `"AFTER"` 1888 */ 1889 this.state = function () { 1890 return _state; 1891 }; 1892 1893 /** 1894 * **Get** the current scroll offset for the start of the scene. 1895 * Mind, that the scrollOffset is related to the size of the container, if `triggerHook` is bigger than `0` (or `"onLeave"`). 1896 * This means, that resizing the container or changing the `triggerHook` will influence the scene's start offset. 1897 * @method ScrollMagic.Scene#scrollOffset 1898 * @example 1899 * // get the current scroll offset for the start and end of the scene. 1900 * var start = scene.scrollOffset(); 1901 * var end = scene.scrollOffset() + scene.duration(); 1902 * console.log("the scene starts at", start, "and ends at", end); 1903 * 1904 * @returns {number} The scroll offset (of the container) at which the scene will trigger. Y value for vertical and X value for horizontal scrolls. 1905 */ 1906 this.scrollOffset = function () { 1907 return _scrollOffset.start; 1908 }; 1909 1910 /** 1911 * **Get** the trigger position of the scene (including the value of the `offset` option). 1912 * @method ScrollMagic.Scene#triggerPosition 1913 * @example 1914 * // get the scene's trigger position 1915 * var triggerPosition = scene.triggerPosition(); 1916 * 1917 * @returns {number} Start position of the scene. Top position value for vertical and left position value for horizontal scrolls. 1918 */ 1919 this.triggerPosition = function () { 1920 var pos = _options.offset; // the offset is the basis 1921 if (_controller) { 1922 // get the trigger position 1923 if (_options.triggerElement) { 1924 // Element as trigger 1925 pos += _triggerPos; 1926 } else { 1927 // return the height of the triggerHook to start at the beginning 1928 pos += _controller.info("size") * Scene.triggerHook(); 1929 } 1930 } 1931 return pos; 1932 }; 1933 1934 1935 var 1936 _pin, 1937 _pinOptions; 1938 1939 Scene 1940 .on("shift.internal", function (e) { 1941 var durationChanged = e.reason === "duration"; 1942 if ((_state === SCENE_STATE_AFTER && durationChanged) || (_state === SCENE_STATE_DURING && _options.duration === 0)) { 1943 // 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]. 1944 updatePinState(); 1945 } 1946 if (durationChanged) { 1947 updatePinDimensions(); 1948 } 1949 }) 1950 .on("progress.internal", function (e) { 1951 updatePinState(); 1952 }) 1953 .on("add.internal", function (e) { 1954 updatePinDimensions(); 1955 }) 1956 .on("destroy.internal", function (e) { 1957 Scene.removePin(e.reset); 1958 }); 1959 /** 1960 * Update the pin state. 1961 * @private 1962 */ 1963 var updatePinState = function (forceUnpin) { 1964 if (_pin && _controller) { 1965 var 1966 containerInfo = _controller.info(), 1967 pinTarget = _pinOptions.spacer.firstChild; // may be pin element or another spacer, if cascading pins 1968 1969 if (!forceUnpin && _state === SCENE_STATE_DURING) { // during scene or if duration is 0 and we are past the trigger 1970 // pinned state 1971 if (_util.css(pinTarget, "position") != "fixed") { 1972 // change state before updating pin spacer (position changes due to fixed collapsing might occur.) 1973 _util.css(pinTarget, { 1974 "position": "fixed" 1975 });
1976 // update pin spacer 1977 updatePinDimensions(); 1978 } 1979 1980 var 1981 fixedPos = _util.get.offset(_pinOptions.spacer, true), // get viewport position of spacer 1982 scrollDistance = _options.reverse || _options.duration === 0 ? 1983 containerInfo.scrollPos - _scrollOffset.start // quicker 1984 : 1985 Math.round(_progress * _options.duration * 10) / 10; // if no reverse and during pin the position needs to be recalculated using the progress 1986 1987 // add scrollDistance 1988 fixedPos[containerInfo.vertical ? "top" : "left"] += scrollDistance; 1989 1990 // set new values 1991 _util.css(_pinOptions.spacer.firstChild, { 1992 top: fixedPos.top, 1993 left: fixedPos.left 1994 }); 1995 } else { 1996 // unpinned state 1997 var 1998 newCSS = { 1999 position: _pinOptions.inFlow ? "relative" : "absolute", 2000 top: 0, 2001 left: 0 2002 }, 2003 change = _util.css(pinTarget, "position") != newCSS.position; 2004 2005 if (!_pinOptions.pushFollowers) { 2006 newCSS[containerInfo.vertical ? "top" : "left"] = _options.duration * _progress; 2007 } else if (_options.duration > 0) { // only concerns scenes with duration 2008 if (_state === SCENE_STATE_AFTER && parseFloat(_util.css(_pinOptions.spacer, "padding-top")) === 0) { 2009 change = true; // if in after state but havent updated spacer yet (jumped past pin) 2010 } else if (_state === SCENE_STATE_BEFORE && parseFloat(_util.css(_pinOptions.spacer, "padding-bottom")) === 0) { // before 2011 change = true; // jumped past fixed state upward direction 2012 } 2013 } 2014 // set new values 2015 _util.css(pinTarget, newCSS); 2016 if (change) { 2017 // update pin spacer if state changed 2018 updatePinDimensions(); 2019 } 2020 } 2021 } 2022 }; 2023 2024 /** 2025 * Update the pin spacer and/or element size. 2026 * The size of the spacer needs to be updated whenever the duration of the scene changes, if it is to push down following elements. 2027 * @private 2028 */ 2029 var updatePinDimensions = function () { 2030 if (_pin && _controller && _pinOptions.inFlow) { // no spacerresize, if original position is absolute 2031 var 2032 after = (_state === SCENE_STATE_AFTER), 2033 before = (_state === SCENE_STATE_BEFORE), 2034 during = (_state === SCENE_STATE_DURING), 2035 vertical = _controller.info("vertical"), 2036 pinTarget = _pinOptions.spacer.firstChild, // usually the pined element but can also be another spacer (cascaded pins) 2037 marginCollapse = _util.isMarginCollapseType(_util.css(_pinOptions.spacer, "display")), 2038 css = {}; 2039 2040 // set new size 2041 // if relsize: spacer -> pin | else: pin -> spacer 2042 if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) { 2043 if (during) { 2044 _util.css(_pin, { 2045 "width": _util.get.width(_pinOptions.spacer) 2046 }); 2047 } else { 2048 _util.css(_pin, { 2049 "width": "100%" 2050 }); 2051 } 2052 } else { 2053 // minwidth is needed for cascaded pins. 2054 css["min-width"] = _util.get.width(vertical ? _pin : pinTarget, true, true); 2055 css.width = during ? css["min-width"] : "auto"; 2056 } 2057 if (_pinOptions.relSize.height) { 2058 if (during) { 2059 // the only padding the spacer should ever include is the duration (if pushFollowers = true), so we need to substract that. 2060 _util.css(_pin, { 2061 "height": _util.get.height(_pinOptions.spacer) - (_pinOptions.pushFollowers ? _options.duration : 0) 2062 }); 2063 } else { 2064 _util.css(_pin, { 2065 "height": "100%" 2066 }); 2067 } 2068 } else { 2069 // margin is only included if it's a cascaded pin to resolve an IE9 bug 2070 css["min-height"] = _util.get.height(vertical ? pinTarget : _pin, true, !marginCollapse); // needed for cascading pins 2071 css.height = during ? css["min-height"] : "auto"; 2072 } 2073 2074 // add space for duration if pushFollowers is true 2075 if (_pinOptions.pushFollowers) { 2076 css["padding" + (vertical ? "Top" : "Left")] = _options.duration * _progress; 2077 css["padding" + (vertical ? "Bottom" : "Right")] = _options.duration * (1 - _progress); 2078 } 2079 _util.css(_pinOptions.spacer, css); 2080 } 2081 }; 2082 2083 /** 2084 * Updates the Pin state (in certain scenarios) 2085 * 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. 2086 * So this function is called on resize and scroll of the document. 2087 * @private 2088 */ 2089 var updatePinInContainer = function () { 2090 if (_controller && _pin && _state === SCENE_STATE_DURING && !_controller.info("isDocument")) { 2091 updatePinState(); 2092 } 2093 }; 2094 2095 /** 2096 * Updates the Pin spacer size state (in certain scenarios) 2097 * If container is resized during pin and relatively sized the size of the pin might need to be updated... 2098 * So this function is called on resize of the container. 2099 * @private 2100 */ 2101 var updateRelativePinSpacer = function () { 2102 if (_controller && _pin && // well, duh 2103 _state === SCENE_STATE_DURING && // element in pinned state? 2104 ( // is width or height relatively sized, but not in relation to body? then we need to recalc. 2105 ((_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) && _util.get.width(window) != _util.get.width(_pinOptions.spacer.parentNode)) || 2106 (_pinOptions.relSize.height && _util.get.height(window) != _util.get.height(_pinOptions.spacer.parentNode)) 2107 ) 2108 ) { 2109 updatePinDimensions(); 2110 } 2111 }; 2112 2113 /** 2114 * Is called, when the mousewhel is used while over a pinned element inside a div container. 2115 * If the scene is in fixed state scroll events would be counted towards the body. This forwards the event to the scroll container. 2116 * @private 2117 */ 2118 var onMousewheelOverPin = function (e) { 2119 if (_controller && _pin && _state === SCENE_STATE_DURING && !_controller.info("isDocument")) { // in pin
2119state 2120 e.preventDefault(); 2121 _controller._setScrollPos(_controller.info("scrollPos") - ((e.wheelDelta || e[_controller.info("vertical") ? "wheelDeltaY" : "wheelDeltaX"]) / 3 || -e.detail * 30)); 2122 } 2123 }; 2124 2125 /** 2126 * Pin an element for the duration of the scene. 2127 * If the scene duration is 0 the element will only be unpinned, if the user scrolls back past the start position. 2128 * Make sure only one pin is applied to an element at the same time. 2129 * An element can be pinned multiple times, but only successively. 2130 * _**NOTE:** The option `pushFollowers` has no effect, when the scene duration is 0._ 2131 * @method ScrollMagic.Scene#setPin 2132 * @example 2133 * // pin element and push all following elements down by the amount of the pin duration. 2134 * scene.setPin("#pin"); 2135 * 2136 * // pin element and keeping all following elements in their place. The pinned element will move past them. 2137 * scene.setPin("#pin", {pushFollowers: false}); 2138 * 2139 * @param {(string|object)} element - A Selector targeting an element or a DOM object that is supposed to be pinned. 2140 * @param {object} [settings] - settings for the pin 2141 * @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. 2142 Ignored, when duration is `0`. 2143 * @param {string} [settings.spacerClass="scrollmagic-pin-spacer"] - Classname of the pin spacer element, which is used to replace the element. 2144 * 2145 * @returns {Scene} Parent object for chaining. 2146 */ 2147 this.setPin = function (element, settings) { 2148 var 2149 defaultSettings = { 2150 pushFollowers: true, 2151 spacerClass: "scrollmagic-pin-spacer" 2152 }; 2153 var pushFollowersActivelySet = settings && settings.hasOwnProperty('pushFollowers'); 2154 settings = _util.extend({}, defaultSettings, settings); 2155 2156 // validate Element 2157 element = _util.get.elements(element)[0]; 2158 if (!element) { 2159 log(1, "ERROR calling method 'setPin()': Invalid pin element supplied."); 2160 return Scene; // cancel 2161 } else if (_util.css(element, "position") === "fixed") { 2162 log(1, "ERROR calling method 'setPin()': Pin does not work with elements that are positioned 'fixed'."); 2163 return Scene; // cancel 2164 } 2165 2166 if (_pin) { // preexisting pin? 2167 if (_pin === element) { 2168 // same pin we already have -> do nothing 2169 return Scene; // cancel 2170 } else { 2171 // kill old pin 2172 Scene.removePin(); 2173 } 2174 2175 } 2176 _pin = element; 2177 2178 var 2179 parentDisplay = _pin.parentNode.style.display, 2180 boundsParams = ["top", "left", "bottom", "right", "margin", "marginLeft", "marginRight", "marginTop", "marginBottom"]; 2181 2182 _pin.parentNode.style.display = 'none'; // hack start to force css to return stylesheet values instead of calculated px values. 2183 var 2184 inFlow = _util.css(_pin, "position") != "absolute", 2185 pinCSS = _util.css(_pin, boundsParams.concat(["display"])), 2186 sizeCSS = _util.css(_pin, ["width", "height"]); 2187 _pin.parentNode.style.display = parentDisplay; // hack end. 2188 2189 if (!inFlow && settings.pushFollowers) { 2190 log(2, "WARNING: If the pinned element is positioned absolutely pushFollowers will be disabled."); 2191 settings.pushFollowers = false; 2192 } 2193 window.setTimeout(function () { // wait until all finished, because with responsive duration it will only be set after scene is added to controller 2194 if (_pin && _options.duration === 0 && pushFollowersActivelySet && settings.pushFollowers) { 2195 log(2, "WARNING: pushFollowers =", true, "has no effect, when scene duration is 0."); 2196 } 2197 }, 0); 2198 2199 // create spacer and insert 2200 var 2201 spacer = _pin.parentNode.insertBefore(document.createElement('div'), _pin), 2202 spacerCSS = _util.extend(pinCSS, { 2203 position: inFlow ? "relative" : "absolute", 2204 boxSizing: "content-box", 2205 mozBoxSizing: "content-box", 2206 webkitBoxSizing: "content-box" 2207 }); 2208 2209 if (!inFlow) { // copy size if positioned absolutely, to work for bottom/right positioned elements. 2210 _util.extend(spacerCSS, _util.css(_pin, ["width", "height"])); 2211 } 2212 2213 _util.css(spacer, spacerCSS); 2214 spacer.setAttribute(PIN_SPACER_ATTRIBUTE, ""); 2215 _util.addClass(spacer, settings.spacerClass); 2216 2217 // set the pin Options 2218 _pinOptions = { 2219 spacer: spacer,
2220 relSize: { // save if size is defined using % values. if so, handle spacer resize differently... 2221 width: sizeCSS.width.slice(-1) === "%", 2222 height: sizeCSS.height.slice(-1) === "%", 2223 autoFullWidth: sizeCSS.width === "auto" && inFlow && _util.isMarginCollapseType(pinCSS.display) 2224 }, 2225 pushFollowers: settings.pushFollowers, 2226 inFlow: inFlow, // stores if the element takes up space in the document flow 2227 }; 2228 2229 if (!_pin.___origStyle) { 2230 _pin.___origStyle = {}; 2231 var 2232 pinInlineCSS = _pin.style, 2233 copyStyles = boundsParams.concat(["width", "height", "position", "boxSizing", "mozBoxSizing", "webkitBoxSizing"]); 2234 copyStyles.forEach(function (val) { 2235 _pin.___origStyle[val] = pinInlineCSS[val] || ""; 2236 }); 2237 } 2238 2239 // if relative size, transfer it to spacer and make pin calculate it... 2240 if (_pinOptions.relSize.width) { 2241 _util.css(spacer, { 2242 width: sizeCSS.width 2243 }); 2244 } 2245 if (_pinOptions.relSize.height) { 2246 _util.css(spacer, { 2247 height: sizeCSS.height 2248 }); 2249 } 2250 2251 // now place the pin element inside the spacer 2252 spacer.appendChild(_pin); 2253 // and set new css 2254 _util.css(_pin, { 2255 position: inFlow ? "relative" : "absolute", 2256 margin: "auto", 2257 top: "auto", 2258 left: "auto", 2259 bottom: "auto", 2260 right: "auto" 2261 }); 2262 2263 if (_pinOptions.relSize.width || _pinOptions.relSize.autoFullWidth) { 2264 _util.css(_pin, { 2265 boxSizing: "border-box", 2266 mozBoxSizing: "border-box", 2267 webkitBoxSizing: "border-box" 2268 }); 2269 } 2270 2271 // add listener to document to update pin position in case controller is not the document. 2272 window.addEventListener('scroll', updatePinInContainer); 2273 window.addEventListener('resize', updatePinInContainer); 2274 window.addEventListener('resize', updateRelativePinSpacer); 2275 // add mousewheel listener to catch scrolls over fixed elements 2276 _pin.addEventListener("mousewheel", onMousewheelOverPin); 2277 _pin.addEventListener("DOMMouseScroll", onMousewheelOverPin); 2278 2279 log(3, "added pin"); 2280 2281 // finally update the pin to init 2282 updatePinState(); 2283 2284 return Scene; 2285 }; 2286 2287 /** 2288 * Remove the pin from the scene. 2289 * @method ScrollMagic.Scene#removePin 2290 * @example 2291 * // remove the pin from the scene without resetting it (the spacer is not removed) 2292 * scene.removePin(); 2293 * 2294 * // remove the pin from the scene and reset the pin element to its initial position (spacer is removed) 2295 * scene.removePin(true); 2296 * 2297 * @param {boolean} [reset=false] - If `false` the spacer will not be removed and the element's position will not be reset. 2298 * @returns {Scene} Parent object for chaining. 2299 */ 2300 this.removePin = function (reset) { 2301 if (_pin) { 2302 if (_state === SCENE_STATE_DURING) { 2303 updatePinState(true); // force unpin at position 2304 } 2305 if (reset || !_controller) { // if there's no controller no progress was made anyway... 2306 var pinTarget = _pinOptions.spacer.firstChild; // usually the pin element, but may be another spacer (cascaded pins)... 2307 if (pinTarget.hasAttribute(PIN_SPACER_ATTRIBUTE)) { // copy margins to child spacer 2308 var 2309 style = _pinOptions.spacer.style, 2310 values = ["margin", "marginLeft", "marginRight", "marginTop", "marginBottom"], 2311 margins = {}; 2312 values.forEach(function (val) { 2313 margins[val] = style[val] || ""; 2314 }); 2315 _util.css(pinTarget, margins); 2316 } 2317 _pinOptions.spacer.parentNode.insertBefore(pinTarget, _pinOptions.spacer); 2318 _pinOptions.spacer.parentNode.removeChild(_pinOptions.spacer); 2319 if (!_pin.parentNode.hasAttribute(PIN_SPACER_ATTRIBUTE)) { // if it's the last pin for this element -> restore inline styles 2320 // TODO: only correctly set for first pin (when cascading) - how to fix? 2321 _util.css(_pin, _pin.___origStyle); 2322 delete _pin.___origStyle; 2323 } 2324 } 2325 window.removeEventListener('scroll', updatePinInContainer); 2326 window.removeEventListener('resize', updatePinInContainer); 2327 window.removeEventListener('resize', updateRelativePinSpacer); 2328 _pin.removeEventListener("mousewheel", onMousewheelOverPin); 2329 _pin.removeEventListener("DOMMouseScroll", onMousewheelOverPin); 2330 _pin = undefined; 2331 log(3, "removed pin (reset: " + (reset ? "true" : "false") + ")"); 2332 } 2333 return Scene; 2334 }; 2335 2336 2337 var
2338 _cssClasses, 2339 _cssClassElems = []; 2340 2341 Scene 2342 .on("destroy.internal", function (e) { 2343 Scene.removeClassToggle(e.reset); 2344 }); 2345 /** 2346 * Define a css class modification while the scene is active. 2347 * When the scene triggers the classes will be added to the supplied element and removed, when the scene is over. 2348 * If the scene duration is 0 the classes will only be removed if the user scrolls back past the start position. 2349 * @method ScrollMagic.Scene#setClassToggle 2350 * @example 2351 * // add the class 'myclass' to the element with the id 'my-elem' for the duration of the scene 2352 * scene.setClassToggle("#my-elem", "myclass"); 2353 * 2354 * // add multiple classes to multiple elements defined by the selector '.classChange' 2355 * scene.setClassToggle(".classChange", "class1 class2 class3"); 2356 * 2357 * @param {(string|object)} element - A Selector targeting one or more elements or a DOM object that is supposed to be modified. 2358 * @param {string} classes - One or more Classnames (separated by space) that should be added to the element during the scene. 2359 * 2360 * @returns {Scene} Parent object for chaining. 2361 */ 2362 this.setClassToggle = function (element, classes) { 2363 var elems = _util.get.elements(element); 2364 if (elems.length === 0 || !_util.type.String(classes)) { 2365 log(1, "ERROR calling method 'setClassToggle()': Invalid " + (elems.length === 0 ? "element" : "classes") + " supplied."); 2366 return Scene; 2367 } 2368 if (_cssClassElems.length > 0) { 2369 // remove old ones 2370 Scene.removeClassToggle(); 2371 } 2372 _cssClasses = classes; 2373 _cssClassElems = elems; 2374 Scene.on("enter.internal_class leave.internal_class", function (e) { 2375 var toggle = e.type === "enter" ? _util.addClass : _util.removeClass; 2376 _cssClassElems.forEach(function (elem, key) {
2377 toggle(elem, _cssClasses); 2378 }); 2379 }); 2380 return Scene; 2381 }; 2382 2383 /** 2384 * Remove the class binding from the scene. 2385 * @method ScrollMagic.Scene#removeClassToggle 2386 * @example 2387 * // remove class binding from the scene without reset 2388 * scene.removeClassToggle(); 2389 * 2390 * // remove class binding and remove the changes it caused 2391 * scene.removeClassToggle(true); 2392 * 2393 * @param {boolean} [reset=false] - If `false` and the classes are currently active, they will remain on the element. If `true` they will be removed. 2394 * @returns {Scene} Parent object for chaining. 2395 */ 2396 this.removeClassToggle = function (reset) { 2397 if (reset) { 2398 _cssClassElems.forEach(function (elem, key) { 2399 _util.removeClass(elem, _cssClasses); 2400 }); 2401 } 2402 Scene.off("start.internal_class end.internal_class"); 2403 _cssClasses = undefined; 2404 _cssClassElems = []; 2405 return Scene; 2406 }; 2407 2408 // INIT 2409 construct(); 2410 return Scene; 2411 }; 2412 2413 // store pagewide scene options 2414 var SCENE_OPTIONS = { 2415 defaults: { 2416 duration: 0, 2417 offset: 0, 2418 triggerElement: undefined, 2419 triggerHook: 0.5, 2420 reverse: true, 2421 loglevel: 2 2422 }, 2423 validate: { 2424 offset: function (val) { 2425 val = parseFloat(val); 2426 if (!_util.type.Number(val)) { 2427 throw ["Invalid value for option \"offset\":", val]; 2428 } 2429 return val; 2430 }, 2431 triggerElement: function (val) { 2432 val = val || undefined; 2433 if (val) { 2434 var elem = _util.get.elements(val)[0]; 2435 if (elem && elem.parentNode) { 2436 val = elem; 2437 } else { 2438 throw ["Element defined in option \"triggerElement\" was not found:", val]; 2439 } 2440 } 2441 return val; 2442 }, 2443 triggerHook: function (val) { 2444 var translate = { 2445 "onCenter": 0.5, 2446 "onEnter": 1, 2447 "onLeave": 0 2448 }; 2449 if (_util.type.Number(val)) { 2450 val = Math.max(0, Math.min(parseFloat(val), 1)); // make sure its betweeen 0 and 1 2451 } else if (val in translate) { 2452 val = translate[val]; 2453 } else { 2454 throw ["Invalid value for option \"triggerHook\": ", val]; 2455 } 2456 return val; 2457 }, 2458 reverse: function (val) { 2459 return !!val; // force boolean 2460 }, 2461 loglevel: function (val) { 2462 val = parseInt(val); 2463 if (!_util.type.Number(val) || val < 0 || val > 3) { 2464 throw ["Invalid value for option \"loglevel\":", val]; 2465 } 2466 return val; 2467 } 2468 }, // holder for validation methods. duration validation is handled in 'getters-setters.js' 2469 shifts: ["duration", "offset", "triggerHook"], // list of options that trigger a `shift` event 2470 }; 2471 /* 2472 * method used to add an option to ScrollMagic Scenes. 2473 * TODO: DOC (private for dev) 2474 */ 2475 ScrollMagic.Scene.addOption = function (name, defaultValue, validationCallback, shifts) { 2476 if (!(name in SCENE_OPTIONS.defaults)) { 2477 SCENE_OPTIONS.defaults[name] = defaultValue; 2478 SCENE_OPTIONS.validate[name] = validationCallback; 2479 if (shifts) { 2480 SCENE_OPTIONS.shifts.push(name); 2481 } 2482 } else { 2483 ScrollMagic._util.log(1, "[static] ScrollMagic.Scene -> Cannot add Scene option '" + name + "', because it already exists."); 2484 } 2485 }; 2486 // instance extension function for plugins 2487 // TODO: DOC (private for dev) 2488 ScrollMagic.Scene.extend = function (extension) { 2489 var oldClass = this; 2490 ScrollMagic.Scene = function () { 2491 oldClass.apply(this, arguments); 2492 this.$super = _util.extend({}, this); // copy parent state 2493 return extension.apply(this, arguments) || this; 2494 }; 2495 _util.extend(ScrollMagic.Scene, oldClass); // copy properties 2496 ScrollMagic.Scene.prototype = oldClass.prototype; // copy prototype 2497 ScrollMagic.Scene.prototype.constructor = ScrollMagic.Scene; // restore constructor 2498 }; 2499 2500 2501 2502 /** 2503 * TODO: DOCS (private for dev) 2504 * @class 2505 * @private 2506 */ 2507 2508 ScrollMagic.Event = function (type, namespace, target, vars) { 2509 vars = vars || {}; 2510 for (var key in vars) { 2511 this[key] = vars[key]; 2512 } 2513 this.type = type; 2514 this.target = this.currentTarget = target; 2515 this.namespace = namespace || ''; 2516 this.timeStamp = this.timestamp = Date.now(); 2517 return this; 2518 }; 2519 2520 /* 2521 * TODO: DOCS (private for dev) 2522 */ 2523 2524 var _util = ScrollMagic._util = (function (window) { 2525 var U = {}, 2526 i; 2527 2528 /** 2529 * ------------------------------ 2530 * internal helpers 2531 * ------------------------------ 2532 */ 2533 2534 // parse float and fall back to 0. 2535 var floatval = function (number) { 2536 return parseFloat(number) || 0; 2537 }; 2538 // get current style IE safe (otherwise IE would return calculated values for 'auto') 2539 var _getComputedStyle = function (elem) { 2540 return elem.currentStyle ? elem.currentStyle : window.getComputedStyle(elem); 2541 }; 2542 2543 // get element dimension (width or height) 2544 var _dimension = function (which, elem, outer, includeMargin) { 2545 elem = (elem === document) ? window : elem; 2546 if (elem === window) { 2547 includeMargin = false; 2548 } else if (!_type.DomElement(elem)) { 2549 return 0; 2550 } 2551 which = which.charAt(0).toUpperCase() + which.substr(1).toLowerCase(); 2552 var dimension = (outer ? elem['offset' + which] || elem['outer' + which] : elem['client' + which] || elem['inner' + which]) || 0; 2553 if (outer && includeMargin) { 2554 var style = _getComputedStyle(elem); 2555 dimension += which === 'Height' ? floatval(style.marginTop) + floatval(style.marginBottom) : floatval(style.marginLeft) + floatval(style.marginRight); 2556 } 2557 return dimension; 2558 }; 2559 // converts 'margin-top' into 'marginTop' 2560 var _camelCase = function (str) { 2561 return str.replace(/^[^a-z]+([a-z])/g, '$1').replace(/-([a-z])/g, function (g) { 2562 return g[1].toUpperCase(); 2563 }); 2564 }; 2565 2566 /** 2567 * ------------------------------ 2568 * external helpers 2569 * ------------------------------ 2570 */ 2571 2572 // extend obj â same as jQuery.extend({}, objA, objB) 2573 U.extend = function (obj) { 2574 obj = obj || {}; 2575 for (i = 1; i < arguments.length; i++) { 2576 if (!arguments[i]) { 2577 continue; 2578 } 2579 for (var key in arguments[i]) { 2580 if (arguments[i].hasOwnProperty(key)) { 2581 obj[key] = arguments[i][key]; 2582 } 2583 } 2584 } 2585 return obj;
2586 }; 2587 2588 // check if a css display type results in margin-collapse or not 2589 U.isMarginCollapseType = function (str) { 2590 return ["block", "flex", "list-item", "table", "-webkit-box"].indexOf(str) > -1; 2591 }; 2592 2593 // implementation of requestAnimationFrame 2594 // based on https://gist.github.com/paulirish/1579671 2595 var 2596 lastTime = 0, 2597 vendors = ['ms', 'moz', 'webkit', 'o']; 2598 var _requestAnimationFrame = window.requestAnimationFrame; 2599 var _cancelAnimationFrame = window.cancelAnimationFrame; 2600 // try vendor prefixes if the above doesn't work 2601 for (i = 0; !_requestAnimationFrame && i < vendors.length; ++i) { 2602 _requestAnimationFrame = window[vendors[i] + 'RequestAnimationFrame']; 2603 _cancelAnimationFrame = window[vendors[i] + 'CancelAnimationFrame'] || window[vendors[i] + 'CancelRequestAnimationFrame']; 2604 } 2605 2606 // fallbacks 2607 if (!_requestAnimationFrame) { 2608 _requestAnimationFrame = function (callback) { 2609 var 2610 currTime = new Date().getTime(), 2611 timeToCall = Math.max(0, 16 - (currTime - lastTime)), 2612 id = window.setTimeout(function () { 2613 callback(currTime + timeToCall); 2614 }, timeToCall); 2615 lastTime = currTime + timeToCall; 2616 return id; 2617 }; 2618 } 2619 if (!_cancelAnimationFrame) { 2620 _cancelAnimationFrame = function (id) { 2621 window.clearTimeout(id); 2622 }; 2623 } 2624 U.rAF = _requestAnimationFrame.bind(window); 2625 U.cAF = _cancelAnimationFrame.bind(window); 2626 2627 var 2628 loglevels = ["error", "warn", "log"], 2629 console = window.console || {}; 2630 2631 console.log = console.log || function () {}; // no console log, well - do nothing then... 2632 // make sure methods for all levels exist. 2633 for (i = 0; i < loglevels.length; i++) { 2634 var method = loglevels[i]; 2635 if (!console[method]) { 2636 console[method] = console.log; // prefer .log over nothing 2637 } 2638 } 2639 U.log = function (loglevel) { 2640 if (loglevel > loglevels.length || loglevel <= 0) loglevel = loglevels.length; 2641 var now = new Date(), 2642 time = ("0" + now.getHours()).slice(-2) + ":" + ("0" + now.getMinutes()).slice(-2) + ":" + ("0" + now.getSeconds()).slice(-2) + ":" + ("00" + now.getMilliseconds()).slice(-3), 2643 method = loglevels[loglevel - 1], 2644 args = Array.prototype.splice.call(arguments, 1), 2645 func = Function.prototype.bind.call(console[method], console); 2646 args.unshift(time); 2647 func.apply(console, args); 2648 }; 2649 2650 /** 2651 * ------------------------------ 2652 * type testing 2653 * ------------------------------ 2654 */ 2655 2656 var _type = U.type = function (v) { 2657 return Object.prototype.toString.call(v).replace(/^\[object (.+)\]$/, "$1").toLowerCase(); 2658 }; 2659 _type.String = function (v) { 2660 return _type(v) === 'string'; 2661 }; 2662 _type.Function = function (v) { 2663 return _type(v) === 'function'; 2664 }; 2665 _type.Array = function (v) { 2666 return Array.isArray(v); 2667 }; 2668 _type.Number = function (v) { 2669 return !_type.Array(v) && (v - parseFloat(v) + 1) >= 0; 2670 }; 2671 _type.DomElement = function (o) { 2672 return ( 2673 typeof HTMLElement === "object" || typeof HTMLElement === "function" ? o instanceof HTMLElement || o instanceof SVGElement : //DOM2 2674 o && typeof o === "object" && o !== null && o.nodeType === 1 && typeof o.nodeName === "string" 2675 ); 2676 }; 2677 2678 /** 2679 * ------------------------------ 2680 * DOM Element info 2681 * ------------------------------ 2682 */ 2683 // always returns a list of matching DOM elements, from a selector, a DOM element or an list of elements or even an array of selectors 2684 var _get = U.get = {}; 2685 _get.elements = function (selector) { 2686 var arr = []; 2687 if (_type.String(selector)) { 2688 try { 2689 selector = document.querySelectorAll(selector); 2690 } catch (e) { // invalid selector 2691 return arr; 2692 } 2693 } 2694 if (_type(selector) === 'nodelist' || _type.Array(selector) || selector instanceof NodeList) { 2695 for (var i = 0, ref = arr.length = selector.length; i < ref; i++) { // list of elements 2696 var elem = selector[i]; 2697 arr[i] = _type.DomElement(elem) ? elem : _get.elements(elem); // if not an element, try to resolve recursively 2698 } 2699 } else if (_type.DomElement(selector) || selector === document || selector === window) { 2700 arr = [selector]; // only the element 2701 } 2702 return arr; 2703 }; 2704 // get scroll top value 2705 _get.scrollTop = function (elem) { 2706 return (elem && typeof elem.scrollTop === 'number') ? elem.scrollTop : window.pageYOffset || 0; 2707 }; 2708 // get scroll left value 2709 _get.scrollLeft = function (elem) { 2710 return (elem && typeof elem.scrollLeft === 'number') ? elem.scrollLeft : window.pageXOffset || 0; 2711 }; 2712 // get element height 2713 _get.width = function (elem, outer, includeMargin) { 2714 return _dimension('width', elem, outer, includeMargin); 2715 }; 2716 // get element width 2717 _get.height = function (elem, outer, includeMargin) { 2718 return _dimension('height', elem, outer, includeMargin); 2719 }; 2720 2721 // get element position (optionally relative to viewport) 2722 _get.offset = function (elem, relativeToViewport) { 2723 var offset = { 2724 top: 0, 2725 left: 0 2726 }; 2727 if (elem && elem.getBoundingClientRect) { // check if available 2728 var rect = elem.getBoundingClientRect(); 2729 offset.top = rect.top; 2730 offset.left = rect.left; 2731 if (!relativeToViewport) { // clientRect is by default relative to viewport... 2732 offset.top += _get.scrollTop(); 2733 offset.left += _get.scrollLeft(); 2734 } 2735 } 2736 return offset; 2737 }; 2738 2739 /** 2740 * ------------------------------ 2741 * DOM Element manipulation 2742 * ------------------------------ 2743 */ 2744 2745 U.addClass = function (elem, classname) { 2746 if (classname) { 2747 if (elem.classList) 2748 elem.classList.add(classname); 2749 else 2750 elem.className += ' ' + classname; 2751 } 2752 }; 2753 U.removeClass = function (elem, classname) { 2754 if (classname) { 2755 if (elem.classList) 2756 elem.classList.remove(classname); 2757 else 2758 elem.className = elem.className.replace(new RegExp('(^|\\b)' + classname.split(' ').join('|') + '(\\b|$)', 'gi'), ' '); 2759 } 2760 }; 2761 // if options is string -> returns css value 2762 // if options is array -> returns object with css value pairs 2763 // if options is object -> set new css values 2764 U.css = function (elem, options) { 2765 if (_type.String(options)) { 2766 return _getComputedStyle(elem)[_camelCase(options)]; 2767 } else if (_type.Array(options)) { 2768 var 2769 obj = {}, 2770 style = _getComputedStyle(elem); 2771 options.forEach(function (option, key) { 2772 obj[option] = style[_camelCase(option)]; 2773 }); 2774 return obj;
2775 } else { 2776 for (var option in options) { 2777 var val = options[option]; 2778 if (val == parseFloat(val)) { // assume pixel for seemingly numerical values 2779 val += 'px'; 2780 } 2781 elem.style[_camelCase(option)] = val; 2782 } 2783 } 2784 }; 2785 2786 return U; 2787 }(window || {})); 2788 2789 2790 ScrollMagic.Scene.prototype.addIndicators = function () { 2791 ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling addIndicators() due to missing Plugin \'debug.addIndicators\'. Please make sure to include plugins/debug.addIndicators.js'); 2792 return this; 2793 } 2794 ScrollMagic.Scene.prototype.removeIndicators = function () { 2795 ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling removeIndicators() due to missing Plugin \'debug.addIndicators\'. Please make sure to include plugins/debug.addIndicators.js'); 2796 return this; 2797 } 2798 ScrollMagic.Scene.prototype.setTween = function () { 2799 ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling setTween() due to missing Plugin \'animation.gsap\'. Please make sure to include plugins/animation.gsap.js'); 2800 return this; 2801 } 2802 ScrollMagic.Scene.prototype.removeTween = function () { 2803 ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling removeTween() due to missing Plugin \'animation.gsap\'. Please make sure to include plugins/animation.gsap.js'); 2804 return this; 2805 } 2806 ScrollMagic.Scene.prototype.setVelocity = function () { 2807 ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling setVelocity() due to missing Plugin \'animation.velocity\'. Please make sure to include plugins/animation.velocity.js'); 2808 return this; 2809 } 2810 ScrollMagic.Scene.prototype.removeVelocity = function () { 2811 ScrollMagic._util.log(1, '(ScrollMagic.Scene) -> ERROR calling removeVelocity() due to missing Plugin \'animation.velocity\'. Please make sure to include plugins/animation.velocity.js'); 2812 return this; 2813 } 2814 2815 return ScrollMagic; 2816}));
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.