1 /*! |
|
2 * jQuery.scrollTo |
|
3 * Copyright (c) 2007 Ariel Flesler - aflesler ○ gmail • com | https://github.com/flesler |
|
4 * Licensed under MIT |
|
5 * https://github.com/flesler/jquery.scrollTo |
|
6 * @projectDescription Lightweight, cross-browser and highly customizable animated scrolling with jQuery |
|
7 * @author Ariel Flesler |
|
8 * @version 2.1.2 |
|
9 */ |
|
10 ;(function(factory) { |
|
11 'use strict'; |
|
12 if (typeof define === 'function' && define.amd) { |
|
13 // AMD |
|
14 define(['jquery'], factory); |
|
15 } else if (typeof module !== 'undefined' && module.exports) { |
|
16 // CommonJS |
|
17 module.exports = factory(require('jquery')); |
|
18 } else { |
|
19 // Global |
|
20 factory(jQuery); |
|
21 } |
|
22 })(function($) { |
|
23 'use strict'; |
|
24 |
|
25 var $scrollTo = $.scrollTo = function(target, duration, settings) { |
|
26 return $(window).scrollTo(target, duration, settings); |
|
27 }; |
|
28 |
|
29 $scrollTo.defaults = { |
|
30 axis:'xy', |
|
31 duration: 0, |
|
32 limit:true |
|
33 }; |
|
34 |
|
35 function isWin(elem) { |
|
36 return !elem.nodeName || |
|
37 $.inArray(elem.nodeName.toLowerCase(), ['iframe','#document','html','body']) !== -1; |
|
38 } |
|
39 |
|
40 $.fn.scrollTo = function(target, duration, settings) { |
|
41 if (typeof duration === 'object') { |
|
42 settings = duration; |
|
43 duration = 0; |
|
44 } |
|
45 if (typeof settings === 'function') { |
|
46 settings = { onAfter:settings }; |
|
47 } |
|
48 if (target === 'max') { |
|
49 target = 9e9; |
|
50 } |
|
51 |
|
52 settings = $.extend({}, $scrollTo.defaults, settings); |
|
53 // Speed is still recognized for backwards compatibility |
|
54 duration = duration || settings.duration; |
|
55 // Make sure the settings are given right |
|
56 var queue = settings.queue && settings.axis.length > 1; |
|
57 if (queue) { |
|
58 // Let's keep the overall duration |
|
59 duration /= 2; |
|
60 } |
|
61 settings.offset = both(settings.offset); |
|
62 settings.over = both(settings.over); |
|
63 |
|
64 return this.each(function() { |
|
65 // Null target yields nothing, just like jQuery does |
|
66 if (target === null) return; |
|
67 |
|
68 var win = isWin(this), |
|
69 elem = win ? this.contentWindow || window : this, |
|
70 $elem = $(elem), |
|
71 targ = target, |
|
72 attr = {}, |
|
73 toff; |
|
74 |
|
75 switch (typeof targ) { |
|
76 // A number will pass the regex |
|
77 case 'number': |
|
78 case 'string': |
|
79 if (/^([+-]=?)?\d+(\.\d+)?(px|%)?$/.test(targ)) { |
|
80 targ = both(targ); |
|
81 // We are done |
|
82 break; |
|
83 } |
|
84 // Relative/Absolute selector |
|
85 targ = win ? $(targ) : $(targ, elem); |
|
86 /* falls through */ |
|
87 case 'object': |
|
88 if (targ.length === 0) return; |
|
89 // DOMElement / jQuery |
|
90 if (targ.is || targ.style) { |
|
91 // Get the real position of the target |
|
92 toff = (targ = $(targ)).offset(); |
|
93 } |
|
94 } |
|
95 |
|
96 var offset = $.isFunction(settings.offset) && settings.offset(elem, targ) || settings.offset; |
|
97 |
|
98 $.each(settings.axis.split(''), function(i, axis) { |
|
99 var Pos = axis === 'x' ? 'Left' : 'Top', |
|
100 pos = Pos.toLowerCase(), |
|
101 key = 'scroll' + Pos, |
|
102 prev = $elem[key](), |
|
103 max = $scrollTo.max(elem, axis); |
|
104 |
|
105 if (toff) {// jQuery / DOMElement |
|
106 attr[key] = toff[pos] + (win ? 0 : prev - $elem.offset()[pos]); |
|
107 |
|
108 // If it's a dom element, reduce the margin |
|
109 if (settings.margin) { |
|
110 attr[key] -= parseInt(targ.css('margin'+Pos), 10) || 0; |
|
111 attr[key] -= parseInt(targ.css('border'+Pos+'Width'), 10) || 0; |
|
112 } |
|
113 |
|
114 attr[key] += offset[pos] || 0; |
|
115 |
|
116 if (settings.over[pos]) { |
|
117 // Scroll to a fraction of its width/height |
|
118 attr[key] += targ[axis === 'x'?'width':'height']() * settings.over[pos]; |
|
119 } |
|
120 } else { |
|
121 var val = targ[pos]; |
|
122 // Handle percentage values |
|
123 attr[key] = val.slice && val.slice(-1) === '%' ? |
|
124 parseFloat(val) / 100 * max |
|
125 : val; |
|
126 } |
|
127 |
|
128 // Number or 'number' |
|
129 if (settings.limit && /^\d+$/.test(attr[key])) { |
|
130 // Check the limits |
|
131 attr[key] = attr[key] <= 0 ? 0 : Math.min(attr[key], max); |
|
132 } |
|
133 |
|
134 // Don't waste time animating, if there's no need. |
|
135 if (!i && settings.axis.length > 1) { |
|
136 if (prev === attr[key]) { |
|
137 // No animation needed |
|
138 attr = {}; |
|
139 } else if (queue) { |
|
140 // Intermediate animation |
|
141 animate(settings.onAfterFirst); |
|
142 // Don't animate this axis again in the next iteration. |
|
143 attr = {}; |
|
144 } |
|
145 } |
|
146 }); |
|
147 |
|
148 animate(settings.onAfter); |
|
149 |
|
150 function animate(callback) { |
|
151 var opts = $.extend({}, settings, { |
|
152 // The queue setting conflicts with animate() |
|
153 // Force it to always be true |
|
154 queue: true, |
|
155 duration: duration, |
|
156 complete: callback && function() { |
|
157 callback.call(elem, targ, settings); |
|
158 } |
|
159 }); |
|
160 $elem.animate(attr, opts); |
|
161 } |
|
162 }); |
|
163 }; |
|
164 |
|
165 // Max scrolling position, works on quirks mode |
|
166 // It only fails (not too badly) on IE, quirks mode. |
|
167 $scrollTo.max = function(elem, axis) { |
|
168 var Dim = axis === 'x' ? 'Width' : 'Height', |
|
169 scroll = 'scroll'+Dim; |
|
170 |
|
171 if (!isWin(elem)) |
|
172 return elem[scroll] - $(elem)[Dim.toLowerCase()](); |
|
173 |
|
174 var size = 'client' + Dim, |
|
175 doc = elem.ownerDocument || elem.document, |
|
176 html = doc.documentElement, |
|
177 body = doc.body; |
|
178 |
|
179 return Math.max(html[scroll], body[scroll]) - Math.min(html[size], body[size]); |
|
180 }; |
|
181 |
|
182 function both(val) { |
|
183 return $.isFunction(val) || $.isPlainObject(val) ? val : { top:val, left:val }; |
|
184 } |
|
185 |
|
186 // Add special hooks so that window scroll properties can be animated |
|
187 $.Tween.propHooks.scrollLeft = |
|
188 $.Tween.propHooks.scrollTop = { |
|
189 get: function(t) { |
|
190 return $(t.elem)[t.prop](); |
|
191 }, |
|
192 set: function(t) { |
|
193 var curr = this.get(t); |
|
194 // If interrupt is true and user scrolled, stop animating |
|
195 if (t.options.interrupt && t._last && t._last !== curr) { |
|
196 return $(t.elem).stop(); |
|
197 } |
|
198 var next = Math.round(t.now); |
|
199 // Don't waste CPU |
|
200 // Browsers don't render floating point scroll |
|
201 if (curr !== next) { |
|
202 $(t.elem)[t.prop](next); |
|
203 t._last = this.get(t); |
|
204 } |
|
205 } |
|
206 }; |
|
207 |
|
208 // AMD requirement |
|
209 return $scrollTo; |
|
210 }); |
|