OSDN Git Service

update iscroll.js to 3.2.1
[keitairc/keitairc.git] / data / public / iscroll / iscroll.js
1 /**
2  * 
3  * Find more about the scrolling function at
4  * http://cubiq.org/scrolling-div-for-mobile-webkit-turns-3/16
5  *
6  * Copyright (c) 2009 Matteo Spinelli, http://cubiq.org/
7  * Released under MIT license
8  * http://cubiq.org/dropbox/mit-license.txt
9  * 
10  * Version 3.2.1 - Last updated: 2010.06.03
11  * 
12  */
13
14 (function(){
15
16 function iScroll (el, options) {
17         this.element = typeof el == 'object' ? el : document.getElementById(el);
18         this.wrapper = this.element.parentNode;
19
20         this.element.style.webkitTransitionProperty = '-webkit-transform';
21         this.element.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
22         this.element.style.webkitTransitionDuration = '0';
23         this.element.style.webkitTransform = 'translate3d(0,0,0)';
24
25         // Get options
26         this.options = {
27                 bounce: true,
28                 checkDOMChanges: true,
29                 topOnDOMChanges: false,
30                 hScrollBar: true,
31                 vScrollBar: true
32         };
33         
34         if (typeof options == 'object') {
35                 for (var i in options) {
36                         this.options[i] = options[i];
37                 }
38         }
39
40         this.refresh();
41         
42         this.element.addEventListener('touchstart', this);
43         this.element.addEventListener('touchmove', this);
44         this.element.addEventListener('touchend', this);
45         window.addEventListener('orientationchange', this);
46
47         if (this.options.checkDOMChanges) {
48                 this.element.addEventListener('DOMSubtreeModified', this);
49         }
50 }
51
52 iScroll.prototype = {
53         x: 0,
54         y: 0,
55
56         handleEvent: function (e) {
57                 switch (e.type) {
58                         case 'touchstart': this.onTouchStart(e); break;
59                         case 'touchmove': this.onTouchMove(e); break;
60                         case 'touchend': this.onTouchEnd(e); break;
61                         case 'webkitTransitionEnd': this.onTransitionEnd(e); break;
62                         case 'orientationchange': this.refresh(); break;
63                         case 'DOMSubtreeModified': this.onDOMModified(e); break;
64                 }
65         },
66         
67         onDOMModified: function (e) {
68                 this.refresh();
69                 
70                 if (this.options.topOnDOMChanges && (this.x!=0 || this.y!=0)) {
71                         this.scrollTo(0,0,'0');
72                 }
73         },
74
75         refresh: function () {
76                 this.scrollWidth = this.wrapper.clientWidth;
77                 this.scrollHeight = this.wrapper.clientHeight;
78                 this.maxScrollX = this.scrollWidth - this.element.offsetWidth;
79                 this.maxScrollY = this.scrollHeight - this.element.offsetHeight;
80
81                 var resetX = this.x, resetY = this.y;
82                 if (this.scrollX) {
83                         if (this.maxScrollX >= 0) {
84                                 resetX = 0;
85                         } else if (this.x < this.maxScrollX) {
86                                 resetX = this.maxScrollX;
87                         }
88                 }
89                 if (this.scrollY) {
90                         if (this.maxScrollY >= 0) {
91                                 resetY = 0;
92                         } else if (this.y < this.maxScrollY) {
93                                 resetY = this.maxScrollY;
94                         }
95                 }
96                 if (resetX!=this.x || resetY!=this.y) {
97                         this.scrollTo(resetX,resetY,'0');
98                 }
99
100                 this.scrollX = this.element.offsetWidth > this.scrollWidth ? true : false;
101                 this.scrollY = this.element.offsetHeight > this.scrollHeight ? true : false;
102
103                 // Update horizontal scrollbar
104                 if (this.options.hScrollBar && this.scrollX) {
105                         this.scrollBarX = (this.scrollBarX instanceof scrollbar) ? this.scrollBarX : new scrollbar('horizontal', this.wrapper);
106                         this.scrollBarX.init(this.scrollWidth, this.element.offsetWidth);
107                 } else if (this.scrollBarX) {
108                         this.scrollBarX = this.scrollBarX.remove();
109                 }
110
111                 // Update vertical scrollbar
112                 if (this.options.vScrollBar && this.scrollY) {
113                         this.scrollBarY = (this.scrollBarY instanceof scrollbar) ? this.scrollBarY : new scrollbar('vertical', this.wrapper);
114                         this.scrollBarY.init(this.scrollHeight, this.element.offsetHeight);
115                 } else if (this.scrollBarY) {
116                         this.scrollBarY = this.scrollBarY.remove();
117                 }
118         },
119
120         setPosition: function (x, y) { 
121                 this.x = x !== null ? x : this.x;
122                 this.y = y !== null ? y : this.y;
123
124                 this.element.style.webkitTransform = 'translate3d(' + this.x + 'px,' + this.y + 'px,0)';
125
126                 // Move the scrollbars
127                 if (this.scrollBarX) {
128                         this.scrollBarX.setPosition(this.scrollBarX.maxScroll / this.maxScrollX * this.x);
129                 }
130                 if (this.scrollBarY) {
131                         this.scrollBarY.setPosition(this.scrollBarY.maxScroll / this.maxScrollY * this.y);
132                 }
133         },
134                 
135         onTouchStart: function(e) {
136             if (e.targetTouches.length != 1) {
137                 return false;
138         }
139
140                 e.stopPropagation();
141                 
142                 this.element.style.webkitTransitionDuration = '0';
143                 
144                 if (this.scrollBarX) {
145                         this.scrollBarX.bar.style.webkitTransitionDuration = '0, 250ms';
146                 }
147                 if (this.scrollBarY) {
148                         this.scrollBarY.bar.style.webkitTransitionDuration = '0, 250ms';
149                 }
150
151                 // Check if elem is really where it should be
152                 var theTransform = new WebKitCSSMatrix(window.getComputedStyle(this.element).webkitTransform);
153                 if (theTransform.m41 != this.x || theTransform.m42 != this.y) {
154                         this.setPosition(theTransform.m41, theTransform.m42);
155                 }
156
157                 this.touchStartX = e.touches[0].pageX;
158                 this.scrollStartX = this.x;
159
160                 this.touchStartY = e.touches[0].pageY;
161                 this.scrollStartY = this.y;
162
163                 this.scrollStartTime = e.timeStamp;
164                 this.moved = false;
165         },
166         
167         onTouchMove: function(e) {
168                 if (e.targetTouches.length != 1) {
169                         return false;
170                 }
171
172                 e.preventDefault();
173
174                 var leftDelta = this.scrollX === true ? e.touches[0].pageX - this.touchStartX : 0,
175                         topDelta = this.scrollY === true ? e.touches[0].pageY - this.touchStartY : 0,
176                         newX = this.x + leftDelta,
177                         newY = this.y + topDelta;
178
179                 // Slow down if outside of the boundaries
180                 if (newX > 0 || newX < this.maxScrollX) { 
181                         newX = this.options.bounce ? Math.round(this.x + leftDelta / 4) : this.x;
182                 }
183                 if (newY > 0 || newY < this.maxScrollY) { 
184                         newY = this.options.bounce ? Math.round(this.y + topDelta / 4) : this.y;
185                 }
186
187                 if (this.scrollBarX && !this.scrollBarX.visible) {
188                         this.scrollBarX.show();
189                 }
190                 if (this.scrollBarY && !this.scrollBarY.visible) {
191                         this.scrollBarY.show();
192                 }
193
194                 this.setPosition(newX, newY);
195
196                 this.touchStartX = e.touches[0].pageX;
197                 this.touchStartY = e.touches[0].pageY;
198                 this.moved = true;
199
200                 // Prevent slingshot effect
201                 if( e.timeStamp-this.scrollStartTime > 250 ) {
202                         this.scrollStartX = this.x;
203                         this.scrollStartY = this.y;
204                         this.scrollStartTime = e.timeStamp;
205                 }
206         },
207         
208         onTouchEnd: function(e) {
209                 if (e.targetTouches.length > 0) {
210                         return false;
211                 }
212
213                 if (!this.moved) {
214                         // Find the last touched element
215                         var theTarget = e.changedTouches[0].target;
216                         if (theTarget.nodeType == 3) {
217                                 theTarget = theTarget.parentNode;
218                         }
219                         // Create the fake event
220                         var theEvent = document.createEvent('MouseEvents');
221                         theEvent.initMouseEvent("click", true, true, document.defaultView,
222                                                                         e.detail, e.screenX, e.screenY, e.clientX, e.clientY,
223                                                                         e.ctrlKey, e.altKey, e.shiftKey, e.metaKey,
224                                                                         e.button, e.relatedTarget);
225                         theTarget.dispatchEvent(theEvent);
226                         return false;
227                 }
228                 
229                 var time = e.timeStamp - this.scrollStartTime;
230
231                 var momentumX = this.scrollX === true
232                         ? this.momentum(this.x - this.scrollStartX,
233                                                         time,
234                                                         this.options.bounce ? -this.x + this.scrollWidth/4 : -this.x,
235                                                         this.options.bounce ? this.x + this.element.offsetWidth - this.scrollWidth + this.scrollWidth/4 : this.x + this.element.offsetWidth - this.scrollWidth)
236                         : { dist: 0, time: 0 };
237
238                 var momentumY = this.scrollY === true
239                         ? this.momentum(this.y - this.scrollStartY,
240                                                         time,
241                                                         this.options.bounce ? -this.y + this.scrollHeight/4 : -this.y,
242                                                         this.options.bounce ? this.y + this.element.offsetHeight - this.scrollHeight + this.scrollHeight/4 : this.y + this.element.offsetHeight - this.scrollHeight)
243                         : { dist: 0, time: 0 };
244
245                 if (!momentumX.dist && !momentumY.dist) {
246                         this.resetPosition();
247                         return false;
248                 }
249
250                 var newDuration = Math.max(Math.max(momentumX.time, momentumY.time), 1);                // The minimum animation length must be 1ms
251                 var newPositionX = this.x + momentumX.dist;
252                 var newPositionY = this.y + momentumY.dist;
253
254                 this.element.addEventListener('webkitTransitionEnd', this);
255
256                 this.scrollTo(newPositionX, newPositionY, newDuration + 'ms');
257
258                 // Move the scrollbars
259                 if (this.scrollBarX) {
260                         this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * newPositionX, newDuration + 'ms');       
261                 }               
262                 if (this.scrollBarY) {
263                         this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * newPositionY, newDuration + 'ms');
264                 }
265         },
266         
267         onTransitionEnd: function () {
268                 this.element.removeEventListener('webkitTransitionEnd', this);
269                 this.resetPosition();
270         },
271
272         resetPosition: function () {
273                 var resetX = this.x,
274                         resetY = this.y;
275                 
276                 if (this.x >= 0) {
277                         resetX = 0;
278                 } else if (this.x < this.maxScrollX) {
279                         resetX = this.maxScrollX;
280                 }
281
282                 if (this.y >= 0) {
283                         resetY = 0;
284                 } else if (this.y < this.maxScrollY) {
285                         resetY = this.maxScrollY;
286                 }
287
288                 if (resetX != this.x || resetY != this.y) {
289                         this.scrollTo(resetX, resetY, '500ms');
290
291                         if (this.scrollBarX && resetX != this.x) {
292                                 this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * resetX, '500ms');
293                         }                       
294                         if (this.scrollBarY && resetY != this.y) {
295                                 this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * resetY, '500ms');
296                         }
297                 }
298                 
299                 // Hide the scrollbars
300                 if (this.scrollBarX) {
301                         this.scrollBarX.hide();
302                 }
303                 if (this.scrollBarY) {
304                         this.scrollBarY.hide();
305                 }
306         },
307
308         scrollTo: function (destX, destY, runtime) {
309                 this.element.style.webkitTransitionDuration = runtime || '400ms';
310                 this.setPosition(destX, destY);
311         },
312
313         momentum: function (dist, time, maxDistUpper, maxDistLower) {
314                 var friction = 0.1,
315                         deceleration = 1.5,
316                         speed = Math.abs(dist) / time * 1000,
317                         newDist = speed * speed / (20 * friction) / 1000;
318
319                 // Proportinally reduce speed if we are outside of the boundaries 
320                 if (dist > 0 && newDist > maxDistUpper) {
321                         speed = speed * maxDistUpper / newDist;
322                         newDist = maxDistUpper;
323                 }
324                 if (dist < 0 && newDist > maxDistLower) {
325                         speed = speed * maxDistLower / newDist;
326                         newDist = maxDistLower;
327                 }
328                 
329                 newDist = newDist * (dist < 0 ? -1 : 1);
330                 
331                 var newTime = speed / deceleration;
332
333                 return { dist: Math.round(newDist), time: Math.round(newTime) };
334         }
335 };
336
337 var scrollbar = function (dir, wrapper) {
338         this.dir = dir;
339         this.bar = document.createElement('div');
340         this.bar.className = 'scrollbar ' + dir;
341         this.bar.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
342         this.bar.style.webkitTransform = 'translate3d(0,0,0)';
343         this.bar.style.webkitTransitionProperty = '-webkit-transform,opacity';
344         this.bar.style.webkitTransitionDuration = '0,300ms';
345         this.bar.style.pointerEvents = 'none';
346         this.bar.style.opacity = '0';
347
348         wrapper.appendChild(this.bar);
349 }
350
351 scrollbar.prototype = {
352         size: 0,
353         maxSize: 0,
354         maxScroll: 0,
355         visible: false,
356         
357         init: function (scroll, size) {
358                 var offset = this.dir == 'horizontal' ? this.bar.offsetWidth - this.bar.clientWidth : this.bar.offsetHeight - this.bar.clientHeight;
359                 this.maxSize = scroll - 8;              // 8 = distance from top + distance from bottom
360                 this.size = Math.round(this.maxSize * this.maxSize / size) + offset;
361                 this.maxScroll = this.maxSize - this.size;
362                 this.bar.style[this.dir == 'horizontal' ? 'width' : 'height'] = (this.size - offset) + 'px';
363         },
364         
365         setPosition: function (pos) {
366                 if (pos < 0) {
367                         pos = 0;
368                 } else if (pos > this.maxScroll) {
369                         pos = this.maxScroll;
370                 }
371
372                 pos = this.dir == 'horizontal' ? 'translate3d(' + Math.round(pos) + 'px,0,0)' : 'translate3d(0,' + Math.round(pos) + 'px,0)';
373                 this.bar.style.webkitTransform = pos;
374         },
375         
376         scrollTo: function (pos, runtime) {
377                 this.bar.style.webkitTransitionDuration = (runtime || '400ms') + ',300ms';
378                 this.setPosition(pos);
379         },
380         
381         show: function () {
382                 this.visible = true;
383                 this.bar.style.opacity = '1';
384         },
385
386         hide: function () {
387                 this.visible = false;
388                 this.bar.style.opacity = '0';
389         },
390         
391         remove: function () {
392                 this.bar.parentNode.removeChild(this.bar);
393                 return null;
394         }
395 };
396
397 // Expose iScroll to the world
398 window.iScroll = iScroll;
399 })();