-
Notifications
You must be signed in to change notification settings - Fork 0
/
jcp-viewport.js
221 lines (184 loc) · 6.78 KB
/
jcp-viewport.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
/**
* Parallax viewport class
*
* Creates a new parallax viewport controller on the given element, which will animate child elements as layers.
*
* @requires jcparallax.js
* @requires jcp-animator.js
* @requires jcp-timer.js
* @requires jcp-layer.js
*
* @param {jQuery} el element to read input coordinates from for animating the parallax
* @param {object} options default options for each layer's movement
* @param {object} layerOptions array of options corresponding to each layer resulting from running
* options.layerSelector against the viewport element (or against each element
* in options.layerSelector if it is provided as a jQuery collection)
*/
(function($) {
jcparallax.Viewport = function(el, options, layerOptions)
{
// setup element & cache dimensions
this.element = el;
this.refreshCoords();
// default options
var defaults = {
layerSelector: '.jcp-layer',
movementRangeX: true,
movementRangeY: true,
inputEvent: null, // for use when using a custom inputHandler callback
inputHandler: 'mousemove',
animHandler: 'position',
framerate: 120, // sampling rate (in ms) when using CSS transitions to tween between samples
fbFramerate: null, // sampling rate for fallback plain-js mode. Calculated from framerate if not provided.
transitionCheckCb : null
};
// determine layer movement ranges if set to automatic
this.options = $.extend(true, defaults, options);
// set layer opts for passing on to child layers
this.layerOptions = layerOptions;
// create the timer handler for updating the effect (:TODO: disable when inactive, reuse synced timers)
var that = this;
this.timer = new jcparallax.TransitionInterval(function() {
return that.updateLayers.call(that);
}, this.options.framerate, this.options.fbFramerate, function() {
that._checkFramerate.call(that);
});
// find layers
var layers;
if (options.layerSelector.jquery) {
layers = options.layerSelector;
} else {
layers = $(options.layerSelector, el);
}
// initialise layers
this.addLayers(layers);
// start our animation timer
this.timer.start();
};
$.extend(jcparallax.Viewport.prototype, {
// cached viewport coordinate data
offsetX : null,
offsetY : null,
sizeX : null,
sizeY : null,
scrollX : null,
scrollY : null,
/**
* Add elements as layers to this viewport
* @param {jQuery} layerEls jQuery collection containing new layer elements to create
*/
addLayers : function(layerEls)
{
var that = this;
this.layers = [];
layerEls.each(function(i) {
var opts = $.extend({}, that.options),
layer = $(this),
handler;
// build options by merging in layer-specific overrides
if (that.layerOptions && that.layerOptions[i]) {
$.extend(opts, that.layerOptions[i]);
}
// create and store the new layer handler
handler = new jcparallax.Layer(that, layer, opts);
layer.data(jcparallax.layerStorageKey, handler);
that.layers.push(handler);
});
this.timer.addElements(layerEls); // add layer elements for control by the timer
},
/**
* Call for a redraw in the positions of all layers
* This moves the layers in response to their Animator's last sampled input
* position from its input callback, and should be run at a regular interval
* for best success.
*
* @return true if the input event coordinates were different to last time - required for CSS transition timing to function
*/
updateLayers : function()
{
// redraw the layer elements
var changed = false;
$.each(this.layers, function(i, layer) {
if (layer.redraw()) {
changed = true;
}
});
return changed; // return to indicate whether layers needed updating
},
/**
* Refreshes the coordinates of the viewport used in input normalisation,
* as well as refreshing the movement ranges for all layers under the control
* of the viewport.
*/
refreshCoords : function()
{
var offset = this.element.offset();
this.offsetX = offset.left;
this.offsetY = offset.top;
this.sizeX = this.element.width();
this.sizeY = this.element.height();
this.scrollX = this.element[0].scrollWidth - this.sizeX;
this.scrollY = this.element[0].scrollHeight - this.sizeY;
if (this.layers) {
$.each(this.layers, function(i, layer) {
layer.refreshCoords();
});
}
},
/**
* Checks input jcparallax options and returns the appropriate framerate for
* animation depending on available browser support.
*
* Used internally by Viewport and Layer classes to determine animation speed.
*
* @param {object} opts input options map to jcparallax.Viewport or jcparallax.Layer
* @param {function} transitionCheckCb (optional) callback for additional custom checking of the input options, to enable future expansion.
* This callback should return TRUE if CSS transitions can be used for the animHandler given in the options.
* @return {int} the sampling interval to animate this transition at, in ms
*/
_checkFramerate : function()
{
return ((!jcparallax.support.backgroundTransitions && this.options.animHandler == 'background')
|| (!jcparallax.support.textShadowTransitions && this.options.animHandler == 'textShadow')
|| (this.options.transitionCheckCb && !this.options.transitionCheckCb.call(this, this.options)));
}
});
//------------------------------------------------------------------------------
// Input event handlers
//------------------------------------------------------------------------------
jcparallax.Viewport.inputHandlers = {
mousemove : function(el, evt)
{
var xPos = evt.pageX - this.viewport.offsetX,
yPos = evt.pageY - this.viewport.offsetY;
this.updateLastSamplePos(xPos / this.viewport.sizeX, yPos / this.viewport.sizeY);
},
scroll : function(el, evt)
{
var xPos = el.scrollLeft(),
yPos = el.scrollTop();
// :NOTE: scroll offsets are often calculated smaller than the available space due to scrollbar space
this.updateLastSamplePos(this.viewport.scrollX ? Math.min(1, xPos / this.viewport.scrollX) : 0,
this.viewport.scrollY ? Math.min(1, yPos / this.viewport.scrollY) : 0);
},
click : function(el, evt)
{
var xPos = evt.pageX - this.viewport.offsetX,
yPos = evt.pageY - this.viewport.offsetY;
// :TODO: start a timer to animate the parallax over the specified duration
this.updateLastSamplePos(xPos / this.viewport.sizeX, yPos / this.viewport.sizeY);
},
// the following advanced events would need to have inputEvent provided in options as well
mousemove_xcentered : function(el, evt)
{
var xPos = evt.pageX - this.viewport.offsetX,
yPos = evt.pageY - this.viewport.offsetY,
halfX = this.viewport.sizeX / 2;
if (xPos > halfX) {
xPos -= halfX;
xPos = halfX - xPos;
}
this.updateLastSamplePos((xPos / this.viewport.sizeX) * 2, yPos / this.viewport.sizeY);
}
};
})(jQuery);