PageSourceSearch

https://www.ecpat.org.tw/Scripts/movingboxes/js/jquery.movingboxes.js

js ecpat.org.tw collected 2026-09-24 07:16:22 UTC 21,845 bytes, 551 lines download raw bytes

1/*!
2 * Moving Boxes v2.3.4
3 * by Chris Coyier
4 * http://css-tricks.com/moving-boxes/
5 */
6/*jshint browser:true, jquery:true */
7;(function($){
8	"use strict";
9	$.movingBoxes = function(el, options){
10		// To avoid scope issues, use 'base' instead of 'this'
11		// to reference this class from internal events and functions.
12		var o, base = this;
13
14		// Access to jQuery and DOM versions of element
15		base.$el = $(el).addClass('mb-slider');
16		base.el = el;
17
18		// Add a reverse reference to the DOM object
19		base.$el.data('movingBoxes', base);
20
21		base.init = function(){
22			base.options = o = $.extend({}, $.movingBoxes.defaultOptions, options);
23
24			// Setup formatting (to reduce the amount of initial HTML)
25			base.$el.wrap('<div class="movingBoxes mb-wrapper"><div class="mb-scroll" /></div>');
26
27			// defaults
28			base.$window = base.$el.parent(); // mb-scroll
29			base.$wrap = base.$window.parent() // mb-wrapper
30				.prepend('<a class="mb-scrollButtons mb-left"></a>')
31				.append('<a class="mb-scrollButtons mb-right"></a><div class="mb-left-shadow"></div><div class="mb-right-shadow"></div>');
32
33			base.$panels = base.$el.children().addClass('mb-panel');
34			base.runTime = $('.mb-slider').index(base.$el) + 1; // Get index (run time) of this slider on the page
35			base.regex = new RegExp('slider' + base.runTime + '=(\\d+)', 'i'); // hash tag regex
36
37			base.initialized = false;
38			base.currentlyMoving = false;
39			base.curPanel = (o.initAnimation) ? 1 : base.getHash() || o.startPanel;
40			// save original slider width
41			base.width = (o.width) ? parseInt(o.width,10) : base.$el.width();
42			// save panel width, o.panelWidth originally a fraction (0.5 of o.width) if defined, or get first panel width
43			// now can be set after initialization to resize using fraction (value <= 2) or px (all values > 2)
44			base.pWidth = (o.panelWidth) ? (o.panelWidth <=2 ? o.panelWidth * base.width : o.panelWidth) : base.$panels.eq(0).width();
45
46			// Set up click on left/right arrows
47			base.$left = base.$wrap.find('.mb-left').click(function(){
48				base.goBack();
49				return false;
50			});
51			base.$right = base.$wrap.find('.mb-right').click(function(){
52				base.goForward();
53				return false;
54			});
55			// code to run to update MovingBoxes when the number of panels change
56			base.update({}, false);
57			// make sure current panel is centered
58			base.setWrap(base.curPanel);
59			// go to clicked panel
60			base.$el.delegate('.mb-panel', 'click', function(e){
61				if (!$(this).hasClass(o.currentPanel)) {
62					e.preventDefault();  // prevent non-current panel links from working
63					base.change( base.$panels.index($(this)) + base.adj, {}, true );
64				}
65			});
66
67			// Activate moving box on click or when an internal link obtains focus
68			base.$wrap.click(function(){
69				if (!base.$wrap.hasClass('mb-active-slider')) {
70					base.active();
71				}
72			});
73			base.$panels.delegate('a', 'focus' ,function(e){
74				e.preventDefault();
75				// focused link centered in moving box
76				var loc = base.$panels.index($(this).closest('.mb-panel')) + base.adj;
77				if (loc !== base.curPanel){
78					base.change( loc, {}, true );
79				}
80			});
81
82			// Add keyboard navigation
83			$(document).keyup(function(e){
84				// ignore arrow/space keys if inside a form element
85				if (e.target.tagName.match('TEXTAREA|INPUT|SELECT')) { return; }
86				switch (e.which) {
87					case 39: case 32: // right arrow & space
88						if (base.$wrap.is('.mb-active-slider')){
89							base.goForward();
90						}
91						break;
92					case 37: // left arrow
93						if (base.$wrap.is('.mb-active-slider')){
94							base.goBack();
95						}
96						break;
97				}
98			});
99
100			// Bind Events
101			$.each('preinit initialized initChange beforeAnimation completed'.split(' '), function(i,evt){
102				if ($.isFunction(o[evt])){
103					base.$el.bind(evt + '.movingBoxes', o[evt]);
104				}
105			});
106
107			base.$el.trigger( 'preinit.movingBoxes', [ base, base.curPanel ] );
108
109		};
110
111		// update the panel, flag is used to prevent events from firing
112		base.update = function(callback, flag){
113
114			// Infinite loop
115			base.$el.children('.cloned').remove();
116			base.$panels = base.$el.children();
117			base.adj = (o.wrap && base.$panels.length > 1) ? 0 : 1; // count adjustment for infinite panels
118
119			base.width = (o.width) ? parseInt(o.width,10) : base.width;
120			base.$wrap.css('width', base.width); // set wrapper width
121
122			if (o.wrap && base.$panels.length > 1) {
123				base.$el.prepend( base.$panels.filter(':last').clone().addClass('cloned') );
124				base.$el.append( base.$panels.filter(':first').clone().addClass('cloned') );
125				base.$el.find('.cloned').each(function(){
126					// disable all focusable elements in cloned panels to prevent shifting the panels by tabbing
127					$(this).find('a,input,textarea,select,button,area').removeAttr('name').attr('disabled', 'disabled');
128					$(this).find('[id]').andSelf().removeAttr('id');
129				});
130			}
131
132			// Set up panes & content sizes
133			// defined $panels again to include cloned panels
134			base.$panels = base.$el.children()
135				.addClass('mb-panel')
136				// inner wrap of each panel
137				.each(function(){
138					if ($(this).find('.mb-inside').length === 0) {
139						$(this).wrapInner('<div class="mb-inside" />');
140					}
141				});
142			base.totalPanels = base.$panels.filter(':not(.cloned)').length; // don't include cloned panels in total
143			// in case current panel no longer exists
144			if (base.totalPanels <= 1) { base.curPanel = 1; }
145
146			base.setSizes(flag);
147
148			base.buildNav();
149
150			base.change(base.curPanel, callback, flag); // initialize from first panel... then scroll to start panel
151
152			// check panel height after all images load
153			base.imagesLoaded(function(){
154				base.setSizes(false);
155				base.setWrap(base.curPanel);
156
157				// animate to chosen start panel - starting from the first panel makes it look better
158				if (!base.initialized){
159					setTimeout(function(){
160						base.initialized = true;
161						base.change(base.getHash() || o.startPanel, {}, false);
162						base.$el.trigger( 'initialized.movingBoxes', [ base, base.curPanel ] );
163					}, o.speed * 2 );
164				}
165
166			});
167
168		};
169
170		base.setSizes = function(flag){
171			// include padding & margins around the panels
172			base.padding = parseInt(base.$panels.css('padding-left'), 10) + parseInt(base.$panels.css('margin-left'), 10);
173
174			// save 'cur' numbers (current larger panel size), use stored sizes if they exist
175			base.curWidth = (o.panelWidth) ? (o.panelWidth <=2 ? o.panelWidth * base.width : o.panelWidth) : base.pWidth;
176			// save 'reg' (reduced size) numbers
177			base.regWidth = base.curWidth * o.reducedSize;
178			// set image heights so base container height is correctly set
179			base.$panels.css({ width: base.curWidth, fontSize: '1em' }); // make all panels big
180			// save each panel height... script will resize container as needed
181			// make sure current panel css is applied before measuring
182			base.$panels.eq(base.curPanel - base.adj).addClass(o.currentPanel);
183			base.heights = base.$panels.css('height','auto').map(function(i,e){ return $(e).outerHeight(true); }).get();
184
185			base.returnToNormal(base.curPanel, 0); // resize new panel, animation time
186			base.growBigger(base.curPanel, 0, flag);
187			base.updateArrows(base.curPanel);
188
189			// make base container wide enough to contain all the panels
190			base.$el.css({
191				position : 'absolute',
192				// add a bit more width to each box (base.padding *2; then add 1/2 overall width in case only one panel exists)
193				width    : (base.curWidth + base.padding * 2) * base.$panels.length + (base.width - base.curWidth) / 2,
194				height   : Math.max.apply( this, base.heights ) + 10,
195				// add padding so scrollLeft = 0 centers the left-most panel (needed because scrollLeft cannot be < 0)
196				'padding-left' : (base.width - base.curWidth) / 2
197			});
198			base.$window.css({ height : (o.fixedHeight) ? Math.max.apply( this, base.heights ) : base.heights[base.curPanel - base.adj] });
199		};
200
201		// Creates the numbered navigation links
202		base.buildNav = function() {
203			if (base.$nav) {
204				base.$nav.find('.mb-links').empty();
205			} else {
206				base.$nav = $('<div class="mb-controls"><span class="mb-links"></span></div>').appendTo(base.$wrap);
207			}
208			if (o.buildNav && base.totalPanels > 1) {
209				var t, j, a = '', $a;
210				base.$panels.filter(':not(.cloned)').each(function(i){
211					j = i + 1;
212					a = '<a class="mb-link mb-panel' + j + '" href="#"></a>';
213					$a = $(a);
214					// If a formatter function is present, use it
215					if ($.isFunction(o.navFormatter)) {
216						t = o.navFormatter(j, $(this));
217						if (typeof(t) === "string") {
218							$a.html(t);
219						} else {
220							$a = $('<a/>', t);
221						}
222					} else {
223						$a.html(j);
224					}
225					$a
226					.appendTo(base.$nav.find('.mb-links'))
227					.addClass('mb-link mb-panel' + j)
228					.data('index', j);
229				});
230				base.$nav
231					.find('a.mb-link').bind('click', function() {
232						base.change( $(this).data('index') );
233						return false;
234					});
235			}
236		};
237
238		// Resize panels to normal
239		base.returnToNormal = function(num, time){
240			var panels = base.$panels.not(':eq(' + (num - base.adj) + ')').removeClass(o.currentPanel);
241			if (o.reducedSize === 1) {
242				panels.css({ width: base.regWidth }); // excluding fontsize change to prevent video flicker
243			} else {
244				panels.stop(true,false).animate({ width: base.regWidth, fontSize: o.reducedSize + 'em' }, (time === 0) ? 0 : o.speed);
245			}
246		};
247
248		// Zoom in on selected panel
249		base.growBigger = function(num, time, flag){
250			var panels = base.$panels.eq(num - base.adj);
251			if (o.reducedSize === 1) {
252				panels.css({ width: base.curWidth }); // excluding fontsize change to prevent video flicker
253				// time delay prevents click outer panel from following links - fixes issue #67
254				setTimeout(function(){
255					base.completed(num, flag);
256				}, (time === 0) ? 0 : o.speed);
257			} else {
258				panels.stop(true,false).animate({ width: base.curWidth, fontSize: '1em' }, (time === 0) ? 0 : o.speed, function(){
259					// completed event trigger
260					// even though animation is not queued, trigger is here because it is the last animation to complete
261					base.completed(num, flag);
262				});
263			}
264		};
265
266		// instantly center the indicated panel
267		base.setWrap = function(panel){
268			if (base.totalPanels >= 1) {
269				base.growBigger(panel, 0, false);
270				var leftValue = base.$panels.eq(panel - base.adj).position().left - (base.width - base.curWidth) / 2 + base.padding;
271				base.$window.scrollLeft(leftValue);
272			}
273		};
274
275		base.completed = function(num, flag){
276			// add current panel class after animating in case it has sizing parameters
277			var loc = base.$panels.eq(num - base.adj);
278			if (!loc.hasClass('cloned')) { loc.addClass(o.currentPanel); }
279			if (flag !== false) { base.$el.trigger( 'completed.movingBoxes', [ base, num ] ); }
280		};
281
282		// go forward/back
283		base.goForward = function(callback){
284			if (base.initialized) {
285				base.change(base.curPanel + 1, callback);
286			}
287		};
288
289		base.goBack = function(callback){
290			if (base.initialized) {
291				base.change(base.curPanel - 1, callback);
292			}
293		};
294
295		// Change view to display selected panel
296		base.change = function(curPanel, callback, flag){
297
298			if (base.totalPanels < 1) {
299				if (typeof(callback) === 'function') { callback(base); }
300				return;
301			}
302			var ani, leftValue, wrapped = false;
303			flag = flag !== false;
304
305			// check if curPanel is a jQuery selector or object
306			// $('' + curPanel) needed because $(3) = [3], but $('3') = []
307			if ($('' + curPanel).length || (curPanel instanceof $ && $(curPanel).length)) {
308				curPanel = $(curPanel).closest('.mb-panel').index() + base.adj;
309			} else {
310				// make sure it's a number and not a string
311				curPanel = parseInt(curPanel, 10);
312			}
313
314			if (base.initialized && flag) {
315				// make this moving box active
316				if (!base.$wrap.hasClass('mb-active-slider')) { base.active(); }
317				// initChange event - has extra parameter with targeted panel (not cleaned)
318				base.$el.trigger( 'initChange.movingBoxes', [ base, curPanel ] );
319			}
320
321			// Make infinite scrolling work
322			if (o.wrap) {
323				if (curPanel > base.totalPanels) {
324					wrapped = true;
325					curPanel = 1;
326					base.returnToNormal(0, 0);
327					base.setWrap(0);
328				} else if (curPanel === 0) {
329					wrapped = false;
330					curPanel = base.totalPanels;
331					base.setWrap(curPanel + 1);
332				}
333			}
334
335			if ( curPanel < base.adj ) { curPanel = (o.wrap) ? base.totalPanels : 1; }
336			if ( curPanel > base.totalPanels - base.adj ) { curPanel = (o.wrap) ? 1 : base.totalPanels; }
337			// abort if panel is already animating
338			// animation callback needed to clear this flag, but there is no animation before base.initialized is set
339			if (base.curPanel !== curPanel && (!base.currentlyMoving || !base.initialized)) {
340				// set animation flag; animation callback will clear this flag
341				base.currentlyMoving = !o.stopAnimation;
342
343				// center panel in scroll window
344				base.$curPanel = base.$panels.eq(curPanel - base.adj);
345				leftValue = base.$curPanel.position().left - (base.width - base.curWidth) / 2 + base.padding;
346				// when scrolling right, add the difference of the larger current panel width
347				if (base.initialized && (curPanel > base.curPanel || wrapped)) { leftValue -= ( base.curWidth - base.regWidth ); }
348				ani = (o.fixedHeight) ? { scrollLeft : leftValue } : { scrollLeft: leftValue, height: base.heights[curPanel - base.adj] };
349				base.curPanel = curPanel;
350				// before animation trigger
351				if (base.initialized && flag) { base.$el.trigger( 'beforeAnimation.movingBoxes', [ base, curPanel ] ); }
352
353				if (o.delayBeforeAnimate) {
354					// delay starting slide animation
355					setTimeout(function(){
356						base.animateBoxes(curPanel, ani, flag, callback);
357					}, parseInt(o.delayBeforeAnimate, 10) || 0);
358				} else {
359					base.animateBoxes(curPanel, ani, flag, callback);
360				}
361			} else {
362				base.endAnimation();
363			}
364		};
365
366		base.animateBoxes = function(curPanel, ani, flag, callback){
367			// animate the panels
368			base.$window.scrollTop(0).stop(true,false).animate( ani,
369				{
370					queue    : false,
371					duration : o.speed,
372					easing   : o.easing,
373					complete : function(){
374						if (base.initialized) {
375							base.$window.scrollTop(0); // Opera fix - otherwise, it moves the focus link to the middle of the viewport
376						}
377						base.currentlyMoving = false;
378						if (typeof(callback) === 'function') { callback(base); }
379					}
380				}
381			);
382
383			base.returnToNormal(curPanel);
384			base.growBigger(curPanel, o.speed, flag);
385			base.updateArrows(curPanel);
386			if (o.hashTags && base.initialized) { base.setHash(curPanel); }
387			base.endAnimation();
388
389		};
390
391		base.endAnimation = function(){
392			// Update navigation links
393			if (o.buildNav && base.$nav.length) {
394				base.$nav.find('a.mb-link')
395					.removeClass(o.currentPanel)
396					.eq(base.curPanel - 1).addClass(o.currentPanel);
397			}
398		};
399
400		base.updateArrows = function(cur){
401			base.$left.toggleClass(o.disabled, (!o.wrap && cur === base.adj) || base.totalPanels <= 1);
402			base.$right.toggleClass(o.disabled, (!o.wrap && cur === base.totalPanels) || base.totalPanels <= 1);
403		};
404
405		// This method tries to find a hash that matches an ID and slider-X
406		// If either found, it tries to find a matching item
407		// If that is found as well, then it returns the page number
408		base.getHash = function(){
409			var h = window.location.hash,
410				i = h.indexOf('&'),
411				n = h.match(base.regex);
412			// test for "/#/" or "/#!/" used by the jquery address plugin - $('#/') breaks jQuery
413			if (n === null && !/^#&/.test(h) && !/#!?\//.test(h)) {
414				// #quote2&panel1-3&panel3-3
415				h = h.substring(0, (i >= 0 ? i : h.length));
416				// ensure the element is in the same slider
417				n = ($(h).length && $(h).closest('.mb-slider')[0] === base.el) ? $(h).closest('.mb-panel').index() + base.adj : null;
418			} else if (n !== null) {
419				// #&panel1-3&panel3-3
420				n = (o.hashTags) ? parseInt(n[1],10) : null;
421			}
422			return (n > base.totalPanels) ? null : n;
423		};
424
425		// set hash tags
426		base.setHash = function(n){
427			var s = 'slider' + base.runTime + "=",
428				h = window.location.hash;
429			if ( typeof h !== 'undefined' ) {
430				window.location.hash = (h.indexOf(s) > 0) ? h.replace(base.regex, s + n) : h + "&" + s + n;
431			}
432		};
433
434		// Make moving box active (for keyboard navigation)
435		base.active = function(){
436			$('.mb-active-slider').removeClass('mb-active-slider');
437			base.$wrap.addClass('mb-active-slider');
438		};
439
440		// get: var currentPanel = $('.slider').data('movingBoxes').currentPanel();  // returns # of currently selected/enlarged panel
441		// set: var currentPanel = $('.slider').data('movingBoxes').currentPanel(2, function(){ alert('done!'); }); // returns and scrolls to 2nd panel
442		base.currentPanel = function(panel, callback){
443			if (typeof(panel) !== 'undefined') {
444				base.change(panel, callback); // parse in case someone sends a string
445			}
446			return base.curPanel;
447		};
448
449		// based on https://github.com/Mottie/imagesLoaded plugin
450		base.imagesLoaded = function(callback, img) {
451			var i, ic,
452				c = true, // complete flag
453				t = img ? $(img) : base.$panels.find('img'),
454				l = t.length;
455			img = img || []; // array of images that didn't complete
456			for ( i = 0; i < l; i++ ) {
457				if (t[i].tagName === "IMG") {
458					// IE: fileSize property = -1 before image has loaded & if image load error, so if false is returned
459					// 10x, then just assume it's an error & call it complete - it's what Firefox & webkit does
460					ic = ('fileSize' in t[i] && t[i].fileSize < 0 && t[i].count > 10) ? true : t[i].complete;
461					// complete flag, checks previous flag status, complete flag & image height
462					// image height may need to be > 20 (or whatever the line-height is) because the alt text is included
463					c = (c && ic && t[i].height !== 0); // complete flag
464					// save non-complete images for next iteration
465					if (ic === false) {
466						img.push(t[i]);
467						// iteration count for IE
468						t[i].count = (t[i].count || 0) + 1;
469					}
470				}
471			}
472			if (c) {
473				// all complete, run the callback
474				if (typeof callback === "function") { callback(); }
475			} else {
476				// some images not loaded, rinse & repeat
477				setTimeout(function(){
478					base.imagesLoaded(callback, img);
479				}, 200);
480			}
481		};
482
483		// Run initializer
484		base.init();
485	};
486
487	$.movingBoxes.defaultOptions = {
488		// Appearance
489		startPanel   : 1,         // start with this panel
490		reducedSize  : 0.8,       // non-current panel size: 80% of panel size
491		fixedHeight  : false,     // if true, slider height set to max panel height; if false, slider height will auto adjust.
492
493		// Behaviour
494		initAnimation: true,      // if true, movingBoxes will initialize, then animate into the starting slide (if not the first slide)
495		stopAnimation: false,     // if true, movingBoxes will force the animation to complete immediately, if the user selects the next panel
496		hashTags     : true,      // if true, hash tags are enabled
497		wrap         : false,     // if true, the panel will loop through the panels infinitely
498		buildNav     : false,     // if true, navigation links will be added
499		navFormatter : null,      // function which returns the navigation text for each panel
500		easing       : 'swing',   // anything other than "linear" or "swing" requires the easing plugin
501
502		// Times
503		speed              : 500, // animation time in milliseconds
504		delayBeforeAnimate : 0,   // time to delay in milliseconds before MovingBoxes animates to the selected panel
505
506		// Selectors & classes
507		currentPanel : 'current', // current panel class
508		tooltipClass : 'tooltip', // added to the navigation, but the title attribute is blank unless the link text-indent is negative
509		disabled     : 'disabled',// class added to arrows that are disabled (left arrow when on first panel, right arrow on last panel)
510
511		// Callbacks
512		preinit         : null,   // callback after the basic MovingBoxes structure has been built; before "initialized"
513		initialized     : null,   // callback when MovingBoxes has completed initialization; all images loaded
514		initChange      : null,   // callback upon change panel initialization
515		beforeAnimation : null,   // callback before any animation occurs
516		completed       : null    // callback after animation completes
517
518		// deprecated options - but still used to keep the plugin backwards compatible
519		// and allow resizing the overall width and panel width dynamically (i.e. on window resize)
520		// width        : 800,       // overall width of movingBoxes (not including navigation arrows)
521		// panelWidth   : 0.5        // current panel width adjusted to 50% of overall width
522
523	};
524
525	$.fn.movingBoxes = function(options, callback, flag){
526		var mb;
527		return this.each(function(){
528			mb = $(this).data('movingBoxes');
529			// initialize the slider but prevent multiple initializations
530			if ((typeof(options)).match('object|undefined')){
531				if (mb && options instanceof $ && options.length) {
532					// pass a jQuery object to change panels
533					mb.change(options, callback, flag);
534				} else if (mb) {
535					mb.update(callback, flag);
536				} else {
537					(new $.movingBoxes(this, options));
538				}
539			} else if (mb) {
540				// page #, autoplay, one time callback, if flag is false then no events triggered and animation time = 0
541				mb.change(options, callback, flag);
542			}
543		});
544	};
545
546	// Return the movingBoxes object
547	$.fn.getMovingBoxes = function(){
548		return this.data('movingBoxes');
549	};
550
551})(jQuery);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.