/**
@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@ @@@@@@@@
@@@@@@@ @@@@@@@
@@@@@@@ @@@@@@@
@@@@@@@ @@@@@@@
@@@@@@@@ @ @@@@@@@@
@@@@@@@@@ @@@ @@@@@@@@@
@@@@@@@@@@@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@ @@@@@@@
@@@@@@@@@@@@ @@@
@@@@@@
@@@@
@@
*
* jQuery Reel
* ===========
* The 360° plugin for jQuery
*
* @license Copyright (c) 2009-2013 Petr Vostrel (http://petr.vostrel.cz/)
* Licensed under the MIT License (LICENSE.txt).
*
* jQuery Reel
* http://reel360.org
* Version: 1.3.0
* Updated: 2013-11-04
*
* Requires jQuery 1.6.2 or higher
*/
/*
* CDN
* ---
* - http://code.vostrel.net/jquery.reel-bundle.js (recommended)
* - http://code.vostrel.net/jquery.reel.js
* - http://code.vostrel.net/jquery.reel-debug.js
* - or http://code.vostrel.net/jquery.reel-edge.js if you feel like it ;)
*
* Optional Plugins
* ----------------
* - jQuery.mouseWheel [B] (Brandon Aaron, http://plugins.jquery.com/project/mousewheel)
* - or jQuery.event.special.wheel (Three Dub Media, http://blog.threedubmedia.com/2008/08/eventspecialwheel.html)
*
* [B] Marked plugins are contained (with permissions) in the "bundle" version from the CDN
*/
(function(factory){
// -----------------------
// [NEW] AMD Compatibility
// -----------------------
//
// Reel registers as an asynchronous module with dependency on jQuery for [AMD][1] compatible script loaders.
// Besides that it also complies with [CommonJS][2] module definition for Node and such.
// Of course, no fancy script loader is necessary and good old plain script tag still works too.
//
// [1]:http://en.wikipedia.org/wiki/Asynchronous_module_definition
// [2]:http://en.wikipedia.org/wiki/CommonJS
//
var
amd= typeof define == 'function' && define.amd && (define(['jquery'], factory) || true),
commonjs= !amd && typeof module == 'object' && typeof module.exports == 'object' && (module.exports= factory),
plain= !amd && !commonjs && factory()
})(function(){ return jQuery.reel || (function($, window, document, undefined){
// ------
// jQuery
// ------
//
// One vital requirement is the correct jQuery. Reel requires at least version 1.6.2
// and a make sure check is made at the very beginning.
//
if (!$) return;
var
version= $ && $().jquery.split(/\./)
if (!version || +(twochar(version[0])+twochar(version[1])+twochar(version[2] || '')) < 10602)
return error('Too old jQuery library. Please upgrade your jQuery to version 1.6.2 or higher');
// ----------------
// Global Namespace
// ----------------
//
// `$.reel` (or `jQuery.reel`) namespace is provided for storage of all Reel belongings.
// It is locally referenced as just `reel` for speedier access.
//
var
reel= $.reel= {
// ### `$.reel.version`
//
// `String` (major.minor.patch), since 1.1
//
version: '1.3.0',
// Options
// -------
//
// When calling `.reel()` method you have plenty of options (far too many) available.
// You collect them into one hash and supply them with your call.
//
// _**Example:** Initiate a non-looping Reel with 12 frames:_
//
// .reel({
// frames: 12,
// looping: false
// })
//
//
// All options are optional and if omitted, default value is used instead.
// Defaults are being housed as members of `$.reel.def` hash.
// If you customize any default value therein, all subsequent `.reel()` calls
// will use the new value as default.
//
// _**Example:** Change default initial frame to 5th:_
//
// $.reel.def.frame = 5
//
// ---
// ### `$.reel.def` ######
// `Object`, since 1.1
//
def: {
//
// ### Basic Definition ######
//
// Reel is just fine with you not setting any options, however if you don't have
// 36 frames or beginning at frame 1, you will want to set total number
// of `frames` and pick a different starting `frame`.
//
// ---
// #### `frame` Option ####
// `Number` (frames), since 1.0
//
frame: 1,
// #### `frames` Option ####
// `Number` (frames), since 1.0
//
frames: 36,
// ~~~
//
// Another common characteristics of any Reel is whether it `loops` and covers
// entire 360° or not.
//
// ---
// #### `loops` Option ####
// `Boolean`, since 1.0
//
loops: true,
// ### Interaction ######
//
// Using boolean switches many user interaction aspects can be turned on and off.
// You can disable the mouse wheel control with `wheelable`, the drag & throw
// action with `throwable`, disallow the dragging completely with `draggable`,
// on touch devices you can disable the browser's decision to scroll the page
// instead of Reel script and you can of course disable the stepping of Reel by
// clicking on either half of the image with `steppable`.
//
// You can even enable `clickfree` operation,
// which will cause Reel to bind to mouse enter/leave events instead of mouse down/up,
// thus allowing a click-free dragging.
//
// ---
// #### `clickfree` Option ####
// `Boolean`, since 1.1
//
clickfree: false,
// #### `draggable` Option ####
// `Boolean`, since 1.1
//
draggable: true,
// #### `scrollable` Option ####
// `Boolean`, since 1.2
//
scrollable: true,
// #### `steppable` Option ####
// `Boolean`, since 1.2
//
steppable: true,
// #### `throwable` Option ####
// `Boolean`, since 1.1; or `Number`, since 1.2.1
//
throwable: true,
// #### `wheelable` Option ####
// `Boolean`, since 1.1
//
wheelable: true,
// ### [NEW] Gyroscope Support ######
//
// When enabled allows gyro-enabled devices (iPad2 for example) to control rotational
// position using the device's attitude in space. In this more, Reel directly maps the
// 360° range of your gyro's primary (alpha) axis directly to the value of `fraction`.
//
// #### `orientable` Option ####
// [NEW] `Boolean`, since 1.3
//
orientable: false,
// ### Order of Images ######
//
// Reel presumes counter-clockwise order of the pictures taken. If the nearer facing
// side doesn't follow your cursor/finger, you did clockwise. Use the `cw` option to
// correct this.
//
// ---
// #### `cw` Option ####
// `Boolean`, since 1.1
//
cw: false,
// ### Sensitivity ######
//
// In Reel sensitivity is set through the `revolution` parameter, which represents horizontal
// dragging distance one must cover to perform one full revolution. By default this value
// is calculated based on the setup you have - it is either twice the width of the image
// or half the width of stitched panorama. You may also set your own.
//
// Optionally `revolution` can be set as an Object with `x` member for horizontal revolution
// and/or `y` member for vertical revolution in multi-row movies.
//
// ---
// #### `revolution` Option ####
// `Number` (pixels) or `Object`, since 1.1, `Object` support since 1.2
//
revolution: undefined,
// ### Rectilinear Panorama ######
//
// The easiest of all is the stitched panorama mode. For this mode, instead of the sprite,
// a single seamlessly stitched stretched image is used and the view scrolls the image.
// This mode is triggered by setting a pixel width of the `stitched` image.
//
// ---
// #### `stitched` Option ####
// `Number` (pixels), since 1.0
//
stitched: 0,
// ### Directional Mode ######
//
// As you may have noticed on Reel's homepage or in [`example/object-movie-directional-sprite`][1]
// when you drag the arrow will point to either direction. In such `directional` mode, the sprite
// is actually 2 in 1 - one file contains two sprites one tightly following the other, one
// for visually going one way (`A`) and one for the other (`B`).
//
// A01 A02 A03 A04 A05 A06
// A07 A08 A09 A10 A11 A12
// A13 A14 A15 B01 B02 B03
// B04 B05 B06 B07 B08 B09
// B10 B11 B12 B13 B14 B15
//
// Switching between `A` and `B` frames is based on direction of the drag. Directional mode isn't
// limited to sprites only, sequences also apply. The figure below shows the very same setup like
// the above figure, only translated into actual frames of the sequence.
//
// 001 002 003 004 005 006
// 007 008 009 010 011 012
// 013 014 015 016 017 018
// 019 020 021 022 023 024
// 025 026 027 028 029 030
//
// Frame `016` represents the `B01` so it actually is first frame of the other direction.
//
// [1]:../example/object-movie-directional-sprite/
//
// ---
// #### `directional` Option ####
// `Boolean`, since 1.1
//
directional: false,
// ### Multi-Row Mode ######
//
// As [`example/object-movie-multirow-sequence`][1] very nicely demonstrates, in multi-row arrangement
// you can perform two-axis manipulation allowing you to add one or more vertical angles. Think of it as
// a layered cake, each new elevation of the camera during shooting creates one layer of the cake -
// - a _row_. One plain horizontal object movie full spin is one row:
//
// A01 A02 A03 A04 A05 A06
// A07 A08 A09 A10 A11 A12
// A13 A14 A15
//
// Second row tightly follows after the first one:
//
// A01 A02 A03 A04 A05 A06
// A07 A08 A09 A10 A11 A12
// A13 A14 A15 B01 B02 B03
// B04 B05 B06 B07 B08 B09
// B10 B11 B12 B13 B14 B15
// C01...
//
// This way you stack up any number of __`rows`__ you wish and set the initial `row` to start with.
// Again, not limited to sprites, sequences also apply.
//
// [1]:../example/object-movie-multirow-sequence/
//
// ---
// #### `row` Option ####
// `Number` (rows), since 1.1
//
row: 1,
// #### `rows` Option ####
// `Number` (rows), since 1.1
//
rows: 0,
// ### [NEW] Multi-Row Locks ######
//
// Optionally you can apply a lock on either of the two axes with `rowlock` and/or `framelock`.
// That will disable direct mouse interaction and will leave using of `.reel()` the only way
// of changing position on the locked axis.
//
// ---
// #### `rowlock` Option ####
// [NEW] `Boolean`, since 1.3
//
rowlock: false,
// #### `framelock` Option ####
// [NEW] `Boolean`, since 1.3
//
framelock: false,
// ### Dual-Orbit Mode ######
//
// Special form of multi-axis movie is the dual-axis mode. In this mode the object offers two plain
// spins - horizontal and vertical orbits combined together crossing each other at the `frame`
// forming sort of a cross if envisioned. [`example/object-movie-dual-orbit-sequence`][1] demonstrates
// this setup. When the phone in the example is facing you (marked in the example with green square
// in the top right), you are at the center. That is within the distance (in frames) defined
// by the `orbital` option. Translation from horizontal to vertical orbit can be achieved on this sweet-spot.
// By default horizontal orbit is chosen first, unless `vertical` option is used against.
//
// In case the image doesn't follow the vertical drag, you may have your vertical orbit `inversed`.
//
// Technically it is just a two-layer movie:
//
// A01 A02 A03 A04 A05 A06
// A07 A08 A09 A10 A11 A12
// A13 A14 A15 B01 B02 B03
// B04 B05 B06 B07 B08 B09
// B10 B11 B12 B13 B14 B15
//
// [1]:../example/object-movie-dual-orbit-sequence/
//
// ---
// #### `orbital` Option ####
// `Number` (frames), since 1.1
//
orbital: 0,
// #### `vertical` Option ####
// `Boolean`, since 1.1
//
vertical: false,
// #### `inversed` Option ####
// `Boolean`, since 1.1
//
inversed: false,
// ### Sprite Layout ######
//
// For both object movies and panoramas Reel presumes you use a combined _Sprite_ to hold all your
// frames in a single file. This powerful technique of using a sheet of several individual images
// has many advantages in terms of compactness, loading, caching, etc. However, know your enemy,
// be also aware of the limitations, which stem from memory limits of mobile
// (learn more in [FAQ](https://github.com/pisi/Reel/wiki/FAQ)).
//
// Inside the sprite, individual frames are laid down one by one, to the right of the previous one
// in a straight _Line_:
//
// 01 02 03 04 05 06
// 07...
//
// Horizontal length of the line is reffered to as `footage`. Unless frames `spacing` in pixels
// is set, edges of frames must touch.
//
// 01 02 03 04 05 06
// 07 08 09 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
//
// This is what you'll get by calling `.reel()` without any options. All frames laid out 6 in line.
// By default nicely even 6 x 6 grid like, which also inherits the aspect ratio of your frames.
//
// By setting `horizontal` to `false`, instead of forming lines, frames are expected to form
// _Columns_. All starts at the top left corner in both cases.
//
// 01 07 13 19 25 31
// 02 08 14 20 26 32
// 03 09 15 21 27 33
// 04 10 16 22 28 34
// 05 11 17 23 29 35
// 06 12 18 24 30 36
//
// URL for the sprite image file is being build from the name of the original `` `src` image
// by adding a `suffix` to it. By default this results in `"object-reel.jpg"` for `"object.jpg"`.
// You can also take full control over the sprite `image` URL that will be used.
//
// ---
// #### `footage` Option ####
// `Number` (frames), since 1.0
//
footage: 6,
// #### `spacing` Option ####
// `Number` (pixels), since 1.0
//
spacing: 0,
// #### `horizontal` Option ####
// `Boolean`, since 1.0
//
horizontal: true,
// #### `suffix` Option ####
// `String`, since 1.0
//
suffix: '-reel',
// #### `image` Option ####
// `String`, since 1.1
//
image: undefined,
// ### Sequence ######
//
// Collection of individual frame images is called _Sequence_ and it this way one HTTP request per
// frame is made carried out as opposed to sprite with one request per entire sprite. Define it with
// string like: `"image_###.jpg"`. The `#` placeholders will be replaced with a numeric +1 counter
// padded to the placeholders length.
// Learn more about [sequences](Sequences).
//
// In case you work with hashed filenames like `64bc654d21cb.jpg`, where no counter element can
// be indentified, or you prefer direct control, `images` can also accept array of plain URL strings.
//
// All images are retrieved from a specified `path`.
//
// ---
// #### `images` Option ####
// [IMPROVED] `String` or `Array`, since 1.1
//
images: '',
// #### `path` Option ####
// `String` (URL path), since 1.1
//
path: '',
// ### Images Preload Order ######
//
// Given sequence images can be additionally reordered to achieve a perceived faster preloading.
// Value given to `preload` option must match a name of a pre-registered function within
// `$.reel.preload` object. There are two functions built-in:
//
// - `"fidelity"` - non-linear way that ensures even spreading of preloaded images around the entire
// revolution leaving the gaps in-between as small as possible. This results in a gradually
// increasing fidelity of the image rather than having one large shrinking gap. This is the default
// behavior.
// - `"linear"` - linear order of preloading
//
// ---
// #### `preload` Option ####
// `String`, since 1.2
//
preload: 'fidelity',
// ### [NEW] Shy Initialization ######
//
// Sometimes, on-demand activation is desirable in order to conserve device resources or bandwidth
// especially with multiple instances on a single page. If so, enable _shy mode_, in which Reel will
// hold up the initialization process until the image is clicked by the user. Alternativelly you can
// initialize shy instance by triggering `"setup"` event.
//
// ---
// #### `shy` Option ####
// [NEW] `Boolean`, since 1.3
//
shy: false,
// ### Animation ######
//
// Your object movie or a panorama can perform an autonomous sustained motion in one direction.
// When `speed` is set in revolutions per second (Hz), after a given `delay` the instance will
// animate and advance frames by itself.
//
// t
// |-------›|-----------›
// Delay Animation
//
// Start and resume of animation happens when given `timeout` has elapsed since user became idle.
//
// t
// |-----------›|= == == = === = = | |-----------›
// Animation User interaction Timeout Animation
//
// When a scene doesn't loop (see [`loops`](#loops-Option)) and the animation reaches one end,
// it stays there for a while and then reversing the direction of the animation it bounces back
// towards the other end. The time spent on the edge can be customized with `rebound`.
//
// ---
// #### `speed` Option ####
// `Number` (Hz), since 1.1
//
speed: 0,
// #### `delay` Option ####
// `Number` (seconds), since 1.1
//
delay: 0,
// #### `timeout` Option ####
// `Number` (seconds), since 1.1
//
timeout: 2,
// #### `duration` Option ####
// `Number` (seconds), since 1.3
//
duration: undefined,
// #### `rebound` Option ####
// `Number` (seconds), since 1.1
//
rebound: 0.5,
// ### Opening Animation ######
//
// Chance is you want the object to spin a little to attract attention and then stop and wait
// for the user to engage. This is called "opening animation" and it is performed for given number
// of seconds (`opening`) at dedicated `entry` speed. The `entry` speed defaults to value of `speed`
// option. After the opening animation has passed, regular animation procedure begins starting with
// the delay (if any).
//
// t
// |--------›|-------›|-----------›
// Opening Delay Animation
//
// ---
// #### `entry` Option ####
// `Number` (Hz), since 1.1
//
entry: undefined,
// #### `opening` Option ####
// `Number` (seconds), since 1.1
//
opening: 0,
// ### Momentum ######
//
// Often also called inertial motion is a result of measuring a velocity of dragging. This velocity
// builds up momentum, so when a drag is released, the image still retains the momentum and continues
// to spin on itself. Naturally the momentum soon wears off as `brake` is being applied.
//
// One can utilize this momentum for a different kind of an opening animation. By setting initial
// `velocity`, the instance gets artificial momentum and spins to slow down to stop.
//
// ---
// #### `brake` Option ####
// `Number`, since 1.1, where it also has a different default `0.5`
//
brake: 0.23,
// #### `velocity` Option ####
// `Number`, since 1.2
//
velocity: 0,
// ### Ticker ######
//
// For purposes of animation, Reel starts and maintains a timer device which emits ticks timing all
// animations. There is only one ticker running in the document and all instances subscribe to this
// one ticker. Ticker is equipped with a mechanism, which compensates for the measured costs
// of running Reels to ensure the ticker ticks on time. The `tempo` (in Hz) of the ticker can be
// specified.
//
// Please note, that ticker is synchronized with a _leader_, the oldest living instance on page,
// and adjusts to his tempo.
//
// ---
// #### `tempo` Option ####
// `Number` (Hz, ticks per second), since 1.1
//
tempo: 36,
// ~~~
//
// Since many mobile devices are sometimes considerably underpowered in comparison with desktops,
// they often can keep up with the 36 Hz rhythm. In Reel these are called __lazy devices__
// and everything mobile qualifies as lazy for the sake of the battery and interaction fluency.
// The ticker is under-clocked for them by a `laziness` factor, which is used to divide the `tempo`.
// Default `laziness` of `6` will effectively use 6 Hz instead (6 = 36 / 6) on lazy devices.
//
// ---
// #### `laziness` Option ####
// `Number`, since 1.1
//
laziness: 6,
// ### Customization ######
//
// You can customize Reel on both functional and visual front. The most visible thing you can
// customize is probably the `cursor`, size of the `preloader`, perhaps add visual `indicator`(s)
// of Reel's position within the range. You can also set custom `hint` for the tooltip, which appears
// when you mouse over the image area. Last but not least you can also add custom class name `klass`
// to the instance.
//
// ---
// #### `cursor` Option ####
// `String`, since 1.2
//
cursor: undefined,
// #### `hint` Option ####
// `String`, since 1.0
//
hint: '',
// #### `indicator` Option ####
// `Number` (pixels), since 1.0
//
indicator: 0,
// #### `klass` Option ####
// `String`, since 1.0
//
klass: '',
// #### `preloader` Option ####
// `Number` (pixels), since 1.1
//
preloader: 2,
// ~~~
//
// You can use custom attributes (`attr`) on the node - it accepts the same name-value pairs object
// jQuery `.attr()` does. In case you want to delegate full interaction control over the instance
// to some other DOM element(s) on page, you can with `area`.
//
// ---
// #### `area` Option ####
// `jQuery`, since 1.1
//
area: undefined,
// #### `attr` Option ####
// `Object`, since 1.2
//
attr: {},
// ### Annotations ######
//
// To further visually describe your scene you can place all kinds of in-picture HTML annotations
// by defining an `annotations` object. Learn more about [Annotations][1] in a dedicated article.
//
// [1]:https://github.com/pisi/Reel/wiki/Annotations
//
// ---
// #### `annotations` Option ####
// `Object`, since 1.2
//
annotations: undefined,
// ### [NEW] Responsiveness ######
//
// By default, dimensions of Reel are fixed and pixel-match the dimensions of the original image
// and the responsive mode is disabled. Using `responsive` option you can enable responsiveness.
// In such a case Reel will adopt dimensions of its parent container element and scale all relevant
// data store values accordingly.
// The scale applied is stored in `"ratio"` data key, where `1.0` means 100% or no scale.
//
// To take full advantage of this, you can setup your URLs to contain actual dimensions and
// serve images in appropriate detail.
// Learn more about [data values in URLs](#Data-Values-in-URLs).
//
// ---
// #### `responsive` Option ####
// [NEW] `Boolean`, since 1.3
//
responsive: false,
// ### Mathematics ######
//
// When reeling, instance conforms to a graph function, which defines whether it will loop
// (`$.reel.math.hatch`) or it won't (`$.reel.math.envelope`). My math is far from flawless
// and I'm sure there are much better ways how to handle things. the `graph` option is there for you
// shall you need it. It accepts a function, which should process given criteria and return
// a fraction of 1.
//
// function( x, start, revolution, lo, hi, cwness, y ){
// return fraction // 0.0 - 1.0
// }
//
// ---
// #### `graph` Option ####
// `Function`, since 1.1
//
graph: undefined,
// ### Monitor ######
//
// Specify a string data key and you will see its real-time value dumped in the upper-left corner
// of the viewport. Its visual can be customized by CSS using `.jquery-reel-monitor` selector.
//
// ---
// #### `monitor` Option ####
// `String` (data key), since 1.1
//
monitor: undefined
},
// -----------------
// [NEW] Quick Start
// -----------------
//
// For basic Reel initialization, you don't even need to write any Javascript!
// All it takes is to add __class name__ `"reel"` to your `` HTML tag,
// assign an unique __`id` attribute__ and decorate the tag with configuration __data attributes__.
// Result of which will be interactive Reel projection.
//
//
//
// All otherwise Javascript [options](#Options) are made available as HTML `data-*` attributes.
//
// Only the `annotations` option doesn't work this way. To quickly create annotations,
// simply have any HTML node (`
` prefferably) anywhere in the DOM,
// assign it __class name__ `"reel-annotation"`, an unique __`id` attribute__
// and add configuration __data attributes__.
//
//
// Whatever HTML I'd like to have in the annotation
//
//
// Most important is the `data-for` attribute, which references target Reel instance by `id`.
//
// ---
//
// Responsible for discovery and subsequent conversion of data-configured Reel images is
// `$.reel.scan()` method, which is being called automagically when the DOM becomes ready.
// Under normal circumstances you don't need to scan by yourself.
//
// It however comes in handy to re-scan when you happen to inject a data-configured Reel `
`
// into already ready DOM.
//
// ---
// ### `$.reel.scan()` Method ######
// [NEW] returns `jQuery`, since 1.3
//
scan: function(){
return $(dot(klass)+':not('+dot(overlay_klass)+' > '+dot(klass)+')').each(function(ix, image){
var
$image= $(image),
options= $image.data(),
images= options.images= soft_array(options.images),
annotations= {}
$(dot(annotation_klass)+'[data-for='+$image.attr(_id_)+']').each(function(ix, annotation){
var
$annotation= $(annotation),
def= $annotation.data(),
x= def.x= numerize_array(soft_array(def.x)),
y= def.y= numerize_array(soft_array(def.y)),
id= $annotation.attr(_id_),
node= def.node= $annotation.removeData()
annotations[id] = def;
});
options.annotations = annotations;
$image.removeData().reel(options);
});
},
// -------
// Methods
// -------
//
// Reel's methods extend jQuery core functions with members of its `$.reel.fn` object. Despite Reel
// being a typical one-method plug-in with its `.reel()` function, for convenience it also offers
// its bipolar twin `.unreel()`.
//
// ---
// ### `$.reel.fn` ######
// returns `Object`, since 1.1
//
fn: {
// ------------
// Construction
// ------------
//
// `.reel()` method is the core of Reel and similar to some jQuery functions, it has adaptive interface.
// It either builds, [reads & writes data](#Data) or [causes events](#Control-Events).
//
// ---
// ### `.reel( [options] )` Method ######
// returns `jQuery`, since 1.0
//
reel: function(){
var
args= arguments,
t= $(this),
data= t.data(),
name= args[0] || {},
value= args[1]
// The main [core of this procedure](#Construction-Core) is rather bulky, so let's skip it for now
// and instead let me introduce the other uses first.
// --------------------
// [NEW] Control Events
// --------------------
//
// [Event][1] messages are what ties and binds all Reel's internal working components together.
// Besides being able to binding to any of these events from your script and react on Reel status changes
// (e.g. position), you can also trigger some of them in order to control Reel's attitude.
//
// You can:
//
// * control the playback of animated Reels with [`play`](#play-Event), [`pause`](#pause-Event)
// or [`stop`](#stop-Event)
// * step the Reel in any direction with [`stepRight`](#stepRight-Event), [`stepLeft`](#stepLeft-Event),
// [`stepUp`](#stepUp-Event), [`stepDown`](#stepDown-Event),
// * reach certain frame with [`reach`](#reach-Event)
//
// Triggering Reel's control event is as simple as passing the desired event name to `.reel()`.
//
// _**Example:** Stop the animation in progress:_
//
// .reel(':stop')
//
// Think of `.reel()` as a convenient shortcut to and synonym for [`.trigger()`][2], only prefix
// the event name with `:`. Of course you can use simple `.trigger()` instead and without the colon.
//
//
// [1]:http://api.jquery.com/category/events/event-object/
// [2]:http://api.jquery.com/trigger
//
// ---
// #### `.reel( event, [arguments] )` ######
// returns `jQuery`, since 1.3
//
if (typeof name != 'object'){
if (name.slice(0, 1) == ':'){
return t.trigger(name.slice(1), value);
}
// ----
// Data
// ----
//
// Reel stores all its inner state values with the standard DOM [data interface][1] interface
// while adding an additional change-detecting event layer, which makes Reel entirely data-driven.
//
// [1]:http://api.jquery.com/data
//
// _**Example:** Find out on what frame a Reel instance currently is:_
//
// .reel('frame') // Returns the frame number
//
// This time think of `.reel(data)` as a synonym for `.data()`. Note, that you can therefore easily
// inspect the entire datastore with `.data()` (without arguments). Use it for debugging only.
// For real-time data watch use [`monitor`](#Monitor) option instead of manually hooking into
// the data.
//
// ---
// #### `.reel( data )` ######
// can return anything, since 1.2
//
else{
if (args.length == 1){
return data[name]
}
// ### Write Access ###
//
// You can store any value the very same way by passing the value as the second function
// argument.
//
// _**Example:** Jump to frame 12:_
//
// .reel('frame', 12)
//
// Only a handful of data keys is suitable for external manipulation. These include `area`,
// `backwards`, `brake`, __`fraction`__, __`frame`__, `playing`, `reeling`, __`row`__, `speed`,
// `stopped`, `velocity` and `vertical`. Use the rest of the keys for reading only, you can
// mess up easily changing them.
//
// ---
// #### `.reel( data, value )` ######
// returns `jQuery`, since 1.2
//
else{
if (value !== undefined){
reel.normal[name] && (value= reel.normal[name](value, data));
// ### Changes ######
//
// Any value that does not equal (`===`) the old value is considered _new value_ and
// in such a case Reel will trigger a _change event_ to announce the change. The event
// type takes form of _`key`_`Change`, where _`key`_ will be the data key/name you've
// just assigned.
//
// _**Example:** Setting `"frame"` to `12` in the above example will trigger
// `"frameChange"`._
//
// Some of these _change events_ (like `frame` or `fraction`) have a
// default handler attached.
//
// You can easily bind to any of the data key change with standard event
// binding methods.
//
// _**Example:** React on instance being manipulated or not:_
//
// .bind('reelingChange', function(evnt, nothing, reeling){
// if (reeling) console.log('Rock & reel!')
// else console.log('Not reeling...')
// })
//
// ---
// The handler function will be executed every time the value changes and
// it will be supplied with three arguments. First one is the event object
// as usual, second is `undefined` and the third will be the actual value.
// In this case it was a boolean type value.
// If the second argument is not `undefined` it is the backward compatible
// "before" event triggered from outside Reel.
//
if (data[name] === undefined) data[name]= value
else if (data[name] !== value) t.trigger(name+'Change', [ undefined, data[name]= value ]);
}
return t
}
}
}
//
// -----------------
// Construction Core
// -----------------
//
// Now, back to the procedure of [constructing](#Construction) a Reel instance
// and binding its event handlers.
//
// Establish local `opt` object made by extending the defaults.
//
else{
var
opt= $.extend({}, reel.def, name),
// Limit the given jQuery collection to just `
` tags with `src` attribute
// and dimensions defined.
applicable= t.filter(_img_).unreel().filter(function(){
var
$this= $(this),
attr= opt.attr,
src= attr.src || $this.attr(_src_),
width= attr.width || $this.attr(_height_) || $this.width(),
height= attr.height || $this.attr(_width_) || $this.height()
if (!src) return error('`src` attribute missing on target image');
if (!width || !height) return error('Dimension(s) of the target image unknown');
return true;
}),
instances= []
applicable.each(function(){
var
t= $(this),
// Quick data interface
set= function(name, value){ return t.reel(name, value) && get(name) },
get= function(name){ return t.data(name) },
on= {
// --------------
// Initialization
// --------------
//
// This internally called private pseudo-handler:
//
// - initiates all data store keys,
// - binds to ticker
// - and triggers `"setup"` Event when finished.
//
setup: function(e){
if (t.hasClass(klass) && t.parent().hasClass(overlay_klass)) return;
set(_options_, opt);
var
attr= {
src: t.attr(_src_),
width: t.attr(_width_) || null,
height: t.attr(_height_) || null,
style: t.attr(_style_) || null,
'class': t.attr(_class_) || null
},
src= t.attr(opt.attr).attr(_src_),
id= set(_id_, t.attr(_id_) || t.attr(_id_, klass+'-'+(+new Date())).attr(_id_)),
data= $.extend({}, t.data()),
images= set(_images_, opt.images || []),
stitched= set(_stitched_, opt.stitched),
is_sprite= !images.length || stitched,
responsive= set(_responsive_, opt.responsive && (knows_background_size ? true : !is_sprite)),
truescale= set(_truescale_, {}),
loops= opt.loops,
orbital= opt.orbital,
revolution= opt.revolution,
rows= opt.rows,
footage= set(_footage_, min(opt.footage, opt.frames)),
spacing= set(_spacing_, opt.spacing),
width= set(_width_, +t.attr(_width_) || t.width()),
height= set(_height_, +t.attr(_height_) || t.height()),
shy= set(_shy_, opt.shy),
frames= set(_frames_, orbital && footage || rows <= 1 && images.length || opt.frames),
multirow= rows > 1 || orbital,
revolution_x= set(_revolution_, axis('x', revolution) || stitched / 2 || width * 2),
revolution_y= set(_revolution_y_, !multirow ? 0 : (axis('y', revolution) || (rows > 3 ? height : height / (5 - rows)))),
rows= stitched ? 1 : ceil(frames / footage),
stitched_travel= set(_stitched_travel_, stitched - (loops ? 0 : width)),
stitched_shift= set(_stitched_shift_, 0),
stage_id= hash(id+opt.suffix),
img_class= t.attr(_class_),
classes= !img_class ? __ : img_class+___,
$overlay= $(tag(_div_), { id: stage_id.substr(1), 'class': classes+___+overlay_klass+___+frame_klass+'0' }),
$instance= t.wrap($overlay.addClass(opt.klass)).addClass(klass),
instances_count= instances.push(add_instance($instance)[0]),
$overlay= $instance.parent().bind(on.instance)
set(_image_, images.length ? __ : opt.image || src.replace(reel.re.image, '$1' + opt.suffix + '.$2'));
set(_cache_, $(tag(_div_), { 'class': cache_klass }).appendTo('body'));
set(_area_, $()),
set(_cached_, []);
set(_frame_, null);
set(_fraction_, null);
set(_row_, opt.row);
set(_tier_, 0);
set(_rows_, rows);
set(_rowlock_, opt.rowlock);
set(_framelock_, opt.framelock);
set(_departure_, set(_destination_, set(_distance_, 0)));
set(_bit_, 1 / frames);
set(_stage_, stage_id);
set(_backwards_, set(_speed_, opt.speed) < 0);
set(_loading_, false);
set(_velocity_, 0);
set(_vertical_, opt.vertical);
set(_preloaded_, 0);
set(_cwish_, negative_when(1, !opt.cw && !stitched));
set(_clicked_location_, {});
set(_clicked_, false);
set(_clicked_on_, set(_clicked_tier_, 0));
set(_lo_, set(_hi_, 0));
set(_reeling_, false);
set(_reeled_, false);
set(_opening_, false);
set(_brake_, opt.brake);
set(_center_, !!orbital);
set(_tempo_, opt.tempo / (reel.lazy? opt.laziness : 1));
set(_opening_ticks_, -1);
set(_ticks_, -1);
set(_annotations_, opt.annotations || $overlay.unbind(dot(_annotations_)) && {});
set(_ratio_, 1);
set(_backup_, {
attr: attr,
data: data
});
opt.steppable || $overlay.unbind('up.steppable');
opt.indicator || $overlay.unbind('.indicator');
css(__, { overflow: _hidden_, position: 'relative' });
responsive || css(__, { width: width, height: height });
responsive && $.each(responsive_keys, function(i, key){ truescale[key]= get(key) });
css(____+___+dot(klass), { display: _block_ });
css(dot(cache_klass), { position: 'fixed', left: px(-100), top: px(-100) }, true);
css(dot(cache_klass)+___+_img_, { position: _absolute_, width: 10, height: 10 }, true);
pool.bind(on.pool);
t.trigger(shy ? 'prepare' : 'setup')
},
// ------
// Events
// ------
//
// Reel is completely event-driven meaning there are many events, which can be called
// (triggered). By binding event handler to any of the events you can easily hook on to any
// event to inject your custom behavior where and when this event was triggered.
//
// _**Example:** Make `#image` element reel and execute some code right after the newly
// created instance is initialized and completely loaded:_
//
// $("#image")
// .reel()
// .bind("loaded", function(ev){
// // Your code
// })
//
// Events bound to all individual instances.
//
instance: {
// ### `teardown` Event ######
// `Event`, since 1.1
//
// This event does do how it sounds like. It will teardown an instance with all its
// belongings leaving no trace.
//
// - It reconstructs the original `
` element,
// - wipes out the data store,
// - removes instance stylesheet
// - and unbinds all its events.
//
teardown: function(e){
var
backup= t.data(_backup_)
t.parent().unbind(on.instance);
if (get(_shy_)) t.parent().unbind(_click_, shy_setup)
else get(_style_).remove() && get(_area_).unbind(ns);
get(_cache_).empty();
clearTimeout(delay);
clearTimeout(gauge_delay);
$(window).unbind(_resize_, slow_gauge);
$(window).unbind(ns);
pool.unbind(on.pool);
pools.unbind(pns);
t.siblings().unbind(ns).remove();
no_bias();
t.removeAttr('onloaded');
remove_instance(t.unbind(ns).removeData().unwrap().attr(backup.attr).data(backup.data));
t.attr(_style_) == __ && t.removeAttr(_style_);
},
// ### `setup` Event ######
// `Event`, since 1.0
//
// `"setup"` Event continues with what has been started by the private `on.setup()`
// handler.
//
// - It prepares all additional on-stage DOM elements
// - and cursors for the instance stylesheet.
//
setup: function(e){
var
$overlay= t.parent().append(preloader()),
$area= set(_area_, $(opt.area || $overlay )),
multirow= opt.rows > 1,
cursor= opt.cursor,
cursor_up= cursor == _hand_ ? drag_cursor : cursor || reel_cursor,
cursor_down= cursor == _hand_ ? drag_cursor_down+___+'!important' : undefined
css(___+dot(klass), { MozUserSelect: _none_, WebkitUserSelect: _none_, MozTransform: 'translateZ(0)' });
t.unbind(_click_, shy_setup);
$area
.bind(_touchstart_, press)
.bind(opt.clickfree ? _mouseenter_ : _mousedown_, press)
.bind(opt.wheelable ? _mousewheel_ : null, wheel)
.bind(_dragstart_, function(){ return false })
css(__, { cursor: cdn(cursor_up) });
css(dot(loading_klass), { cursor: 'wait' });
css(dot(panning_klass)+____+dot(panning_klass)+' *', { cursor: cdn(cursor_down || cursor_up) }, true);
if (get(_responsive_)){
css(___+dot(klass), { width: '100%', height: _auto_ });
$(window).bind(_resize_, slow_gauge);
}
function press(e){ return t.trigger('down', [pointer(e).clientX, pointer(e).clientY, e]) && e.give }
function wheel(e, delta){ return !delta || t.trigger('wheel', [delta, e]) && e.give }
opt.hint && $area.attr('title', opt.hint);
opt.indicator && $overlay.append(indicator('x'));
multirow && opt.indicator && $overlay.append(indicator('y'));
opt.monitor && $overlay.append($monitor= $(tag(_div_), { 'class': monitor_klass }))
&& css(___+dot(monitor_klass), { position: _absolute_, left: 0, top: 0 });
},
// ### `preload` Event ######
// `Event`, since 1.1
//
// Reel keeps a cache of all images it needs for its operation. Either a sprite or all
// sequence images. It first determines the order of requesting the images and then
// asynchronously loads all of them.
//
// - It preloads all frames and sprites.
//
preload: function(e){
var
$overlay= t.parent(),
images= get(_images_),
is_sprite= !images.length,
order= reel.preload[opt.preload] || reel.preload[reel.def.preload],
preload= is_sprite ? [get(_image_)] : order(images.slice(0), opt, get),
to_load= preload.length,
preloaded= set(_preloaded_, is_sprite ? 0.5 : 0),
simultaneous= 0,
$cache= get(_cache_).empty(),
uris= []
$overlay.addClass(loading_klass);
// It also finalizes the instance stylesheet and prepends it to the head.
set(_style_, get(_style_) || $('<'+_style_+' type="text/css">'+css.rules.join('\n')+''+_style_+'>').prependTo(_head_));
set(_loading_, true);
t.trigger('stop');
opt.responsive && gauge();
t.trigger('resize', true);
while(preload.length){
var
uri= reel.substitute(opt.path+preload.shift(), get),
$img= $(tag(_img_)).data(_src_, uri).appendTo($cache)
// Each image, which finishes the load triggers `"preloaded"` Event.
$img.bind('load error abort', function(e){
e.type != 'load' && t.trigger(e.type);
return !detached($overlay) && t.trigger('preloaded') && load() && false;
});
uris.push(uri);
}
setTimeout(function(){ while (++simultaneous < reel.concurrent_requests) load(); }, 0);
set(_cached_, uris);
set(_shy_, false);
function load(){
var
$img= $cache.children(':not([src]):first')
return $img.attr(_src_, $img.data(_src_))
}
},
// ### `preloaded` Event ######
// `Event`, since 1.1
//
// This event is fired by every preloaded image and adjusts the preloader indicator's
// target position. Once all images are preloaded, `"loaded"` Event is triggered.
//
preloaded: function(e){
var
images= get(_images_).length || 1,
preloaded= set(_preloaded_, min(get(_preloaded_) + 1, images))
if (preloaded === 1) var
frame= t.trigger('frameChange', [undefined, get(_frame_)])
if (preloaded === images){
t.parent().removeClass(loading_klass);
t.trigger('loaded');
}
},
// ### `loaded` Event ######
// `Event`, since 1.1
//
// `"loaded"` Event is the one announcing when the instance is "locked and loaded".
// At this time, all is prepared, preloaded and configured for user interaction
// or animation.
//
loaded: function(e){
get(_images_).length > 1 || t.css({ backgroundImage: url(reel.substitute(opt.path+get(_image_), get)) }).attr({ src: cdn(transparent) });
get(_stitched_) && t.attr({ src: cdn(transparent) });
get(_reeled_) || set(_velocity_, opt.velocity || 0);
set(_loading_, false);
loaded= true;
},
// ### `prepare` Event ######
// [NEW] `Event`, since 1.3
//
// In case of `shy` activation, `"prepare"` event is called instead of the full `"setup"`.
// It lefts the target image untouched waiting to be clicked to actually setup.
//
prepare: function(e){
t.css('display', _block_).parent().one(_click_, shy_setup);
},
// ----------------
// Animation Events
// ----------------
//
// ### `opening` Event ######
// `Event`, since 1.1
//
// When [opening animation](#Opening-Animation) is configured for the instance, `"opening"`
// event engages the animation by pre-calculating some of its properties, which will make
// the tick handler
//
opening: function(e){
/*
- initiates opening animation
- or simply plays the reel when without opening
*/
if (!opt.opening) return t.trigger('openingDone');
var
opening= set(_opening_, true),
stopped= set(_stopped_, !get(_speed_)),
speed= opt.entry || opt.speed,
end= get(_fraction_),
duration= opt.opening,
start= set(_fraction_, end - speed * duration),
ticks= set(_opening_ticks_, ceil(duration * leader(_tempo_)))
},
// ### `openingDone` Event ######
// `Event`, since 1.1
//
// `"openingDone"` is fired onceWhen [opening animation](#Opening-Animation) is configured for the instance, `"opening"`
// event engages the animation by pre-calculating some of its properties, which will make
// the tick handler
//
openingDone: function(e){
var
playing= set(_playing_, false),
opening= set(_opening_, false),
evnt= _tick_+dot(_opening_)
pool.unbind(evnt, on.pool[evnt]);
opt.orientable && $(window).bind(_deviceorientation_, orient);
if (opt.delay > 0) delay= setTimeout(function(){ t.trigger('play') }, opt.delay * 1000)
else t.trigger('play');
function orient(e){ return t.trigger('orient', [gyro(e).alpha, gyro(e).beta, gyro(e).gamma, e]) && e.give }
},
// -----------------------
// Playback Control Events
// -----------------------
//
// ### `play` Event ######
// `Event`, since 1.1
//
// `"play"` event can optionally accept a `speed` parameter (in Hz) to change the animation
// speed on the fly.
//
play: function(e, speed){
var
speed= speed ? set(_speed_, speed) : (get(_speed_) * negative_when(1, get(_backwards_))),
duration= opt.duration,
ticks= duration && set(_ticks_, ceil(duration * leader(_tempo_))),
backwards= set(_backwards_, speed < 0),
playing= set(_playing_, !!speed),
stopped= set(_stopped_, !playing)
idle();
},
// ### `reach` Event ######
// [NEW] `Event`, since 1.3
//
// Use this event to instruct Reel to play and reach a given frame. `"reach"` event requires
// `target` parameter, which is the frame to which Reel should animate to and stop.
// Optional `speed` parameter allows for custom speed independent on the regular speed.
//
reach: function(e, target, speed){
if (target == get(_frame_)) return;
var
frames= get(_frames_),
row= set(_row_, ceil(target / frames)),
departure= set(_departure_, get(_frame_)),
target= set(_destination_, target),
shortest = set(_distance_, reel.math.distance(departure, target, frames)),
speed= abs(speed || get(_speed_)) * negative_when(1, shortest < 0)
t.trigger('play', speed);
},
// ### `pause` Event ######
// `Event`, since 1.1
//
// Triggering `"pause"` event will halt the playback for a time period designated
// by the `timeout` option. After this timenout, the playback is resumed again.
//
pause: function(e){
unidle();
},
// ### `stop` Event ######
// `Event`, since 1.1
//
// After `"stop"` event is triggered, the playback stops and stays still until `"play"`ed again.
//
stop: function(e){
var
stopped= set(_stopped_, true),
playing= set(_playing_, !stopped)
},
// ------------------------
// Human Interaction Events
// ------------------------
//
// ### `down` Event ######
// `Event`, since 1.1
//
// Marks the very beginning of touch or mouse interaction. It receives `x` and `y`
// coordinates in arguments.
//
// - It calibrates the center point (origin),
// - considers user active not idle,
// - flags the `` tag with `.reel-panning` class name
// - and binds dragging events for move and lift. These
// are usually bound to the pool (document itself) to get a consistent treating regardless
// the event target element. However in click-free mode, it binds directly to the instance.
//
down: function(e, x, y, ev){
if (!opt.clickfree && ev && ev.button !== undefined && ev.button != DRAG_BUTTON) return;
if (opt.draggable){
var
clicked= set(_clicked_, get(_frame_)),
clickfree= opt.clickfree,
velocity= set(_velocity_, 0),
$area= clickfree ? get(_area_) : pools,
origin= last= recenter_mouse(get(_revolution_), x, y)
unidle();
no_bias();
panned= 0;
$(_html_, pools).addClass(panning_klass);
$area
.bind(_touchmove_+___+_mousemove_, drag)
.bind(_touchend_+___+_touchcancel_, lift)
.bind(clickfree ? _mouseleave_ : _mouseup_, lift)
}
function drag(e){ return t.trigger('pan', [pointer(e).clientX, pointer(e).clientY, e]) && e.give }
function lift(e){ return t.trigger('up', [e]) && e.give }
},
// ### `up` Event ######
// `Event`, since 1.1
//
// This marks the termination of user's interaction. She either released the mouse button
// or lift the finger of the touch screen. This event handler:
//
// - calculates the velocity of the drag at that very moment,
// - removes the `.reel-panning` class from ``
// - and unbinds dragging events from the pool.
//
up: function(e, ev){
var
clicked= set(_clicked_, false),
reeling= set(_reeling_, false),
throwable = opt.throwable,
biases= abs(bias[0] + bias[1]) / 60,
velocity= set(_velocity_, !throwable ? 0 : throwable === true ? biases : min(throwable, biases)),
brakes= braking= velocity ? 1 : 0
unidle();
no_bias();
$(_html_, pools).removeClass(panning_klass);
(opt.clickfree ? get(_area_) : pools).unbind(pns);
},
// ### `pan` Event ######
// [RENAMED] `Event`, since 1.2
//
// Regardles the actual source of movement (mouse or touch), this event is always triggered
// in response and similar to the `"down"` Event it receives `x` and `y` coordinates
// in arguments and in addition it is passed a reference to the original browser event.
// This handler:
//
// - syncs with timer to achieve good performance,
// - calculates the distance from drag center and applies graph on it to get `fraction`,
// - recenters the drag when dragged over limits,
// - detects the direction of the motion
// - and builds up inertial motion bias.
//
// Historically `pan` was once called `slide` (conflicted with Mootools - [GH-51][1])
// or `drag` (that conflicted with MSIE).
//
// [1]:https://github.com/pisi/Reel/issues/51
//
pan: function(e, x, y, ev){
if (opt.draggable && slidable){
slidable= false;
unidle();
var
rows= opt.rows,
orbital= opt.orbital,
scrollable= !get(_reeling_) && rows <= 1 && !orbital && opt.scrollable,
delta= { x: x - last.x, y: y - last.y },
abs_delta= { x: abs(delta.x), y: abs(delta.y) }
if (ev && scrollable && abs_delta.x < abs_delta.y) return ev.give = true;
if (abs_delta.x > 0 || abs_delta.y > 0){
ev && (ev.give = false);
panned= max(abs_delta.x, abs_delta.y);
last= { x: x, y: y };
var
revolution= get(_revolution_),
origin= get(_clicked_location_),
vertical= get(_vertical_)
if (!get(_framelock_)) var
fraction= set(_fraction_, graph(vertical ? y - origin.y : x - origin.x, get(_clicked_on_), revolution, get(_lo_), get(_hi_), get(_cwish_), vertical ? y - origin.y : x - origin.x)),
reeling= set(_reeling_, get(_reeling_) || get(_frame_) != get(_clicked_)),
motion= to_bias(vertical ? delta.y : delta.x || 0),
backwards= motion && set(_backwards_, motion < 0)
if (orbital && get(_center_)) var
vertical= set(_vertical_, abs(y - origin.y) > abs(x - origin.x)),
origin= recenter_mouse(revolution, x, y)
if (rows > 1 && !get(_rowlock_)) var
revolution_y= get(_revolution_y_),
start= get(_clicked_tier_),
lo= - start * revolution_y,
tier= set(_tier_, reel.math.envelope(y - origin.y, start, revolution_y, lo, lo + revolution_y, -1))
if (!(fraction % 1) && !opt.loops) var
origin= recenter_mouse(revolution, x, y)
}
}
},
// ### `wheel` Event ######
// `Event`, since 1.0
//
// Maps Reel to mouse wheel position change event which is provided by a nifty plug-in
// written by Brandon Aaron - the [Mousewheel plug-in][1], which you will need to enable
// the mousewheel wheel for reeling. You can also choose to use [Wheel Special Event
// plug-in][2] by Three Dub Media instead. Either way `"wheel"` Event handler receives
// the positive or negative wheeled distance in arguments. This event:
//
// - calculates wheel input delta and adjusts the `fraction` using the graph,
// - recenters the "drag" each and every time,
// - detects motion direction
// - and nullifies the velocity.
//
// [1]:https://github.com/brandonaaron/jquery-mousewheel
// [2]:http://blog.threedubmedia.com/2008/08/eventspecialwheel.html
//
wheel: function(e, distance, ev){
if (!distance) return;
wheeled= true;
var
delta= ceil(math.sqrt(abs(distance)) / 2),
delta= negative_when(delta, distance > 0),
revolution= 0.0833 * get(_revolution_), // Wheel's revolution is 1/12 of full revolution
origin= recenter_mouse(revolution),
backwards= delta && set(_backwards_, delta < 0),
velocity= set(_velocity_, 0),
fraction= set(_fraction_, graph(delta, get(_clicked_on_), revolution, get(_lo_), get(_hi_), get(_cwish_)))
ev && ev.preventDefault();
ev && (ev.give = false);
unidle();
t.trigger('up', [ev]);
},
// ### `orient` Event ######
// [NEW] `Event`, since 1.3
//
// Maps Reel to device orientation event which is provided by some touch enabled devices
// with gyroscope inside. Event handler receives all three device orientation angles
// in arguments. This event:
//
// - maps alpha angle directly to `fraction`
//
orient: function(e, alpha, beta, gamma, ev){
if (!slidable || operated) return;
oriented= true;
var
alpha_fraction= alpha / 360
fraction= set(_fraction_, +((opt.stitched || opt.cw ? 1 - alpha_fraction : alpha_fraction)).toFixed(2))
slidable = false;
},
// ------------------
// Data Change Events
// ------------------
//
// Besides Reel being event-driven, it also is data-driven respectively data-change-driven
// meaning that there is a mechanism in place, which detects real changes in data being
// stored with `.reel(name, value)`. Learn more about [data changes](#Changes).
//
// These data change bindings are for internal use only and you don't ever trigger them
// per se, you change data and that will trigger respective change event. If the value
// being stored is the same as the one already stored, nothing will be triggered.
//
// _**Example:** Change Reel's current `frame`:_
//
// .reel("frame", 15)
//
// Change events always receive the actual data key value in the third argument.
//
// _**Example:** Log each viewed frame number into the developers console:_
//
// .bind("frameChange", function(e, d, frame){
// console.log(frame)
// })
//
// ---
// ### `fractionChange` Event ######
// `Event`, since 1.0
//
// Internally Reel doesn't really work with the frames you set it up with. It uses
// __fraction__, which is a numeric value ranging from 0.0 to 1.0. When `fraction` changes
// this handler basically calculates and sets new value of `frame`.
//
fractionChange: function(e, nil, fraction){
if (nil !== undefined) return;
var
frame= 1 + floor(fraction / get(_bit_)),
multirow= opt.rows > 1,
orbital= opt.orbital,
center= set(_center_, !!orbital && (frame <= orbital || frame >= get(_footage_) - orbital + 2))
if (multirow) var
frame= frame + (get(_row_) - 1) * get(_frames_)
var
frame= set(_frame_, frame)
},
// ### `tierChange` Event ######
// `Event`, since 1.2
//
// The situation of `tier` is very much similar to the one of `fraction`. In multi-row
// movies, __tier__ is an internal value for the vertical axis. Its value also ranges from
// 0.0 to 1.0. Handler calculates and sets new value of `frame`.
//
tierChange: function(e, nil, tier){
if (nil === undefined) var
row= set(_row_, round(interpolate(tier, 1, opt.rows))),
frames= get(_frames_),
frame= get(_frame_) % frames || frames,
frame= set(_frame_, frame + row * frames - frames)
},
// ### `rowChange` Event ######
// `Event`, since 1.1
//
// The physical vertical position of Reel is expressed in __rows__ and ranges
// from 1 to the total number of rows defined with [`rows`](#rows-Option). This handler
// only converts `row` value to `tier` and sets it.
//
rowChange: function(e, nil, row){
if (nil === undefined) var
tier= may_set(_tier_, undefined, row, opt.rows)
},
// ### `frameChange` Event ######
// `Event`, since 1.0
//
// The physical horizontal position of Reel is expressed in __frames__ and ranges
// from 1 to the total number of frames configured with [`frames`](#frames-Option).
// This handler converts `row` value to `tier` and sets it. This default handler:
//
// - flags the instance's outter wrapper with `.frame-X` class name
// (where `X` is the actual frame number),
// - calculates and eventually sets `fraction` (and `tier` for multi-rows) from given frame,
// - for sequences, it switches the `
`'s `src` to the right frame
// - and for sprites it recalculates sprite's 'background position shift and applies it.
//
frameChange: function(e, nil, frame){
if (nil !== undefined) return;
this.className= this.className.replace(reel.re.frame_klass, frame_klass + frame);
var
frames= get(_frames_),
rows= opt.rows,
path= opt.path,
base= frame % frames || frames,
frame_row= (frame - base) / frames + 1,
frame_tier= (frame_row - 1) / (rows - 1),
row= get(_row_),
tier= !rows ? get(_tier_) : may_set(_tier_, frame_tier, row, rows),
fraction= may_set(_fraction_, undefined, base, frames),
footage= get(_footage_)
if (opt.orbital && get(_vertical_)) var
frame= opt.inversed ? footage + 1 - frame : frame,
frame= frame + footage
var
stitched= get(_stitched_),
images= get(_images_),
is_sprite= !images.length || stitched
if (!is_sprite){
get(_responsive_) && gauge();
get(_preloaded_) && t.attr({ src: reen(reel.substitute(path + images[frame - 1], get)) });
}else{
var
spacing= get(_spacing_),
width= get(_width_),
height= get(_height_)
if (!stitched) var
horizontal= opt.horizontal,
minor= (frame % footage) - 1,
minor= minor < 0 ? footage - 1 : minor,
major= floor((frame - 0.1) / footage),
major= major + (rows > 1 ? 0 : (get(_backwards_) ? 0 : !opt.directional ? 0 : get(_rows_))),
a= major * ((horizontal ? height : width) + spacing),
b= minor * ((horizontal ? width : height) + spacing),
shift= images.length ? [0, 0] : horizontal ? [px(-b), px(-a)] : [px(-a), px(-b)]
else{
var
x= set(_stitched_shift_, round(interpolate(fraction, 0, get(_stitched_travel_))) % stitched),
y= rows <= 1 ? 0 : (height + spacing) * (rows - row),
shift= [px(-x), px(-y)],
image= images.length > 1 && images[row - 1],
fullpath= reel.substitute(path + image, get)
image && t.css('backgroundImage').search(fullpath) < 0 && t.css({ backgroundImage: url(fullpath) })
}
t.css({ backgroundPosition: shift.join(___) })
}
},
// This extra binding takes care of watching frame position while animating the `"reach"` event.
//
'frameChange.reach': function(e, nil, frame){
if (!get(_destination_) || nil !== undefined) return;
var
travelled= reel.math.distance(get(_departure_), frame, get(_frames_)),
onorover= abs(travelled) >= abs(get(_distance_))
if (!onorover) return;
set(_frame_, set(_destination_));
set(_destination_, set(_distance_, set(_departure_, 0)));
t.trigger('stop');
},
// ~~~
//
// When `image` or `images` is changed on the fly, this handler resets the loading cache and triggers
// new preload sequence. Images are actually switched only after the new image is fully loaded.
//
'imageChange imagesChange': function(e, nil, image){
t.trigger('preload');
},
// ---------
// Indicator
// ---------
//
// When configured with the [`indicator`](#indicator-Option) option, Reel adds to the scene
// a visual indicator in a form of a black rectangle traveling along the bottom edge
// of the image. It bears two distinct messages:
//
// - its horizontal position accurately reflects actual `fraction`
// - and its width reflect one frame's share on the whole (more frames mean narrower
// indicator).
//
'fractionChange.indicator': function(e, nil, fraction){
if (opt.indicator && nil === undefined) var
size= opt.indicator,
orbital= opt.orbital,
travel= orbital && get(_vertical_) ? get(_height_) : get(_width_),
slots= orbital ? get(_footage_) : opt.images.length || get(_frames_),
weight= ceil(travel / slots),
travel= travel - weight,
indicate= round(interpolate(fraction, 0, travel)),
indicate= !opt.cw || get(_stitched_) ? indicate : travel - indicate,
$indicator= indicator.$x.css(get(_vertical_)
? { left: 0, top: px(indicate), bottom: _auto_, width: size, height: weight }
: { bottom: 0, left: px(indicate), top: _auto_, width: weight, height: size })
},
// For multi-row object movies, there's a second indicator sticked to the left edge
// and communicates:
//
// - its vertical position accurately reflects actual `tier`
// - and its height reflect one row's share on the whole (more rows mean narrower
// indicator).
//
'tierChange.indicator': function(e, nil, tier){
if (opt.rows > 1 && opt.indicator && nil === undefined) var
travel= get(_height_),
size= opt.indicator,
weight= ceil(travel / opt.rows),
travel= travel - weight,
indicate= round(tier * travel),
$yindicator= indicator.$y.css({ left: 0, top: indicate, width: size, height: weight })
},
// Indicators are bound to `fraction` or `row` changes, meaning they alone can consume
// more CPU resources than the entire Reel scene. Use them for development only.
//
// -----------
// Annotations
// -----------
//
// If you want to annotate features of your scene to better describe the subject,
// there's annotations for you. Annotations feature allows you to place virtually any
// HTML content over or into the image and have its position and visibility synchronized
// with the position of Reel. These two easy looking handlers do a lot more than to fit
// in here.
//
// Learn more about [Annotations][1] in the wiki, where a great care has been taken
// to in-depth explain this new exciting functionality.
//
// [1]:https://github.com/pisi/Reel/wiki/Annotations
//
'setup.annotations': function(e){
var
$overlay= t.parent()
$.each(get(_annotations_), function(ida, note){
var
$note= typeof note.node == _string_ ? $(note.node) : note.node || {},
$note= $note.jquery ? $note : $(tag(_div_), $note),
$note= $note.attr({ id: ida }).addClass(annotation_klass),
$image= note.image ? $(tag(_img_), note.image) : $(),
$link= note.link ? $(tag('a'), note.link).click(function(){ t.trigger('up.annotations', { target: $link }); }) : $()
css(hash(ida), { display: _none_, position: _absolute_ }, true);
note.image || note.link && $note.append($link);
note.link || note.image && $note.append($image);
note.link && note.image && $note.append($link.append($image));
$note.appendTo($overlay);
});
},
'prepare.annotations': function(e){
$.each(get(_annotations_), function(ida, note){
$(hash(ida)).hide();
});
},
'frameChange.annotations': function(e, nil, frame){
if (!get(_preloaded_) || nil !== undefined) return;
var
width= get(_width_),
stitched= get(_stitched_),
ss= get(_stitched_shift_)
$.each(get(_annotations_), function(ida, note){
var
$note= $(hash(ida)),
start= note.start || 1,
end= note.end,
frame= frame || get(_frame_),
offset= frame - 1,
at= note.at ? (note.at[offset] == '+') : false,
offset= note.at ? offset : offset - start + 1,
x= typeof note.x!=_object_ ? note.x : note.x[offset],
y= typeof note.y!=_object_ ? note.y : note.y[offset],
placed= x !== undefined && y !== undefined,
visible= placed && (note.at ? at : (offset >= 0 && (!end || offset <= end - start)))
if (stitched) var
on_edge= x < width && ss > stitched - width,
after_edge= x > stitched - width && ss >= 0 && ss < width,
x= !on_edge ? x : x + stitched,
x= !after_edge ? x : x - stitched,
x= x - ss
if (get(_responsive_)) var
ratio= get(_ratio_),
x= x && x * ratio,
y= y && y * ratio
var
style= { display: visible ? _block_:_none_, left: px(x), top: px(y) }
$note.css(style);
});
},
'up.annotations': function(e, ev){
if (panned > 10 || wheeled) return;
var
$target= $(ev.target),
$link= ($target.is('a') ? $target : $target.parents('a')),
href= $link.attr('href')
href && (panned= 10);
},
// ---------------------
// Click Stepping Events
// ---------------------
//
// For devices without drag support or for developers, who want to use some sort
// of left & right buttons on their site to control your instance from outside, Reel
// supports ordinary click with added detection of left half or right half and resulting
// triggering of `stepLeft` and `stepRight` events respectively.
//
// This behavior can be disabled by the [`steppable`](#steppable-Option) option.
//
'up.steppable': function(e, ev){
if (panned || wheeled) return;
t.trigger(get(_clicked_location_).x - t.offset().left > 0.5 * get(_width_) ? 'stepRight' : 'stepLeft')
},
'stepLeft stepRight': function(e){
unidle();
},
// ### `stepLeft` Event ######
// `Event`, since 1.2
//
stepLeft: function(e){
set(_backwards_, false);
set(_fraction_, get(_fraction_) - get(_bit_) * get(_cwish_));
},
// ### `stepRight` Event ######
// `Event`, since 1.2
//
stepRight: function(e){
set(_backwards_, true);
set(_fraction_, get(_fraction_) + get(_bit_) * get(_cwish_));
},
// ### `stepUp` Event ######
// [NEW] `Event`, since 1.3
//
stepUp: function(e){
set(_row_, get(_row_) - 1);
},
// ### `stepDown` Event ######
// [NEW] `Event`, since 1.3
//
stepDown: function(e){
set(_row_, get(_row_) + 1);
},
// -----------------------
// [NEW] Responsive Events
// -----------------------
//
// In responsive mode in case of parent's size change, in addition to actual recalculations,
// the instance starts to emit throttled `resize` events. This handler in turn emulates
// images changes event leading to reload of frames.
//
// ---
//
// ### `resize` Event ######
// [NEW] `Event`, since 1.3
//
resize: function(e, force){
if (get(_loading_) && !force) return;
var
stitched= get(_stitched_),
spacing= get(_spacing_),
height= get(_height_),
is_sprite= !get(_images_).length || stitched,
rows= opt.rows || 1,
size= get(_images_).length
? !stitched ? undefined : px(stitched)+___+px(height)
: stitched && px(stitched)+___+px((height + spacing) * rows - spacing)
|| px((get(_width_) + spacing) * get(_footage_) - spacing)+___+px((height + spacing) * get(_rows_) * rows * (opt.directional? 2:1) - spacing)
t.css({
height: is_sprite ? px(height) : null,
backgroundSize: size || null
});
force || t.trigger('imagesChange');
},
// ----------------
// Follow-up Events
// ----------------
//
// When some event as a result triggers another event, it preferably is not triggered
// directly, because it would disallow preventing the event propagation / chaining
// to happen. Instead a followup handler is bound to the first event and it triggers the
// second one.
//
'setup.fu': function(e){
var
frame= set(_frame_, opt.frame + (get(_row_) - 1) * get(_frames_))
t.trigger('preload')
},
'wheel.fu': function(){ wheeled= false },
'clean.fu': function(){ t.trigger('teardown') },
'loaded.fu': function(){ t.trigger('opening') }
},
// -------------
// Tick Handlers
// -------------
//
// As opposed to the events bound to the instance itself, there is a [ticker](#Ticker)
// in place, which emits `tick.reel` event on the document level by default every 1/36
// of a second and drives all the animations. Three handlers currently bind each instance
// to the tick.
//
pool: {
// This handler has a responsibility of continuously updating the preloading indicator
// until all images are loaded and to unbind itself then.
//
'tick.reel.preload': function(e){
if (!(loaded || get(_loading_)) || get(_shy_)) return;
var
width= get(_width_),
current= number(preloader.$.css(_width_)),
images= get(_images_).length || 1,
target= round(1 / images * get(_preloaded_) * width)
preloader.$.css({ width: current + (target - current) / 3 + 1 })
if (get(_preloaded_) === images && current > width - 1){
loaded= false;
preloader.$.fadeOut(300, function(){ preloader.$.css({ opacity: 1, width: 0 }) });
}
},
// This handler binds to the document's ticks at all times, regardless the situation.
// It serves several tasks:
//
// - keeps track of how long the instance is being operated by the user,
// - or for how long it is braking the velocity inertia,
// - decreases gained velocity by applying power of the [`brake`](#brake-Option) option,
// - flags the instance as `slidable` again, so that `pan` event handler can be executed
// again,
// - updates the [`monitor`](#monitor-Option) value,
// - bounces off the edges for non-looping panoramas,
// - and most importantly it animates the Reel if [`speed`](#speed-Option) is configured.
//
'tick.reel': function(e){
if (get(_shy_)) return;
var
velocity= get(_velocity_),
leader_tempo= leader(_tempo_),
monitor= opt.monitor
if (!reel.intense && offscreen()) return;
if (braking) var
braked= velocity - (get(_brake_) / leader_tempo * braking),
velocity= set(_velocity_, braked > 0.1 ? braked : (braking= operated= 0))
monitor && $monitor.text(get(monitor));
velocity && braking++;
operated && operated++;
to_bias(0);
slidable= true;
if (operated && !velocity) return mute(e);
if (get(_clicked_)) return mute(e, unidle());
if (get(_opening_ticks_) > 0) return;
if (!opt.loops && opt.rebound) var
edgy= !operated && !(get(_fraction_) % 1) ? on_edge++ : (on_edge= 0),
bounce= on_edge >= opt.rebound * 1000 / leader_tempo,
backwards= bounce && set(_backwards_, !get(_backwards_))
var
direction= get(_cwish_) * negative_when(1, get(_backwards_)),
ticks= get(_ticks_),
step= (!get(_playing_) || oriented || !ticks ? velocity : abs(get(_speed_)) + velocity) / leader(_tempo_),
fraction= set(_fraction_, get(_fraction_) - step * direction),
ticks= !opt.duration ? ticks : ticks > 0 && set(_ticks_, ticks - 1)
!ticks && get(_playing_) && t.trigger('stop');
},
// This handler performs the opening animation duty when during it the normal animation
// is halted until the opening finishes.
//
'tick.reel.opening': function(e){
if (!get(_opening_)) return;
var
speed= opt.entry || opt.speed,
step= speed / leader(_tempo_) * (opt.cw? -1:1),
ticks= set(_opening_ticks_, get(_opening_ticks_) - 1),
fraction= set(_fraction_, get(_fraction_) + step)
ticks || t.trigger('openingDone');
}
}
},
loaded= false,
// ------------------------
// Instance Private Helpers
// ------------------------
//
// - Events propagation stopper / muter
//
mute= function(e, result){ return e.stopImmediatePropagation() || result },
// - Shy initialization helper
//
shy_setup= function(){ t.trigger('setup') },
// - User idle control
//
operated,
braking= 0,
idle= function(){ return operated= 0 },
unidle= function(){
clearTimeout(delay);
pool.unbind(_tick_+dot(_opening_), on.pool[_tick_+dot(_opening_)]);
set(_opening_ticks_, 0);
set(_reeled_, true);
return operated= -opt.timeout * leader(_tempo_)
},
panned= 0,
wheeled= false,
oriented= false,
// - Constructors of UI elements
//
$monitor= $(),
preloader= function(){
css(___+dot(preloader_klass), {
position: _absolute_,
left: 0, bottom: 0,
height: opt.preloader,
overflow: _hidden_,
backgroundColor: '#000'
});
return preloader.$= $(tag(_div_), { 'class': preloader_klass })
},
indicator= function(axis){
css(___+dot(indicator_klass)+dot(axis), {
position: _absolute_,
width: 0, height: 0,
overflow: _hidden_,
backgroundColor: '#000'
});
return indicator['$'+axis]= $(tag(_div_), { 'class': indicator_klass+___+axis })
},
// - CSS rules & stylesheet
//
css= function(selector, definition, global){
var
stage= global ? __ : get(_stage_),
selector= selector.replace(/^/, stage).replace(____, ____+stage)
return css.rules.push(selector+cssize(definition)) && definition
function cssize(values){
var rules= [];
$.each(values, function(key, value){ rules.push(key.replace(/([A-Z])/g, '-$1').toLowerCase()+':'+px(value)+';') });
return '{'+rules.join(__)+'}'
}
},
$style,
// - Off screen detection (vertical only for performance)
//
offscreen= function(){
var
height= get(_height_),
width= get(_width_),
rect= t[0].getBoundingClientRect()
return rect.top < -height
|| rect.left < -width
|| rect.right > width + $(window).width()
|| rect.bottom > height + $(window).height()
},
// - Inertia rotation control
//
on_edge= 0,
last= { x: 0, y: 0 },
to_bias= function(value){ return bias.push(value) && bias.shift() && value },
no_bias= function(){ return bias= [0,0] },
bias= no_bias(),
// - Graph function to be used
//
graph= opt.graph || reel.math[opt.loops ? 'hatch' : 'envelope'],
normal= reel.normal,
// - Response to the size changes in responsive mode
//
slow_gauge= function(){
clearTimeout(gauge_delay);
gauge_delay= setTimeout(gauge, reel.resize_gauge);
},
gauge= function(){
if (t.width() == get(_width_)) return;
var
truescale= get(_truescale_),
ratio= set(_ratio_, t.width() / truescale.width)
$.each(truescale, function(key, value){ set(key, round(value * ratio)) })
t.trigger('resize');
},
// - Delay timer pointers
//
delay, // openingDone's delayed play
gauge_delay, // slow_gauge's throttle
// - Interaction graph's zero point reset
//
recenter_mouse= function(revolution, x, y){
var
fraction= set(_clicked_on_, get(_fraction_)),
tier= set(_clicked_tier_, get(_tier_)),
loops= opt.loops,
lo= set(_lo_, loops ? 0 : - fraction * revolution),
hi= set(_hi_, loops ? revolution : revolution - fraction * revolution)
return x !== undefined && set(_clicked_location_, { x: x, y: y }) || undefined
},
slidable= true,
// ~~~
//
// Data interface used to set `fraction` and `tier` with the value recalculated through their
// _cousin_ keys (`frame` for `fraction` and `row` for `tier`). This value is actually set
// only if it does make a difference in the cousin value.
//
may_set= function(key, value, cousin, maximum){
if (!maximum) return;
var
current= get(key) || 0,
recalculated= value !== undefined ? value : (cousin - 1) / (maximum - 1),
recalculated= key != _fraction_ ? recalculated : min( recalculated, 0.9999),
worthy= +abs(current - recalculated).toFixed(8) >= +(1 / (maximum - 1)).toFixed(8),
value= worthy ? set(key, recalculated) : value || current
return value
},
// ~~~
//
// Global events are bound to the pool (`document`), but to make it work inside an `