Source: ScrollMagic/Scene/core.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
// (BUILD) - REMOVE IN MINIFY - START
/**
 * Send a debug message to the console.
 * @private
 * but provided publicly with _log for plugins
 *
 * @param {number} loglevel - The loglevel required to initiate output for the message.
 * @param {...mixed} output - One or more variables that should be passed to the console.
 */
var log = this._log = function (loglevel, output) {
    if (_options.loglevel >= loglevel) {
        Array.prototype.splice.call(arguments, 1, 0, "(" + NAMESPACE + ") ->");
        _util.log.apply(window, arguments);
    }
};
// (BUILD) - REMOVE IN MINIFY - END

/**
 * Add the scene to a controller.  
 * This is the equivalent to `Controller.addScene(scene)`.
 * @method ScrollMagic.Scene#addTo
 *
 * @example
 * // add a scene to a ScrollMagic Controller
 * scene.addTo(controller);
 *
 * @param {ScrollMagic.Controller} controller - The controller to which the scene should be added.
 * @returns {Scene} Parent object for chaining.
 */
this.addTo = function (controller) {
    if (!(controller instanceof ScrollMagic.Controller)) {
        log(1, "ERROR: supplied argument of 'addTo()' is not a valid ScrollMagic Controller");
    } else if (_controller != controller) {
        // new controller
        if (_controller) { // was associated to a different controller before, so remove it...
            _controller.removeScene(Scene);
        }
        _controller = controller;
        validateOption();
        updateDuration(true);
        updateTriggerElementPosition(true);
        updateScrollOffset();
        _controller.info("container").addEventListener('resize', onContainerResize);
        controller.addScene(Scene);
        Scene.trigger("add", {controller: _controller});
        log(3, "added " + NAMESPACE + " to controller");
        Scene.update();
    }
    return Scene;
};

/**
 * **Get** or **Set** the current enabled state of the scene.  
 * This can be used to disable this scene without removing or destroying it.
 * @method ScrollMagic.Scene#enabled
 *
 * @example
 * // get the current value
 * var enabled = scene.enabled();
 *
     * // disable the scene
 * scene.enabled(false);
 *
 * @param {boolean} [newState] - The new enabled state of the scene `true` or `false`.
 * @returns {(boolean|Scene)} Current enabled state or parent object for chaining.
 */
this.enabled = function (newState) {
    if (!arguments.length) { // get
        return _enabled;
    } else if (_enabled != newState) { // set
        _enabled = !!newState;
        Scene.update(true);
    }
    return Scene;
};

/**
 * Remove the scene from the controller.  
 * This is the equivalent to `Controller.removeScene(scene)`.
 * The scene will not be updated anymore until you readd it to a controller.
 * To remove the pin or the tween you need to call removeTween() or removePin() respectively.
 * @method ScrollMagic.Scene#remove
 * @example
 * // remove the scene from its controller
 * scene.remove();
 *
 * @returns {Scene} Parent object for chaining.
 */
this.remove = function () {
    if (_controller) {
        _controller.info("container").removeEventListener('resize', onContainerResize);
        var tmpParent = _controller;
        _controller = undefined;
        tmpParent.removeScene(Scene);
        Scene.trigger("remove");
        log(3, "removed " + NAMESPACE + " from controller");
    }
    return Scene;
};

/**
 * Destroy the scene and everything.
 * @method ScrollMagic.Scene#destroy
 * @example
 * // destroy the scene without resetting the pin and tween to their initial positions
 * scene = scene.destroy();
 *
 * // destroy the scene and reset the pin and tween
 * scene = scene.destroy(true);
 *
 * @param {boolean} [reset=false] - If `true` the pin and tween (if existent) will be reset.
 * @returns {null} Null to unset handler variables.
 */
this.destroy = function (reset) {
    Scene.trigger("destroy", {reset: reset});
    Scene.remove();
    Scene.off("*.*");
    log(3, "destroyed " + NAMESPACE + " (reset: " + (reset ? "true" : "false") + ")");
    return null;
};


/**
 * Updates the Scene to reflect the current state.  
 * This is the equivalent to `Controller.updateScene(scene, immediately)`.  
 * 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.  
 * 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.
 * 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.  
 * _**NOTE:** This method gets called constantly whenever ScrollMagic detects a change. The only application for you is if you change something outside of the realm of ScrollMagic, like moving the trigger or changing tween parameters._
 * @method ScrollMagic.Scene#update
 * @example
 * // update the scene on next tick
 * scene.update();
 *
 * // update the scene immediately
 * scene.update(true);
 *
 * @fires Scene.update
 *
 * @param {boolean} [immediately=false] - If `true` the update will be instant, if `false` it will wait until next update cycle (better performance).
 * @returns {Scene} Parent object for chaining.
 */
this.update = function (immediately) {
    if (_controller) {
        if (immediately) {
            if (_controller.enabled() && _enabled) {
                var
                    scrollPos = _controller.info("scrollPos"),
                    newProgress;

                if (_options.duration > 0) {
                    newProgress = (scrollPos - _scrollOffset.start)/(_scrollOffset.end - _scrollOffset.start);
                } else {
                    newProgress = scrollPos >= _scrollOffset.start ? 1 : 0;
                }

                Scene.trigger("update", {startPos: _scrollOffset.start, endPos: _scrollOffset.end, scrollPos: scrollPos});

                Scene.progress(newProgress);
            } else if (_pin && _state === "DURING") {
                updatePinState(true); // unpin in position
            }
        } else {
            _controller.updateScene(Scene, false);
        }
    }
    return Scene;
};

/**
 * Updates dynamic scene variables like the trigger element position or the duration.
 * This method is automatically called in regular intervals from the controller. See {@link ScrollMagic.Controller} option `refreshInterval`.
 * 
 * You can call it to minimize lag, for example when you intentionally change the position of the triggerElement.
 * If you don't it will simply be updated in the next refresh interval of the container, which is usually sufficient.
 *
 * @method ScrollMagic.Scene#refresh
 * @since 1.1.0
 * @example
 * scene = new ScrollMagic.Scene({triggerElement: "#trigger"});
 * 
 * // change the position of the trigger
 * $("#trigger").css("top", 500);
 * // immediately let the scene know of this change
 * scene.refresh();
 *
 * @fires {@link Scene.shift}, if the trigger element position or the duration changed
 * @fires {@link Scene.change}, if the duration changed
 *
 * @returns {Scene} Parent object for chaining.
 */
this.refresh = function () {
    updateDuration();
    updateTriggerElementPosition();
    // update trigger element position
    return Scene;
};

/**
 * **Get** or **Set** the scene's progress.  
 * Usually it shouldn't be necessary to use this as a setter, as it is set automatically by scene.update().  
 * The order in which the events are fired depends on the duration of the scene:
 *  1. Scenes with `duration == 0`:  
 *  Scenes that have no duration by definition have no ending. Thus the `end` event will never be fired.  
 *  When the trigger position of the scene is passed the events are always fired in this order:  
 *  `enter`, `start`, `progress` when scrolling forward  
 *  and  
 *  `progress`, `start`, `leave` when scrolling in reverse
 *  2. Scenes with `duration > 0`:  
 *  Scenes with a set duration have a defined start and end point.  
 *  When scrolling past the start position of the scene it will fire these events in this order:  
 *  `enter`, `start`, `progress`  
 *  When continuing to scroll and passing the end point it will fire these events:  
 *  `progress`, `end`, `leave`  
 *  When reversing through the end point these events are fired:  
 *  `enter`, `end`, `progress`  
 *  And when continuing to scroll past the start position in reverse it will fire:  
 *  `progress`, `start`, `leave`  
 *  In between start and end the `progress` event will be called constantly, whenever the progress changes.
 * 
 * In short:  
 * `enter` events will always trigger **before** the progress update and `leave` envents will trigger **after** the progress update.  
 * `start` and `end` will always trigger at their respective position.
 * 
 * Please review the event descriptions for details on the events and the event object that is passed to the callback.
 * 
 * @method ScrollMagic.Scene#progress
 * @example
 * // get the current scene progress
 * var progress = scene.progress();
 *
     * // set new scene progress
 * scene.progress(0.3);
 *
 * @fires {@link Scene.enter}, when used as setter
 * @fires {@link Scene.start}, when used as setter
 * @fires {@link Scene.progress}, when used as setter
 * @fires {@link Scene.end}, when used as setter
 * @fires {@link Scene.leave}, when used as setter
 *
 * @param {number} [progress] - The new progress value of the scene `[0-1]`.
 * @returns {number} `get` -  Current scene progress.
 * @returns {Scene} `set` -  Parent object for chaining.
 */
this.progress = function (progress) {
    if (!arguments.length) { // get
        return _progress;
    } else { // set
        var
            doUpdate = false,
            oldState = _state,
            scrollDirection = _controller ? _controller.info("scrollDirection") : 'PAUSED',
            reverseOrForward = _options.reverse || progress >= _progress;
        if (_options.duration === 0) {
            // zero duration scenes
            doUpdate = _progress != progress;
            _progress = progress < 1 && reverseOrForward ? 0 : 1;
            _state = _progress === 0 ? 'BEFORE' : 'DURING';
        } else {
            // scenes with start and end
            if (progress <= 0 && _state !== 'BEFORE' && reverseOrForward) {
                // go back to initial state
                _progress = 0;
                _state = 'BEFORE';
                doUpdate = true;
            } else if (progress > 0 && progress < 1 && reverseOrForward) {
                _progress = progress;
                _state = 'DURING';
                doUpdate = true;
            } else if (progress >= 1 && _state !== 'AFTER') {
                _progress = 1;
                _state = 'AFTER';
                doUpdate = true;
            } else if (_state === 'DURING' && !reverseOrForward) {
                updatePinState(); // in case we scrolled backwards mid-scene and reverse is disabled => update the pin position, so it doesn't move back as well.
            }
        }
        if (doUpdate) {
            // fire events
            var
                eventVars = {progress: _progress, state: _state, scrollDirection: scrollDirection},
                stateChanged = _state != oldState;

            var trigger = function (eventName) { // tmp helper to simplify code
                Scene.trigger(eventName, eventVars);
            };

            if (stateChanged) { // enter events
                if (oldState !== 'DURING') {
                    trigger("enter");
                    trigger(oldState === 'BEFORE' ? "start" : "end");
                }
            }
            trigger("progress");
            if (stateChanged) { // leave events
                if (_state !== 'DURING') {
                    trigger(_state === 'BEFORE' ? "start" : "end");
                    trigger("leave");
                }
            }
        }

        return Scene;
    }
};