1/** 2 * flipsnap.js 3 * 4 * @version 0.6.3 5 * @url http://hokaccha.github.com/js-flipsnap/ 6 * 7 * Copyright 2011 PixelGrid, Inc. 8 * Licensed under the MIT License: 9 * http://www.opensource.org/licenses/mit-license.php 10 */ 11 12(function(root, factory) { 13 if (typeof define === 'function' && define.amd) { 14 define([], factory); 15 } else if (typeof exports === 'object') { 16 module.exports = factory(); 17 } else { 18 root.Flipsnap = factory(); 19 } 20})(this, function() { 21 22var div = document.createElement('div'); 23var prefix = ['webkit', 'moz', 'o', 'ms']; 24var saveProp = {}; 25var support = Flipsnap.support = {}; 26var gestureStart = false; 27 28var DISTANCE_THRESHOLD = 5; 29var ANGLE_THREHOLD = 55; 30 31support.transform3d = hasProp([ 32 'perspectiveProperty', 33 'WebkitPerspective', 34 'MozPerspective', 35 'OPerspective', 36 'msPerspective' 37]); 38 39support.transform = hasProp([ 40 'transformProperty', 41 'WebkitTransform', 42 'MozTransform', 43 'OTransform', 44 'msTransform' 45]); 46 47support.transition = hasProp([ 48 'transitionProperty', 49 'WebkitTransitionProperty', 50 'MozTransitionProperty', 51 'OTransitionProperty', 52 'msTransitionProperty' 53]); 54 55support.addEventListener = 'addEventListener' in window; 56support.mspointer = window.navigator.msPointerEnabled; 57 58support.cssAnimation = (support.transform3d || support.transform) && support.transition; 59 60var eventTypes = ['touch', 'mouse']; 61var events = { 62 start: { 63 touch: 'touchstart', 64 mouse: 'mousedown' 65 }, 66 move: { 67 touch: 'touchmove', 68 mouse: 'mousemove' 69 }, 70 end: { 71 touch: 'touchend', 72 mouse: 'mouseup' 73 } 74}; 75 76if (support.addEventListener) { 77 document.addEventListener('gesturestart', function() { 78 gestureStart = true; 79 }); 80 81 document.addEventListener('gestureend', function() { 82 gestureStart = false;
83 }); 84} 85 86function Flipsnap(element, opts) { 87 return (this instanceof Flipsnap) 88 ? this.init(element, opts) 89 : new Flipsnap(element, opts); 90} 91 92Flipsnap.prototype.init = function(element, opts) { 93 var self = this; 94 95 // set element 96 self.element = element; 97 if (typeof element === 'string') { 98 self.element = document.querySelector(element); 99 } 100 101 if (!self.element) { 102 throw new Error('element not found'); 103 } 104 105 if (support.mspointer) { 106 self.element.style.msTouchAction = 'pan-y'; 107 } 108 109 // set opts 110 opts = opts || {}; 111 self.distance = opts.distance; 112 self.maxPoint = opts.maxPoint; 113 self.disableTouch = (opts.disableTouch === undefined) ? false : opts.disableTouch; 114 self.disable3d = (opts.disable3d === undefined) ? false : opts.disable3d; 115 self.transitionDuration = (opts.transitionDuration === undefined) ? '350ms' : opts.transitionDuration + 'ms'; 116 self.threshold = opts.threshold || 0; 117 118 // set property 119 self.currentPoint = 0; 120 self.currentX = 0; 121 self.animation = false; 122 self.timerId = null; 123 self.use3d = support.transform3d; 124 if (self.disable3d === true) { 125 self.use3d = false; 126 } 127 128 // set default style 129 if (support.cssAnimation) { 130 self._setStyle({ 131 transitionProperty: getCSSVal('transform'), 132 // transitionTimingFunction: 'cubic-bezier(0,0,0.25,1)', 133 transitionTimingFunction: 'cubic-bezier(0.46, 0.1, 0.34, 1)', 134 transitionDuration: '0ms', 135 transform: self._getTranslate(0) 136 }); 137 } 138 else { 139 self._setStyle({ 140 position: 'relative', 141 left: '0px' 142 }); 143 } 144 145 // initilize 146 self.refresh(); 147 148 eventTypes.forEach(function(type) { 149 self.element.addEventListener(events.start[type], self, false); 150 }); 151 152 return self; 153}; 154 155Flipsnap.prototype.handleEvent = function(event) { 156 var self = this; 157 158 switch (event.type) { 159 // start 160 case events.start.touch: self._touchStart(event, 'touch'); break; 161 case events.start.mouse: self._touchStart(event, 'mouse'); break; 162 163 // move 164 case events.move.touch: self._touchMove(event, 'touch'); break; 165 case events.move.mouse: self._touchMove(event, 'mouse'); break; 166 167 // end 168 case events.end.touch: self._touchEnd(event, 'touch'); break; 169 case events.end.mouse: self._touchEnd(event, 'mouse'); break; 170 171 // click 172 case 'click': self._click(event); break; 173 } 174}; 175 176Flipsnap.prototype.refresh = function() { 177 var self = this; 178 179 // setting max point 180 self._maxPoint = (self.maxPoint === undefined) ? (function() { 181 var childNodes = self.element.childNodes, 182 itemLength = -1, 183 i = 0, 184 len = childNodes.length, 185 node; 186 for(; i < len; i++) { 187 node = childNodes[i]; 188 if (node.nodeType === 1) { 189 itemLength++; 190 } 191 } 192 193 return itemLength; 194 })() : self.maxPoint; 195 196 // setting distance 197 if (self.distance === undefined) { 198 if (self._maxPoint < 0) { 199 self._distance = 0; 200 } 201 else { 202 self._distance = self.element.scrollWidth / (self._maxPoint + 1); 203 } 204 } 205 else { 206 self._distance = self.distance; 207 } 208 209 // setting maxX 210 self._maxX = -self._distance * self._maxPoint; 211 212 self.moveToPoint(); 213}; 214 215Flipsnap.prototype.hasNext = function() { 216 var self = this; 217 218 return self.currentPoint < self._maxPoint; 219}; 220 221Flipsnap.prototype.hasPrev = function() { 222 var self = this; 223 224 return self.currentPoint > 0; 225}; 226 227Flipsnap.prototype.toNext = function(transitionDuration) { 228 var self = this; 229 230 if (!self.hasNext()) { 231 return; 232 } 233 234 self.moveToPoint(self.currentPoint + 1, transitionDuration); 235}; 236 237Flipsnap.prototype.toPrev = function(transitionDuration) { 238 var self = this; 239 240 if (!self.hasPrev()) { 241 return; 242 } 243 244 self.moveToPoint(self.currentPoint - 1, transitionDuration); 245}; 246 247Flipsnap.prototype.moveToPoint = function(point, transitionDuration) { 248 var self = this; 249 250 transitionDuration = transitionDuration === undefined 251 ? self.transitionDuration : transitionDuration + 'ms'; 252 253 var beforePoint = self.currentPoint; 254 255 // not called from `refresh()` 256 if (point === undefined) { 257 point = self.currentPoint; 258 } 259 260 if (point < 0) { 261 self.currentPoint = 0; 262 } 263 else if (point > self._maxPoint) { 264 self.currentPoint = self._maxPoint; 265 } 266 else { 267 self.currentPoint = parseInt(point, 10); 268 } 269 270 if (support.cssAnimation) { 271 self._setStyle({ transitionDuration: transitionDuration }); 272 } 273 else { 274 self.animation = true; 275 } 276 self._setX(- self.currentPoint * self._distance, transitionDuration); 277 278 if (beforePoint !== self.currentPoint) { // is move? 279 // `fsmoveend` is deprecated 280 // `fspointmove` is recommend. 281 self._triggerEvent('fsmoveend', true, false); 282 self._triggerEvent('fspointmove', true, false); 283 } 284}; 285 286Flipsnap.prototype._setX = function(x, transitionDuration) { 287 var self = this; 288 289 self.currentX = x; 290 if (support.cssAnimation) { 291 self.element.style[ saveProp.transform ] = self._getTranslate(x); 292 } 293 else { 294 if (self.animation) { 295 self._animate(x, transitionDuration || self.transitionDuration); 296 } 297 else { 298 self.element.style.left = x + 'px'; 299 } 300 } 301}; 302 303Flipsnap.prototype._touchStart = function(event, type) { 304 var self = this; 305 306 if (self.disableTouch || self.scrolling || gestureStart) { 307 return; 308 } 309 310 self.element.addEventListener(events.move[type], self, false); 311 document.addEventListener(events.end[type], self, false); 312 313 var tagName = event.target.tagName; 314 if (type === 'mouse' && tagName !== 'SELECT' && tagName !== 'INPUT' && tagName !== 'TEXTAREA' && tagName !== 'BUTTON') { 315 event.preventDefault(); 316 } 317 318 if (support.cssAnimation) { 319 self._setStyle({ transitionDuration: '0ms' }); 320 } 321 else { 322 self.animation = false;
323 } 324 self.scrolling = true; 325 self.moveReady = false; 326 self.startPageX = getPage(event, 'pageX'); 327 self.startPageY = getPage(event, 'pageY'); 328 self.basePageX = self.startPageX; 329 self.directionX = 0; 330 self.startTime = event.timeStamp; 331 self._triggerEvent('fstouchstart', true, false); 332}; 333 334Flipsnap.prototype._touchMove = function(event, type) { 335 var self = this; 336 337 if (!self.scrolling || gestureStart) { 338 return; 339 } 340 341 var pageX = getPage(event, 'pageX'); 342 var pageY = getPage(event, 'pageY'); 343 var distX; 344 var newX; 345 346 if (self.moveReady) { 347 event.preventDefault(); 348 349 distX = pageX - self.basePageX; 350 newX = self.currentX + distX; 351 if (newX >= 0 || newX < self._maxX) { 352 newX = Math.round(self.currentX + distX / 3); 353 } 354 355 // When distX is 0, use one previous value. 356 // For android firefox. When touchend fired, touchmove also 357 // fired and distX is certainly set to 0. 358 self.directionX = 359 distX === 0 ? self.directionX : 360 distX > 0 ? -1 : 1; 361 362 // if they prevent us then stop it 363 var isPrevent = !self._triggerEvent('fstouchmove', true, true, { 364 delta: distX, 365 direction: self.directionX 366 }); 367 368 if (isPrevent) { 369 self._touchAfter({ 370 moved: false, 371 originalPoint: self.currentPoint, 372 newPoint: self.currentPoint, 373 cancelled: true 374 }); 375 } else { 376 self._setX(newX); 377 } 378 } 379 else { 380 // https://github.com/hokaccha/js-flipsnap/pull/36 381 var triangle = getTriangleSide(self.startPageX, self.startPageY, pageX, pageY); 382 if (triangle.z > DISTANCE_THRESHOLD) { 383 if (getAngle(triangle) > ANGLE_THREHOLD) { 384 event.preventDefault(); 385 self.moveReady = true; 386 self.element.addEventListener('click', self, true); 387 } 388 else { 389 self.scrolling = false; 390 } 391 } 392 } 393 394 self.basePageX = pageX; 395}; 396 397Flipsnap.prototype._touchEnd = function(event, type) { 398 var self = this; 399 400 self.element.removeEventListener(events.move[type], self, false); 401 document.removeEventListener(events.end[type], self, false); 402 403 if (!self.scrolling) { 404 return; 405 } 406 407 var newPoint = -self.currentX / self._distance; 408 newPoint = 409 (self.directionX > 0) ? Math.ceil(newPoint) : 410 (self.directionX < 0) ? Math.floor(newPoint) : 411 Math.round(newPoint); 412 413 if (newPoint < 0) { 414 newPoint = 0; 415 } 416 else if (newPoint > self._maxPoint) { 417 newPoint = self._maxPoint; 418 } 419 420 if (Math.abs(self.startPageX - self.basePageX) < self.threshold) { 421 newPoint = self.currentPoint; 422 } 423 424 self._touchAfter({ 425 moved: newPoint !== self.currentPoint, 426 originalPoint: self.currentPoint, 427 newPoint: newPoint, 428 cancelled: false 429 }); 430 431 self.moveToPoint(newPoint); 432}; 433 434Flipsnap.prototype._click = function(event) { 435 var self = this; 436 437 event.stopPropagation(); 438 event.preventDefault(); 439}; 440 441Flipsnap.prototype._touchAfter = function(params) { 442 var self = this; 443 444 self.scrolling = false; 445 self.moveReady = false; 446 447 setTimeout(function() { 448 self.element.removeEventListener('click', self, true); 449 }, 200); 450 451 self._triggerEvent('fstouchend', true, false, params); 452}; 453 454Flipsnap.prototype._setStyle = function(styles) { 455 var self = this; 456 var style = self.element.style; 457 458 for (var prop in styles) { 459 setStyle(style, prop, styles[prop]); 460 } 461}; 462 463Flipsnap.prototype._animate = function(x, transitionDuration) { 464 var self = this; 465 466 var elem = self.element; 467 var begin = +new Date(); 468 var from = parseInt(elem.style.left, 10); 469 var to = x; 470 var duration = parseInt(transitionDuration, 10); 471 var easing = function(time, duration) { 472 return -(time /= duration) * (time - 2); 473 }; 474 475 if (self.timerId) { 476 clearInterval(self.timerId); 477 } 478 self.timerId = setInterval(function() { 479 var time = new Date() - begin; 480 var pos, now; 481 if (time > duration) { 482 clearInterval(self.timerId); 483 self.timerId = null; 484 now = to; 485 } 486 else { 487 pos = easing(time, duration); 488 now = pos * (to - from) + from; 489 } 490 elem.style.left = now + "px"; 491 }, 10); 492}; 493 494Flipsnap.prototype.destroy = function() { 495 var self = this; 496
497 eventTypes.forEach(function(type) { 498 self.element.removeEventListener(events.start[type], self, false); 499 }); 500}; 501 502Flipsnap.prototype._getTranslate = function(x) { 503 var self = this; 504 505 return self.use3d 506 ? 'translate3d(' + x + 'px, 0, 0)' 507 : 'translate(' + x + 'px, 0)'; 508}; 509 510Flipsnap.prototype._triggerEvent = function(type, bubbles, cancelable, data) { 511 var self = this; 512 513 var ev = document.createEvent('Event'); 514 ev.initEvent(type, bubbles, cancelable); 515 516 if (data) { 517 for (var d in data) { 518 if (data.hasOwnProperty(d)) { 519 ev[d] = data[d]; 520 } 521 } 522 } 523 524 return self.element.dispatchEvent(ev); 525}; 526 527function getPage(event, page) { 528 return event.changedTouches ? event.changedTouches[0][page] : event[page]; 529} 530 531function hasProp(props) { 532 return some(props, function(prop) { 533 return div.style[ prop ] !== undefined; 534 }); 535} 536 537function setStyle(style, prop, val) { 538 var _saveProp = saveProp[ prop ]; 539 if (_saveProp) { 540 style[ _saveProp ] = val; 541 } 542 else if (style[ prop ] !== undefined) { 543 saveProp[ prop ] = prop; 544 style[ prop ] = val; 545 } 546 else { 547 some(prefix, function(_prefix) { 548 var _prop = ucFirst(_prefix) + ucFirst(prop); 549 if (style[ _prop ] !== undefined) { 550 saveProp[ prop ] = _prop; 551 style[ _prop ] = val; 552 return true; 553 } 554 }); 555 } 556} 557 558function getCSSVal(prop) { 559 if (div.style[ prop ] !== undefined) { 560 return prop; 561 } 562 else { 563 var ret; 564 some(prefix, function(_prefix) { 565 var _prop = ucFirst(_prefix) + ucFirst(prop); 566 if (div.style[ _prop ] !== undefined) { 567 ret = '-' + _prefix + '-' + prop; 568 return true; 569 } 570 }); 571 return ret; 572 } 573} 574 575function ucFirst(str) { 576 return str.charAt(0).toUpperCase() + str.substr(1); 577} 578 579function some(ary, callback) { 580 for (var i = 0, len = ary.length; i < len; i++) { 581 if (callback(ary[i], i)) { 582 return true; 583 } 584 } 585 return false; 586} 587 588function getTriangleSide(x1, y1, x2, y2) { 589 var x = Math.abs(x1 - x2); 590 var y = Math.abs(y1 - y2); 591 var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); 592 593 return { 594 x: x, 595 y: y, 596 z: z 597 }; 598} 599 600function getAngle(triangle) { 601 var cos = triangle.y / triangle.z; 602 var radian = Math.acos(cos); 603 604 return 180 / (Math.PI / radian); 605} 606 607return Flipsnap; 608 609});
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.