forked from pisi/Reel
-
Notifications
You must be signed in to change notification settings - Fork 0
/
jquery.reel.js
772 lines (740 loc) · 33.9 KB
/
jquery.reel.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
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
/**
@@@@@@@@@@@@@@
@@@@@@@@@@@@@@@@@@@@@@
@@@@@@@@ @@@@@@@@
@@@@@@@ @@@@@@@
@@@@@@@ @@@@@@@
@@@@@@@ @@@@@@@
@@@@@@@@ @ @@@@@@@@
@@@@@@@@@ @@@ @@@@@@@@@
@@@@@@@@@@@@@@ @@@@@@@@@@@
@@@@@@@@@@@@@ @@@@@@@
@@@@@@@@@@@@ @@@
@@@@@@
@@@@
@@
*
* jQuery Reel
* ===========
* 360° projection plugin for jQuery
*
* @license Copyright (c) 2009-2011 Petr Vostrel (http://petr.vostrel.cz/)
* Dual licensed under the MIT (MIT-LICENSE.txt)
* and GPL (GPL-LICENSE.txt) licenses.
*
* jQuery Reel
* http://jquery.vostrel.cz/reel
* Version: 1.1.3
* Updated: 2011-05-08
*
* Requires jQuery 1.4.2 or higher
*/
/*
* Have it served by a cloud CDN:
* - http://code.vostrel.cz/jquery.reel-bundle.js (recommended)
* - http://code.vostrel.cz/jquery.reel.js
* - http://code.vostrel.cz/jquery.reel-debug.js
* - or http://code.vostrel.cz/jquery.reel-edge.js if you feel like it ;)
*
* Optional nice-to-have plugins:
* - jQuery.disableTextSelect [B] (James Dempster, http://www.jdempster.com/category/jquery/disabletextselect/)
* - 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
*/
jQuery.reel || (function($, window, document, undefined){
$.reel= {
version: '1.1.3',
// Options defaults
def: {
footage: 6, // number of frames per line/column
frame: 1, // initial frame
frames: 36, // total number of frames; every 10° for full rotation
hint: '', // mouse-sensitive area hint tooltip
horizontal: true, // roll flow; defaults to horizontal
hotspot: undefined, // [deprecated] use `area` instead
indicator: 0, // size of a visual indicator of reeling (in pixels)
klass: '', // plugin instance class name
loops: true, // is it a loop?
reversed: undefined, // [deprecated] use `cw` instead
spacing: 0, // space between frames on reel
stitched: 0, // pixel width (length) of a stitched (rectilinear) panoramic reel
suffix: '-reel', // sprite filename suffix (A.jpg's sprite is A-reel.jpg by default)
tooltip: '', // [deprecated] use `hint` instead
// [NEW] in version 1.1
area: undefined, // custom mouse-sensitive area jQuery collection
brake: 0.5, // brake force of the inertial rotation
clickfree: false, // binds to mouse leave/enter events instead of down/up
cw: false, // true for clockwise organization of sprite
delay: -1, // delay before autoplay in seconds (no autoplay by default (-1))
directional: false, // two sets of frames (for forward and backward motion) are used when true
draggable: true, // mouse or finger drag interaction (allowed by default)
entry: undefined, // speed of the opening animation (Hz, defaults to value of `speed`)
graph: undefined, // custom graph function
image: undefined, // image sprite to be used
images: [], // sequence array of individual images to be used instead of sprite
inversed: false, // flags inversed organization of frames in orbital object movie
laziness: 6, // on "lazy" devices tempo is divided by this divisor for better performace
monitor: undefined, // stored value name to monitor in the upper left corner of the viewport
opening: 0, // duration of opening animation (in seconds)
orbital: 0, // view centering tolerance in frames for dual-orbit object movies
path: '', // URL path to be prepended to `image` or `images` filenames
preloader: 4, // size (height) of a image loading indicator (in pixels)
rebound: 0.5, // time spent on the edge (in seconds) of a non-looping panorama before it bounces back
revolution: undefined, // distance mouse must be dragged for full revolution
// (defaults to double the viewport size or half the `stitched` option)
row: 1, // initial row
rows: 0, // number of rows for a multi-row setup (zero from one-row setup)
speed: 0, // animated rotation speed in revolutions per second (Hz)
step: undefined, // initial step (overrides `frame`)
steps: undefined, // number of steps a revolution is divided in (by default equal to `frames`)
tempo: 36, // shared ticker tempo in ticks per second
timeout: 2, // idle timeout in seconds
throwable: true, // drag & throw interaction (allowed by default)
vertical: false, // switches orbital object movie to vertical mode
wheelable: true // mouse wheel interaction (allowed by default)
}
// [deprecated] options defaults may be gone anytime soon
}
$.fn.reel= function(options){
var
opt= $.extend({}, $.reel.def, options),
applicable= (function(tags){
// Only IMG tags with non-empty SRC and non-zero WIDTH and HEIGHT will pass
var
pass= []
tags.filter(_img_).each(function(ix){
var
$this= $(this),
src= opt.images.length && opt.images || opt.image || $this.attr(_src_),
width= number($this.css(_width_)),
height= number($this.css(_height_))
if (!src || src == __ || !width || !height) return;
pass.push($this);
});
tags.filter(_div_ + dot(klass)).each(function(ix){
pass.push($(this));
});
return $(pass);
})(this),
instances= []
// Backward-compatibility of [deprecated] legacy options
opt.reversed && (opt.cw= true);
opt.tooltip && (opt.hint= opt.tooltip);
opt.hotspot && (opt.area= opt.hotspot);
applicable.each(function(){
var
t= $(this),
data= t.data(),
// Data storage
set= function(name, value){
data[name]= value;
t.trigger('store', [name, value]);
return value;
},
get= function(name){
var value= data[name];
t.trigger('recall', [name, value]);
return value;
},
// Events & handlers
on= {
setup: function(e){
/*
- fills up the data storage with values based on options
- binds to ticker
*/
if (t.hasClass(klass)) return cleanup.call(e);
var
src= t.attr(_src_),
id= set(_id_, t.attr(_id_) || t.attr(_id_, klass+'-'+(+new Date())).attr(_id_)),
styles= t.attr('style'),
images= opt.images,
stitched= opt.stitched,
loops= opt.loops,
size= { x: number(t.css(_width_)), y: number(t.css(_height_)) },
frames= set(_frames_, opt.orbital && opt.footage || opt.rows <= 1 && images.length || opt.frames),
rows= stitched ? 1 : ceil(frames / opt.footage),
style= {
display: 'block',
width: size.x,
height: size.y
},
stage_id= '#'+id+opt.suffix,
classes= t.attr('class'),
overlay_css= { position: 'relative', width: size.x, height: size.y },
$overlay= $(_div_tag_, { id: stage_id.substr(1), 'class': classes+___+overlay_klass, css: overlay_css }),
$instance= t.wrap($overlay).attr({ 'class': klass }).css(style).bind(on),
instances_count= instances.push(add_instance($instance)[0])
set(_image_, images.length && images.length || opt.image || src.replace(/^(.*)\.(jpg|jpeg|png|gif)$/, '$1' + opt.suffix + '.$2'));
set(_classes_, classes);
set(_frame_, opt.frame);
set(_spacing_, opt.spacing);
set(_dimensions_, size);
set(_fraction_, 0);
set(_steps_, opt.steps || opt.frames);
set(_revolution_, opt.revolution || stitched / 2 || size.x * 2);
set(_rows_, rows);
set(_bit_, 1 / (frames - (loops && !stitched ? 0 : 1)));
set(_wheel_step_, 1 / max(frames, get(_steps_)));
set(_stitched_, stitched);
set(_stitched_travel_, stitched - (loops ? 0 : size.x));
set(_stage_, stage_id);
set(_backwards_, set(_speed_, opt.speed) < 0);
set(_velocity_, 0);
set(_vertical_, opt.vertical);
set(_row_, (opt.row - 1) / (opt.rows - 1));
set(_cwish_, negative_when(1, !opt.cw && !stitched));
set(_reeling_, false);
set(_brake_, opt.brake);
set(_center_, !!opt.orbital);
set(_tempo_, opt.tempo / ($.reel.lazy? opt.laziness : 1));
set(_opening_ticks_, 0);
set(_backup_, {
src: src,
style: styles || __
});
pool.bind(_tick_, on.tick);
cleanup.call(e);
t.trigger('start');
},
teardown: function(e){
/*
- unbinds events, erases all state data
- reconstructs the original DOM element
*/
// get rid of Reel's own events
t.unbind(ns).unbind(on);
var
events= t.data('events'),
// clone & restore the original
$original= t.clone()
.attr(t.data(_backup_))
.css({ background: 'transparent' })
.removeClass(klass).addClass(get(_classes_));
// clone original events (inspired by Brandon Aaron's copyEvents plugin)
for (var type in events) $.each(events[type], function(ix, handler){
// for this we need the 1.4.2+ version
$original.bind(type+'.'+handler.namespace, handler.handler, handler.data);
});
$('img:'+_hidden_, t.parent()).remove();
remove_instance(t);
// replace stage with the original
$(get(_stage_)).before($original).detach();
no_bias();
pool
.unbind(_tick_, on.tick)
.unbind(_tick_, on.opening_tick);
stage_pool
.unbind(_mouseup_).unbind(_mousemove_);
cleanup.call(e);
},
start: function(e){
/*
- binds all mouse/touch events (namespaced)
- prepares stage overlay elements
- preloads images if needed
*/
var
space= get(_dimensions_),
frames= get(_frames_),
resolution= max(frames, get(_steps_)),
fraction= set(_fraction_, 1 / resolution * ((opt.step || opt.frame) - 1)),
frame= set(_frame_, fraction * frames + 1),
loaded= 0,
id= t.attr('id'),
$overlay= t.parent(),
$hi= $(_div_tag_, { 'class': hi_klass,
css: { position: _absolute_, left: 0, top: 0, width: space.x, height: space.y, background: _hex_black_, opacity: 0 }
}).appendTo($overlay),
area= set(_area_, $(opt.area || $hi ))
if ($.reel.touchy){
// workaround for downsizing-sprites-bug-in-iPhoneOS inspired by Katrin Ackermann
t.css({ WebkitUserSelect: 'none', WebkitBackgroundSize: opt.images.length
? 'auto'
: (get(_stitched_) && get(_stitched_)+'px '+space.y+'px')
|| (space.x * opt.footage)+'px '+(space.y * get(_rows_) * (opt.rows || 1) * (opt.directional? 2:1))+'px'
});
area
.bind(_touchstart_, function(e){ t.trigger('down', [finger(e).clientX, finger(e).clientY, true]); })
.bind(_touchmove_, function(e){ t.trigger('slide', [finger(e).clientX, finger(e).clientY, true]); return !(opt.rows > 1 || opt.orbital || get(_reeling_)) })
.bind(_touchend_, function(e){ t.trigger('up', [true]); return false })
.bind(_touchcancel_, function(e){ t.trigger('up', [true]); return false })
}else{
area
.css({ cursor: 'url('+drag_cursor+'), '+failsafe_cursor })
.bind(_mousewheel_, function(e, delta){ t.trigger('wheel', [delta]); return false })
.bind(_dblclick_, function(e){ t.trigger('play') })
.bind(opt.clickfree ? _mouseenter_ : _mousedown_, function(e){ t.trigger('down', [e.clientX, e.clientY]); return false })
.bind(opt.clickfree ? _mouseleave_ : '', function(e){ t.trigger('up'); return false })
.disableTextSelect();
}
(opt.hint) && area.attr(_title_, opt.hint);
opt.monitor && $overlay.append($monitor= $(_div_tag_, {
'class': monitor_klass,
css: { position: _absolute_, left: 0, top: 0 }
})) || ($monitor= $());
opt.indicator && $overlay.append(indicator('x'));
opt.rows > 1 && opt.indicator && $overlay.append(indicator('y'));
t.trigger('preload');
},
preload: function(e){
/*
- preloads all frames and sprites
*/
var
space= get(_dimensions_),
$overlay= t.parent(),
image= get(_image_),
images= opt.images,
preload= !images.length ? [image] : new Array().concat(images),
img_tag= t[0],
img_frames= img_tag.frames= preload.length,
img_preloaded= img_tag.preloaded= 0
t.trigger('stop');
$overlay.append($preloader= $(_div_tag_, { 'class': preloader_klass,
css: {
position: _absolute_,
left: 0,
top: space.y - opt.preloader,
height: opt.preloader,
overflow: _hidden_,
backgroundColor: _hex_black_
}
}));
while(preload.length){
var
uri= opt.path+preload.shift(),
$img= $(new Image()).hide().bind('load'+ns, function update_preloader(){
img_tag.preloaded++
$(this).unbind(ns);
$preloader.css({ width: 1 / img_tag.frames * img_tag.preloaded * space.x })
if (img_tag.frames == img_tag.preloaded){
$preloader.remove();
images.length || t.css({ backgroundImage: url(opt.path+image) });
t
.attr({ src: transparent })
.trigger(opt.rows > 1 && !opt.stitched ? 'rowChange' : 'frameChange')
.trigger('loaded')
.trigger('opening');
cleanup.call(e);
}
});
$overlay.append($img);
// The actual loading of the image is done asynchronously
setTimeout((function($img, uri){ return function(){ $img.attr({ src: uri }) } })($img, uri), 0);
}
},
tick: function(e){
/*
- triggered by pool's `tick.reel` event
- keeps track of operated and braked statuses
- decreases inertial velocity by braking
*/
var
velocity= get(_velocity_)
if (braking) var
braked= lofi(velocity - (get(_brake_) / leader(_tempo_) * braking)),
done= velocity * braked <= 0 || velocity < abs(braked),
velocity= !done && set(_velocity_, velocity > abs(get(_speed_)) ? braked : (braking= operated= 0))
$monitor.text(get(opt.monitor));
velocity && braking++;
operated && operated++;
to_bias(0);
slidable= true;
if (operated && !velocity) return cleanup.call(e);
if (get(_clicked_)) return cleanup.call(e, unidle());
var
backwards= get(_cwish_) * negative_when(1, get(_backwards_)),
step= (get(_stopped_) ? velocity : abs(get(_speed_)) + velocity) / leader(_tempo_),
was= get(_fraction_),
fraction= set(_fraction_, was - step * backwards)
cleanup.call(e);
if (fraction == was) return;
t.trigger('fractionChange');
},
opening: function(e){
/*
- initiates opening animation
- or simply plays the reel when without opening
*/
var
speed= opt.entry || opt.speed,
end= get(_fraction_),
duration= opt.opening,
start= set(_fraction_, end - speed * opt.opening),
ticks= set(_opening_ticks_, duration * leader(_tempo_))
pool.bind(_tick_, on.opening_tick);
},
opening_tick: function(e){
/*
- ticker listener dedicated to opening animation
*/
var
speed= opt.entry || opt.speed,
step= speed / leader(_tempo_) * (opt.cw? -1:1),
was= get(_fraction_),
fraction= set(_fraction_, lofi(was + step)),
ticks= set(_opening_ticks_, get(_opening_ticks_) - 1)
t.trigger('fractionChange');
cleanup.call(e);
if (ticks > 1) return;
pool.unbind(_tick_, on.opening_tick);
delay_play();
},
play: function(e, direction){
var
playing= set(_playing_, true),
stopped= set(_stopped_, !playing)
idle();
cleanup.call(e);
},
pause: function(e){
var
playing= set(_playing_, false)
unidle();
cleanup.call(e);
},
stop: function(e){
var
stopped= set(_stopped_, true),
playing= set(_playing_, !stopped)
cleanup.call(e);
},
down: function(e, x, y, touched){
/*
- starts the dragging operation by binding dragging events to the pool
*/
if (opt.draggable){
var
clicked= set(_clicked_, get(_frame_)),
velocity= set(_velocity_, 0),
origin= last= recenter_mouse(x, y, get(_fraction_), get(_revolution_), get(_row_))
unidle();
no_bias();
if (!touched){
stage_pool
.css({ cursor: url(drag_cursor_down)+', '+failsafe_cursor })
.bind(_mousemove_, function(e){ t.trigger('slide', [e.clientX, e.clientY]); cleanup.call(e); return false })
opt.clickfree || stage_pool.bind(_mouseup_, function(e){ t.trigger('up'); cleanup.call(e) })
}
}
cleanup.call(e);
},
up: function(e, touched){
/*
- ends dragging operation by calculating velocity by summing the bias
- unbinds dragging events from pool
- resets the mouse cursor
*/
if (!opt.draggable) return cleanup.call(e);
var
clicked= set(_clicked_, false),
reeling= set(_reeling_, false),
velocity= set(_velocity_, !opt.throwable ? 0 : abs(bias[0] + bias[1]) / 60),
brakes= braking= velocity ? 1 : 0
velocity ? idle() : unidle();
no_bias();
!touched
&& stage_pool.unbind(_mouseup_).unbind(_mousemove_)
&& get(_area_).css({ cursor: url(drag_cursor)+', '+failsafe_cursor });
cleanup.call(e);
},
slide: function(e, x, y, touched){
/*
- calculates the X 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
- builds inertial motion bias
- (`slide` was originally `drag` which conflicted with MSIE)
*/
if (opt.draggable && slidable){
// by checking slidable sync with the ticker tempo is achieved
slidable= false;
unidle();
var
delta= { x: x - last.x, y: y - last.y }
if (abs(delta.x) > 0 || abs(delta.y) > 0){
last= { x: x, y: y };
var
revolution= get(_revolution_),
origin= get(_clicked_location_),
vertical= get(_vertical_),
fraction= set(_fraction_, graph(vertical ? y - origin.y : x - origin.x, get(_clicked_on_), revolution, get(_lo_), get(_hi_), get(_cwish_))),
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 (opt.orbital && get(_center_)) var
vertical= set(_vertical_, abs(y - origin.y) > abs(x - origin.x)),
origin= recenter_mouse(x, y, fraction, revolution, get(_row_))
if (opt.rows > 1) var
space_y= get(_dimensions_).y,
start= get(_clicked_row_),
lo= - start * space_y,
row= set(_row_, lofi($.reel.math.envelope(y - origin.y, start, space_y, lo, lo + space_y, -1)))
var
origin= !(fraction % 1) && !opt.loops && recenter_mouse(x, y, fraction, revolution, get(_row_))
t.trigger('fractionChange');
}
}
cleanup.call(e);
},
wheel: function(e, distance){
/*
- calculates wheel input delta and adjusts fraction using the graph
- recenters the "drag" each and every time
- detects motion direction
- nullifies the velocity
*/
if (!opt.wheelable) return cleanup.call(e);
var
delta= ceil(sqrt(abs(distance)) / 2),
delta= negative_when(delta, distance > 0),
revolution= 0.2 * get(_revolution_), // Wheel's revolution is just 20 % of full revolution
origin= recenter_mouse(undefined, undefined, get(_fraction_), revolution, get(_row_)),
fraction= set(_fraction_, graph(delta, get(_clicked_on_), revolution, get(_lo_), get(_hi_), get(_cwish_))),
backwards= delta && set(_backwards_, delta < 0),
velocity= set(_velocity_, 0)
unidle();
cleanup.call(e);
t.trigger('fractionChange');
return false;
},
fractionChange: function(e, fraction){
/*
- normalizes given fraction (if any) - loop/limit and round
- calculates and changes sprite frame
- for non-looping panoramas
- keeps track of ticks spent on edge
- reverses motion direction if too long
*/
var
fraction= !fraction ? get(_fraction_) : set(_fraction_, fraction),
fraction= opt.loops ? fraction - floor(fraction) : min_max(0, 1, fraction),
fraction= set(_fraction_, lofi(fraction)),
was= get(_frame_),
frame= set(_frame_, 1 + floor(fraction / get(_bit_))),
orbital= opt.orbital,
center= set(_center_, !!orbital && (frame <= orbital || frame >= opt.footage - orbital + 2))
if (!opt.loops && opt.rebound) var
edgy= !operated && !(fraction % 1) ? on_edge++ : (on_edge= 0),
bounce= on_edge >= opt.rebound * 1000 / leader(_tempo_),
backwards= bounce && set(_backwards_, !get(_backwards_))
var
space= get(_dimensions_),
travel= (get(_vertical_) ? space.y : space.x) - opt.indicator,
indicator= min_max(0, travel, round($.reel.math.interpolate(fraction, -1, travel+2))),
indicator= !opt.cw || opt.stitched ? indicator : travel - indicator,
$indicator= $(dot(indicator_klass+'.x'), get(_stage_)).css(get(_vertical_) ? { left: 0, top: indicator } : { left: indicator, top: space.y - opt.indicator });
if (opt.rows > 1) var
ytravel= get(_dimensions_).y - opt.indicator,
yindicator= min_max(0, ytravel, round($.reel.math.interpolate(get(_row_), -1, ytravel+2))),
$yindicator= $(dot(indicator_klass+'.y'), get(_stage_)).css({ top: yindicator })
if (frame == was && frame != 1) return cleanup.call(e);
t.trigger(opt.rows > 1 ? 'rowChange' : 'frameChange');
cleanup.call(e);
},
rowChange: function(e, row){
/*
- recalculates frame from fraction in order to have fresh unshifted value
- shifts the stored frame to a desired row
*/
var
frame= floor(get(_fraction_) / get(_bit_)) + 1,
row= set(_row_, min_max(0, 1, lofi(row != undefined ? (row-1) / (opt.rows-1) : get(_row_)))),
frame= set(_frame_, frame + (opt.rows <= 1 ? 0 : round(row * (opt.rows - 1)) * opt.frames))
cleanup.call(e);
t.trigger('frameChange');
},
frameChange: function(e, frame){
/*
- rounds given frame (if any) and calculates fraction using it
- calculates sprite background position shift and applies it
or changes sprite image
- adjusts indicator position
*/
var
fraction= !frame ? get(_fraction_) : set(_fraction_, lofi(get(_bit_) * (frame-1))),
frame= set(_frame_, round(frame ? frame : get(_frame_))),
images= opt.images,
footage= opt.footage,
space= get(_dimensions_),
horizontal= opt.horizontal
if (get(_vertical_)) var
frame= opt.inversed ? footage + 1 - frame : frame,
frame= frame + footage
if (images.length){
var
sprite= images[frame - 1]
t.attr({ src: opt.path+sprite })
}else{
if (!opt.stitched) var
minor= (frame % footage) - 1,
minor= minor < 0 ? footage - 1 : minor,
major= floor((frame - 0.1) / footage),
major= major + (opt.rows > 1 ? 0 : (get(_backwards_) ? 0 : get(_rows_))),
spacing= get(_spacing_),
a= major * ((horizontal ? space.y : space.x) + spacing),
b= minor * ((horizontal ? space.x : space.y) + spacing),
shift= images.length ? [0, 0] : horizontal ? [-b + _px_, -a + _px_] : [-a + _px_, -b + _px_]
else var
x= round(fraction * get(_stitched_travel_)),
y= 0,
shift= [-x + _px_, y + _px_]
t.css({ backgroundPosition: shift.join(___) })
}
cleanup.call(e);
}
},
// Garbage clean-up facility called by every event
cleanup= function(pass){ ie || delete this; return pass },
// User idle control
operated,
braking= 0,
idle= function(){ return operated= 0 },
unidle= function(){
clearTimeout(delay);
pool.unbind(_tick_, on.opening_tick);
t.trigger('play');
return operated= -opt.timeout * leader(_tempo_)
},
delay,
// Triggers "play" delayed or immediate play
delay_play= function(){
delay= setTimeout(function play(){
t.trigger('play');
}, opt.delay * 1000 || 0);
},
$monitor,
$preloader,
indicator= function(axis){
return $(_div_tag_, {
'class': [indicator_klass, axis].join(___),
css: {
width: opt.indicator,
height: opt.indicator,
overflow: _hidden_,
top: get(_dimensions_).y - opt.indicator,
left: 0,
position: _absolute_,
backgroundColor: _hex_black_
}
})
},
// 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'],
// Resets the interaction graph's zero point
recenter_mouse= function(x, y, fraction, revolution, row){
set(_clicked_on_, fraction);
set(_clicked_row_, row);
set(_lo_, opt.loops ? 0 : - fraction * revolution);
set(_hi_, opt.loops ? revolution : revolution - fraction * revolution);
return x && set(_clicked_location_, { x: x, y: y }) || undefined
},
slidable= true,
stage_pool= $.browser.opera ? pool : $.unique(pool.add(window.top.document))
on.setup();
});
ticker= ticker || (function tick(){
var
start= +new Date(),
tempo= leader(_tempo_);
if (tempo){
pool.trigger(_tick_);
$.reel.cost= (+new Date() + $.reel.cost - start) / 2;
return ticker= setTimeout(tick, max(4, 1000 / tempo - $.reel.cost));
}else{
return ticker= undefined
}
})();
return $(instances);
}
// Mathematics core
$.reel.math= {
envelope: function(x, start, revolution, lo, hi, cwness){
return start + max(lo, min(hi, - x * cwness)) / revolution
},
hatch: function(x, start, revolution, lo, hi, cwness){
var
x= (x < lo ? hi : 0) + x % hi, // Looping
fraction= start + (- x * cwness) / revolution
return fraction - floor(fraction)
},
interpolate: function(fraction, lo, hi){
return lo + fraction * (hi - lo)
}
}
$.reel.touchy= (/iphone|ipod|ipad|android/i).test(navigator.userAgent);
$.reel.lazy= (/iphone|ipod|android/i).test(navigator.userAgent);
$.reel.instances= $();
$.reel.cost= 0;
function leader(key){ return $.reel.instances.length ? $.reel.instances.first().data(key) : null }
$.reel.leader= leader;
function add_instance($instance){ return ($.reel.instances.push($instance[0])) && $instance }
function remove_instance($instance){ return ($.reel.instances= $.reel.instances.not('#'+$instance.attr(_id_))) && $instance }
// Double plugin functions in case plugin is missing
double_for('mousewheel disableTextSelect enableTextSelect'.split(/ /));
// PRIVATE
var
pool= $(document),
browser_version= +$.browser.version.split('.').slice(0,2).join('.'),
ie= $.browser.msie,
knows_data_url= !(ie && browser_version < 8),
failsafe_cursor= 'ew-resize',
ticker,
ticks= { before: 0, now: new Date() },
// HTML classes
klass= 'jquery-reel',
overlay_klass= klass + '-overlay',
indicator_klass= klass + '-indicator',
preloader_klass= klass + '-preloader',
monitor_klass= klass + '-monitor',
hi_klass= klass + '-interface',
// Image resources
transparent= embedded('CAAIAIAAAAAAAAAAACH5BAEAAAAALAAAAAAIAAgAAAIHhI+py+1dAAA7') || cdn('blank.gif'),
drag_cursor= embedded('EAAQAJECAAAAAP///////wAAACH5BAEAAAIALAAAAAAQABAAQAI3lC8AeBDvgosQxQtne7yvLWGStVBelXBKqDJpNzLKq3xWBlU2nUs4C/O8cCvU0EfZGUwt19FYAAA7') || cdn('jquery.reel.cursor-drag.gif'),
drag_cursor_down= embedded('EAAQAJECAAAAAP///////wAAACH5BAEAAAIALAAAAAAQABAAQAIslI95EB3MHECxNjBVdE/5b2zcRV1QBabqhwltq41St4hj5konmVioZ6OtEgUAOw==') || cdn('jquery.reel.cursor-drag-down.gif'),
// Shortcuts
round= Math.round, floor= Math.floor, ceil= Math.ceil,
min= Math.min, max= Math.max, abs= Math.abs, sqrt= Math.sqrt,
number= parseInt,
// Storage keys
_area_= 'area', _backup_= 'backup', _backwards_= 'backwards', _bit_= 'bit', _brake_= 'brake', _center_= 'center',
_classes_= 'classes', _clicked_= 'clicked', _clicked_location_= 'clicked_location',
_clicked_on_= 'clicked_on', _clicked_row_= 'clicked_row', _cwish_= 'cwish', _dimensions_= 'dimensions',
_fraction_= 'fraction', _frame_= 'frame', _frames_= 'frames', _hi_= 'hi', _hidden_= 'hidden', _image_= 'image',
_opening_ticks_= 'opening_ticks', _lo_= 'lo', _playing_= 'playing', _reeling_= 'reeling', _revolution_= 'revolution',
_row_= 'row', _rows_= 'rows', _spacing_= 'spacing', _speed_= 'speed', _stage_= 'stage',
_steps_= 'steps', _stitched_= 'stitched', _stitched_travel_= 'stitched_travel', _stopped_= 'stopped',
_tempo_= 'tempo', _velocity_= 'velocity', _vertical_= 'vertical', _wheel_step_= 'wheel_step',
// Events
ns= '.reel',
_dblclick_= 'dblclick'+ns, _mousedown_= 'mousedown'+ns, _mouseenter_= 'mouseenter'+ns,
_mouseleave_= 'mouseleave'+ns, _mousemove_= 'mousemove'+ns, _mouseup_= 'mouseup'+ns,
_mousewheel_= 'mousewheel'+ns, _tick_= 'tick'+ns, _touchcancel_= 'touchcancel'+ns,
_touchend_= 'touchend'+ns, _touchstart_= 'touchstart'+ns, _touchmove_= 'touchmove'+ns,
// Various string primitives
__= '', ___= ' ', _absolute_= 'absolute', _div_= 'div', _div_tag_= tag(_div_),
_height_= 'height', _hex_black_= '#000', _id_= 'id', _img_= 'img', _px_= 'px', _src_= 'src',
_title_= 'title', _width_= 'width'
// Helpers
function embedded(image){ return knows_data_url && 'data:image/gif;base64,R0lGODlh'+image }
function tag(string){ return '<' + string + '/>' }
function dot(string){ return '.' + string }
function cdn(path){ return 'http://code.vostrel.cz/' + path }
function url(location){ return 'url(' + location + ')' }
function lofi(number){ return +number.toFixed(4) }
function min_max(minimum, maximum, number){ return max(minimum, min(maximum, number)) }
function double_for(methods){ $.each(methods, pretend);
function pretend(){ if (!$.fn[this]) $.fn[this]= function(){ return this }}
}
function negative_when(value, condition){ return abs(value) * (condition ? -1 : 1) }
function finger(e){ return e.originalEvent.touches[0] }
})(jQuery, window, document);