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;
}
};