OSDN Git Service

import iscroll
[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.1beta1 - Last updated: 2010.05.06
11  * 
12  */
13
14 function iScroll (el, options) {
15         this.element = typeof el == 'object' ? el : document.getElementById(el);
16         this.wrapper = this.element.parentNode;
17
18         this.wrapper.style.overflow = 'hidden';
19         this.wrapper.style.position = 'relative';
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                 hScrollBar: true,
29                 vScrollBar: true
30         };
31         
32         if (typeof options == 'object') {
33                 for (var i in options) {
34                         this.options[i] = options[i];
35                 }
36         }
37
38         this.refresh();
39         
40         this.element.addEventListener('touchstart', this);
41         this.element.addEventListener('touchmove', this);
42         this.element.addEventListener('touchend', this);
43         this.element.addEventListener('DOMSubtreeModified', this);
44         window.addEventListener('orientationchange', this);
45 }
46
47 iScroll.prototype = {
48         _x: 0,
49         _y: 0,
50
51         handleEvent: function (e) {
52                 switch (e.type) {
53                         case 'touchstart': this.onTouchStart(e); break;
54                         case 'touchmove': this.onTouchMove(e); break;
55                         case 'touchend': this.onTouchEnd(e); break;
56                         case 'webkitTransitionEnd': this.onTransitionEnd(e); break;
57                         case 'orientationchange': this.refresh(); /*this.scrollTo(0,0,'0');*/ break;
58                         case 'DOMSubtreeModified': this.refresh(); break;
59                 }
60         },
61
62         refresh: function () {
63                 this.element.style.webkitTransitionDuration = '0';
64                 this.scrollWidth = this.wrapper.clientWidth;
65                 this.scrollHeight = this.wrapper.clientHeight;
66                 this.maxScrollX = this.scrollWidth - this.element.offsetWidth;
67                 this.maxScrollY = this.scrollHeight - this.element.offsetHeight;
68                 
69                 var resetX = this.x, resetY = this.y;
70                 if (this.scrollX && this.x < this.maxScrollX) {
71                         resetX = this.maxScrollX;
72                 }
73                 if (this.scrollY && this.y < this.maxScrollY) {
74                         resetY = this.maxScrollY;
75                 }
76                 // If content width/lenght has been modified, reset scroller position
77                 this.scrollTo(resetX,resetY,'0');
78
79                 this.scrollX = this.element.offsetWidth > this.scrollWidth ? true : false;
80                 this.scrollY = this.element.offsetHeight > this.scrollHeight ? true : false;
81
82                 // Update horizontal scrollbar
83                 if (this.options.hScrollBar && this.scrollX) {
84                         this.scrollBarX = new scrollbar('horizontal', this.wrapper);                    
85                         this.scrollBarX.init(this.scrollWidth, this.element.offsetWidth);
86                 } else if (this.scrollBarX) {
87                         this.scrollBarX = this.scrollBarX.remove();
88                 }
89
90                 // Update vertical scrollbar
91                 if (this.options.vScrollBar && this.scrollY) {
92                         this.scrollBarY = new scrollbar('vertical', this.wrapper);                      
93                         this.scrollBarY.init(this.scrollHeight, this.element.offsetHeight);
94                 } else if (this.scrollBarY) {
95                         this.scrollBarY = this.scrollBarY.remove();
96                 }
97         },
98
99         get x() {
100                 return this._x;
101         },
102
103         get y() {
104                 return this._y;
105         },
106
107         setPosition: function (x, y) { 
108                 this._x = x !== null ? x : this._x;
109                 this._y = y !== null ? y : this._y;
110
111                 this.element.style.webkitTransform = 'translate3d(' + this._x + 'px,' + this._y + 'px,0)';
112
113                 // Move the scrollbars
114                 if (this.scrollBarX) {
115                         this.scrollBarX.setPosition(this.scrollBarX.maxScroll / this.maxScrollX * this._x);
116                 }
117                 if (this.scrollBarY) {
118                         this.scrollBarY.setPosition(this.scrollBarY.maxScroll / this.maxScrollY * this._y);
119                 }
120         },
121                 
122         onTouchStart: function(e) {
123             if (e.targetTouches.length != 1) {
124                 return false;
125         }
126
127                 e.preventDefault();
128                 e.stopPropagation();
129                 
130                 this.element.style.webkitTransitionDuration = '0';
131                 
132                 if (this.scrollBarX) {
133                         this.scrollBarX.bar.style.webkitTransitionDuration = '0, 250ms';
134                 }
135                 if (this.scrollBarY) {
136                         this.scrollBarY.bar.style.webkitTransitionDuration = '0, 250ms';
137                 }
138
139                 // Check if elem is really where it should be
140                 var theTransform = new WebKitCSSMatrix(window.getComputedStyle(this.element).webkitTransform);
141                 if (theTransform.m41 != this.x || theTransform.m42 != this.y) {
142                         this.setPosition(theTransform.m41, theTransform.m42);
143                 }
144
145                 this.touchStartX = e.touches[0].pageX;
146                 this.scrollStartX = this.x;
147
148                 this.touchStartY = e.touches[0].pageY;
149                 this.scrollStartY = this.y;                     
150
151                 this.scrollStartTime = e.timeStamp;
152                 this.moved = false;
153         },
154         
155         onTouchMove: function(e) {
156                 if (e.targetTouches.length != 1) {
157                         return false;
158                 }
159
160                 var leftDelta = this.scrollX === true ? e.touches[0].pageX - this.touchStartX : 0;
161                 var topDelta = this.scrollY === true ? e.touches[0].pageY - this.touchStartY : 0;
162
163                 if (this.x > 0 || this.x < this.maxScrollX) { 
164                         leftDelta = Math.round(leftDelta / 4);          // Slow down if outside of the boundaries
165                 }
166
167                 if (this.y > 0 || this.y < this.maxScrollY) { 
168                         topDelta = Math.round(topDelta / 4);            // Slow down if outside of the boundaries
169                 }
170
171                 if (this.scrollBarX && !this.scrollBarX.visible) {
172                         this.scrollBarX.show();
173                 }
174                 if (this.scrollBarY && !this.scrollBarY.visible) {
175                         this.scrollBarY.show();
176                 }
177
178                 this.setPosition(this.x + leftDelta, this.y + topDelta);
179
180                 this.touchStartX = e.touches[0].pageX;
181                 this.touchStartY = e.touches[0].pageY;
182                 this.moved = true;
183
184                 // Prevent slingshot effect
185                 if( e.timeStamp-this.scrollStartTime > 250 ) {
186                         this.scrollStartX = this.x;
187                         this.scrollStartY = this.y;
188                         this.scrollStartTime = e.timeStamp;
189                 }
190         },
191         
192         onTouchEnd: function(e) {
193                 if (e.targetTouches.length > 0) {
194                         return false;
195                 }
196
197                 if (!this.moved) {
198                         var theEvent = document.createEvent('MouseEvents');
199                         theEvent.initMouseEvent("click", true, true, document.defaultView, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, null);
200                         e.changedTouches[0].target.dispatchEvent(theEvent);             
201                         return false;
202                 }
203                 
204                 var time = e.timeStamp - this.scrollStartTime;
205
206                 var momentumX = this.scrollX === true
207                         ? this.momentum(this.x - this.scrollStartX,
208                                                         time,
209                                                         -this.x + 50,
210                                                         this.x + this.element.offsetWidth - this.scrollWidth + 50)
211                         : { dist: 0, time: 0 };
212
213                 var momentumY = this.scrollY === true
214                         ? this.momentum(this.y - this.scrollStartY,
215                                                         time,
216                                                          -this.y + /*this.scrollHeight/3*/ 50,
217                                                         this.y + this.element.offsetHeight - this.scrollHeight + /*this.scrollHeight/3*/ 50)
218                         : { dist: 0, time: 0 };
219
220                 if (!momentumX.dist && !momentumY.dist) {
221                         this.onTransitionEnd(); // I know, I know... This is lame
222                         return false;
223                 }
224
225                 var newDuration = Math.max(momentumX.time, momentumY.time);
226                 var newPositionX = this.x + momentumX.dist;
227                 var newPositionY = this.y + momentumY.dist;
228
229                 this.element.addEventListener('webkitTransitionEnd', this);
230
231                 this.scrollTo(newPositionX, newPositionY, newDuration + 'ms');
232
233                 // Move the scrollbars
234                 if (this.scrollBarX) {
235                         this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * newPositionX, newDuration + 'ms');       
236                 }               
237                 if (this.scrollBarY) {
238                         this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * newPositionY, newDuration + 'ms');
239                 }
240         },
241         
242         onTransitionEnd: function () {
243                 this.element.removeEventListener('webkitTransitionEnd', this);
244                 this.resetPosition();
245
246                 // Hide the scrollbars
247                 if (this.scrollBarX) {
248                         this.scrollBarX.hide();
249                 }
250                 if (this.scrollBarY) {
251                         this.scrollBarY.hide();
252                 }
253         },
254
255         resetPosition: function () {
256                 var resetX = resetY = null;
257                 if (this.x > 0 || this.x < this.maxScrollX) {
258                         resetX = this.x >= 0 ? 0 : this.maxScrollX;
259                 }
260
261                 if (this.y > 0 || this.y < this.maxScrollY) {
262                         resetY = this.y >= 0 ? 0 : this.maxScrollY;
263                 }
264
265                 if (resetX !== null || resetY !== null) {
266                         this.scrollTo(resetX, resetY, '500ms');
267
268                         if (this.scrollBarX) {
269                                 this.scrollBarX.scrollTo(this.scrollBarX.maxScroll / this.maxScrollX * (resetX || this.x), '500ms');
270                         }                       
271                         if (this.scrollBarY) {
272                                 this.scrollBarY.scrollTo(this.scrollBarY.maxScroll / this.maxScrollY * (resetY || this.y), '500ms');
273                         }
274                 }
275         },
276
277         scrollTo: function (destX, destY, runtime) {
278                 this.element.style.webkitTransitionDuration = runtime || '400ms';
279                 this.setPosition(destX, destY);
280         },
281
282         momentum: function (dist, time, maxDist1, maxDist2) {
283                 friction = 0.1;
284                 deceleration = 1.5;
285
286                 var speed = Math.abs(dist) / time * 1000;
287                 var newDist = speed * speed / (20 * friction) / 1000;
288
289                 // Proportinally reduce speed if we are outside of the boundaries 
290                 if (dist > 0 && maxDist1 !== undefined && newDist > maxDist1) {
291                         speed = speed * maxDist1 / newDist;
292                         newDist = maxDist1;
293                 }
294                 if (dist < 0 && maxDist2 !== undefined && newDist > maxDist2) {
295                         speed = speed * maxDist2 / newDist;
296                         newDist = maxDist2;
297                 }
298                 
299                 newDist = newDist * (dist < 0 ? -1 : 1);
300                 
301                 var newTime = -speed / -deceleration;
302                 if (newTime < 1) {      // We can't go back in time
303                         newTime = 1;
304                 }
305
306                 return { dist: Math.round(newDist), time: Math.round(newTime) };
307         }
308 };
309
310 var scrollbar = function (dir, wrapper) {
311         this.dir = dir;
312         this.bar = document.createElement('div');
313         this.bar.className = 'scrollbar ' + dir;
314         this.bar.style.webkitTransitionTimingFunction = 'cubic-bezier(0,0,0.25,1)';
315         this.bar.style.webkitTransform = 'translate3d(0,0,0)';
316         this.bar.style.webkitTransitionProperty = '-webkit-transform,opacity';
317         this.bar.style.webkitTransitionDuration = '0,250ms';
318         this.bar.style.pointerEvents = 'none';
319         this.bar.style.opacity = '0';
320
321         wrapper.appendChild(this.bar);
322 }
323
324 scrollbar.prototype = {
325         size: 0,
326         maxSize: 0,
327         maxScroll: 0,
328         visible: false,
329         
330         init: function (scroll, size) {
331                 var offset = this.dir == 'horizontal' ? this.bar.offsetWidth - this.bar.clientWidth : this.bar.offsetHeight - this.bar.clientHeight;
332                 this.maxSize = scroll - 8;              // 8 = distance from top + distance from bottom
333                 this.size = Math.round(this.maxSize * this.maxSize / size) + offset;
334                 this.maxScroll = this.maxSize - this.size;
335                 this.bar.style[this.dir == 'horizontal' ? 'width' : 'height'] = (this.size - offset) + 'px';
336         },
337         
338         setPosition: function (pos) {
339                 if (pos < 0) {
340                         pos = 0;
341                 } else if (pos > this.maxScroll) {
342                         pos = this.maxScroll;
343                 }
344
345                 pos = this.dir == 'horizontal' ? 'translate3d(' + Math.round(pos) + 'px,0,0)' : 'translate3d(0,' + Math.round(pos) + 'px,0)';
346                 this.bar.style.webkitTransform = pos;
347         },
348         
349         scrollTo: function (pos, runtime) {
350                 this.bar.style.webkitTransitionDuration = (runtime || '400ms') + ',250ms';
351                 this.setPosition(pos);
352         },
353         
354         show: function () {
355                 this.visible = true;
356                 this.bar.style.opacity = '1';
357         },
358
359         hide: function () {
360                 this.visible = false;
361                 this.bar.style.opacity = '0';
362         },
363         
364         remove: function () {
365                 this.bar.parentNode.removeChild(this.bar);
366                 return null;
367         }
368 };