[关闭]
@kirkzwy 2014-11-27T02:53:17.000000Z 字数 11802 阅读 3098

flipsnap.js源码学习笔记

coding flipsnap.js


1.引入全局命名空间

  1. (function(window, document, undefined){})(window, window.document)

2.定义全局变量

  1. /* 新建div节点 */
  2. var div = document.createElement('div');
  3. /* 浏览器前缀 */
  4. var prefix = ['webkit', 'moz', 'o', 'ms'];
  5. /* 存储属性对象 */
  6. var saveProp = {};
  7. /* 检测浏览器支持的对象 */
  8. var support = Flipsnap.support = {};
  9. /* 手势状态判断 */
  10. var gestureStart = false;
  11. /* 阀值设置 */
  12. var DISTANCE_THRESHOLD = 5;
  13. var ANGLE_THREHOLD = 55;

3.分别检测对transform3d, transform及trasition 3个css3属性的支持程度

  1. support.transform3d = hasProp([
  2. 'perspectiveProperty',
  3. 'WebkitPerspective',
  4. 'MozPerspective',
  5. 'OPerspective',
  6. 'msPerspective'
  7. ]);
  8. support.transform = hasProp([
  9. 'transformProperty',
  10. 'WebkitTransform',
  11. 'MozTransform',
  12. 'OTransform',
  13. 'msTransform'
  14. ]);
  15. support.transition = hasProp([
  16. 'transitionProperty',
  17. 'WebkitTransitionProperty',
  18. 'MozTransitionProperty',
  19. 'OTransitionProperty',
  20. 'msTransitionProperty'
  21. ]);

使用hasProp()函数

  1. function hasProp(props) {
  2. return some(props, function(prop) {
  3. return div.style[ prop ] !== undefined;
  4. });
  5. }

其中some()闭包函数##为,只要数组内其中一个为true即判定为true

  1. function some(ary, callback) {
  2. for (var i = 0, len = ary.length; i < len; i++) {
  3. if (callback(ary[i], i)) {
  4. return true;
  5. }
  6. }
  7. return false;
  8. }

4.其他浏览器的属性支持(事件监听属性及IE的指针事件的支持)及总的css动画属性支持判断

  1. support.addEventListener = 'addEventListener' in window;
  2. support.mspointer = window.navigator.msPointerEnabled;
  3. support.cssAnimation = (support.transform3d || support.transform) && support.transition;

5.定义事件种类及相关事件对象

  1. var eventTypes = ['touch', 'mouse'];
  2. var events = {
  3. start: {
  4. touch: 'touchstart',
  5. mouse: 'mousedown'
  6. },
  7. move: {
  8. touch: 'touchmove',
  9. mouse: 'mousemove'
  10. },
  11. end: {
  12. touch: 'touchend',
  13. mouse: 'mouseup'
  14. }
  15. };

6.添加事件监听

  1. if (support.addEventListener) {
  2. document.addEventListener('gesturestart', function() {
  3. gestureStart = true;
  4. });
  5. document.addEventListener('gestureend', function() {
  6. gestureStart = false;
  7. });
  8. }

7.定义全局新类Flipsnap(),并初始化新类,其中使用了Flipsnap类的初始方法init()

  1. function Flipsnap(element, opts) {
  2. return (this instanceof Flipsnap)
  3. ? this.init(element, opts)
  4. : new Flipsnap(element, opts);
  5. }

8.定义Flipsnap类的初始化方法init()

  1. Flipsnap.prototype.init = function(element, opts) {
  2. var self = this;
  3. // set element
  4. self.element = element;
  5. if (typeof element === 'string') {
  6. self.element = document.querySelector(element);
  7. }
  8. if (!self.element) {
  9. throw new Error('element not found');
  10. }
  11. if (support.mspointer) {
  12. self.element.style.msTouchAction = 'pan-y';
  13. }
  14. // set opts
  15. opts = opts || {};
  16. self.distance = opts.distance;
  17. self.maxPoint = opts.maxPoint;
  18. self.disableTouch = (opts.disableTouch === undefined) ? false : opts.disableTouch;
  19. self.disable3d = (opts.disable3d === undefined) ? false : opts.disable3d;
  20. self.transitionDuration = (opts.transitionDuration === undefined) ? '350ms' : opts.transitionDuration + 'ms';
  21. self.threshold = opts.threshold || 0;
  22. // set property
  23. self.currentPoint = 0;
  24. self.currentX = 0;
  25. self.animation = false;
  26. self.use3d = support.transform3d;
  27. if (self.disable3d === true) {
  28. self.use3d = false;
  29. }
  30. // set default style
  31. if (support.cssAnimation) {
  32. self._setStyle({
  33. transitionProperty: getCSSVal('transform'),
  34. transitionTimingFunction: 'cubic-bezier(0,0,0.25,1)',
  35. transitionDuration: '0ms',
  36. transform: self._getTranslate(0)
  37. });
  38. }
  39. else {
  40. self._setStyle({
  41. position: 'relative',
  42. left: '0px'
  43. });
  44. }
  45. // initilize
  46. self.refresh();
  47. eventTypes.forEach(function(type) {
  48. // 为什么要传人self作回调函数,(self=Flipsnap())?
  49. self.element.addEventListener(events.start[type], self, false);
  50. });
  51. return self;
  52. };

其中_setStyle()为FLipsnap的内部方法

  1. Flipsnap.prototype._setStyle = function(styles) {
  2. var self = this;
  3. var style = self.element.style;
  4. for (var prop in styles) {
  5. setStyle(style, prop, styles[prop]);
  6. }
  7. };

其中setStyle()函数,设置对应css属性为对应值,具体为

  1. function setStyle(style, prop, val) {
  2. var _saveProp = saveProp[ prop ];
  3. if (_saveProp) {
  4. style[ _saveProp ] = val;
  5. }
  6. else if (style[ prop ] !== undefined) {
  7. saveProp[ prop ] = prop;
  8. style[ prop ] = val;
  9. }
  10. else {
  11. some(prefix, function(_prefix) {
  12. var _prop = ucFirst(_prefix) + ucFirst(prop);
  13. if (style[ _prop ] !== undefined) {
  14. saveProp[ prop ] = _prop;
  15. style[ _prop ] = val;
  16. return true;
  17. }
  18. });
  19. }
  20. }

并在saveProp中设置过的属性

getCSSVal()函数用于获取已用的css属性值,具体如下

  1. function getCSSVal(prop) {
  2. if (div.style[ prop ] !== undefined) {
  3. return prop;
  4. }
  5. else {
  6. var ret;
  7. some(prefix, function(_prefix) {
  8. var _prop = ucFirst(_prefix) + ucFirst(prop);
  9. if (div.style[ _prop ] !== undefined) {
  10. ret = '-' + _prefix + '-' + prop;
  11. return true;
  12. }
  13. });
  14. return ret;
  15. }
  16. }

ucFirst()函数用于将首字母变大写,具体如下

  1. function ucFirst(str) {
  2. return str.charAt(0).toUpperCase() + str.substr(1);
  3. }

cubic-bezier(0,0,0.25,1)为css3的变形值

self._getTranslate为Flipsnap的内部方法

定义3d属性值,具体为

  1. Flipsnap.prototype._getTranslate = function(x) {
  2. var self = this;
  3. return self.use3d
  4. ? 'translate3d(' + x + 'px, 0, 0)'
  5. : 'translate(' + x + 'px, 0)';
  6. };

refresh()的Flipsnap方法,具体如下

  1. Flipsnap.prototype.refresh = function() {
  2. var self = this;
  3. // setting max point
  4. self._maxPoint = (self.maxPoint === undefined) ? (function() {
  5. var childNodes = self.element.childNodes,
  6. itemLength = -1,
  7. i = 0,
  8. len = childNodes.length,
  9. node;
  10. for(; i < len; i++) {
  11. node = childNodes[i];
  12. if (node.nodeType === 1) {
  13. itemLength++;
  14. }
  15. }
  16. return itemLength;
  17. })() : self.maxPoint;
  18. // setting distance
  19. if (self.distance === undefined) {
  20. if (self._maxPoint < 0) {
  21. self._distance = 0;
  22. }
  23. else {
  24. self._distance = self.element.scrollWidth / (self._maxPoint + 1);
  25. }
  26. }
  27. else {
  28. self._distance = self.distance;
  29. }
  30. // setting maxX
  31. self._maxX = -self._distance * self._maxPoint;
  32. self.moveToPoint();
  33. };

通过refresh方法设定_maxPoint(最多移动次数)、_distance(移动距离)和_maxX(最大x轴偏向值)属性,从而控制最多的滑动次数;

moveToPoint()的Flipsnap方法,判定是否需要滑动并触发事件,具体如下

  1. Flipsnap.prototype.moveToPoint = function(point, transitionDuration) {
  2. var self = this;
  3. transitionDuration = transitionDuration === undefined
  4. ? self.transitionDuration : transitionDuration + 'ms';
  5. var beforePoint = self.currentPoint;
  6. // not called from `refresh()`
  7. if (point === undefined) {
  8. point = self.currentPoint;
  9. }
  10. if (point < 0) {
  11. self.currentPoint = 0;
  12. }
  13. else if (point > self._maxPoint) {
  14. self.currentPoint = self._maxPoint;
  15. }
  16. else {
  17. self.currentPoint = parseInt(point, 10);
  18. }
  19. if (support.cssAnimation) {
  20. self._setStyle({ transitionDuration: transitionDuration });
  21. }
  22. else {
  23. self.animation = true;
  24. }
  25. self._setX(- self.currentPoint * self._distance, transitionDuration);
  26. if (beforePoint !== self.currentPoint) { // is move?
  27. // `fsmoveend` is deprecated
  28. // `fspointmove` is recommend.
  29. self._triggerEvent('fsmoveend', true, false);
  30. self._triggerEvent('fspointmove', true, false);
  31. }
  32. };

其中_setX()为Flipsnap的内部方法,具体如下

  1. Flipsnap.prototype._setX = function(x, transitionDuration) {
  2. var self = this;
  3. self.currentX = x;
  4. if (support.cssAnimation) {
  5. self.element.style[ saveProp.transform ] = self._getTranslate(x);
  6. }
  7. else {
  8. if (self.animation) {
  9. self._animate(x, transitionDuration || self.transitionDuration);
  10. }
  11. else {
  12. self.element.style.left = x + 'px';
  13. }
  14. }
  15. };

其中_animate()为Flipsnap的内部方法,具体如下

  1. Flipsnap.prototype._animate = function(x, transitionDuration) {
  2. var self = this;
  3. var elem = self.element;
  4. var begin = +new Date();
  5. var from = parseInt(elem.style.left, 10);
  6. var to = x;
  7. var duration = parseInt(transitionDuration, 10);
  8. var easing = function(time, duration) {
  9. return -(time /= duration) * (time - 2);
  10. };
  11. var timer = setInterval(function() {
  12. var time = new Date() - begin;
  13. var pos, now;
  14. if (time > duration) {
  15. clearInterval(timer);
  16. now = to;
  17. }
  18. else {
  19. pos = easing(time, duration);
  20. now = pos * (to - from) + from;
  21. }
  22. elem.style.left = now + "px";
  23. }, 10);
  24. };

其中_triggerEvent() 为Flipsnap的内部方法,具体如下

  1. Flipsnap.prototype._triggerEvent = function(type, bubbles, cancelable, data) {
  2. var self = this;
  3. var ev = document.createEvent('Event');
  4. ev.initEvent(type, bubbles, cancelable);
  5. if (data) {
  6. for (var d in data) {
  7. if (data.hasOwnProperty(d)) {
  8. ev[d] = data[d];
  9. }
  10. }
  11. }
  12. return self.element.dispatchEvent(ev);
  13. };

9.定义Flipsnap类的事件控制方法handleEvent()

  1. Flipsnap.prototype.handleEvent = function(event) {
  2. var self = this;
  3. switch (event.type) {
  4. // start
  5. case events.start.touch: self._touchStart(event, 'touch'); break;
  6. case events.start.mouse: self._touchStart(event, 'mouse'); break;
  7. // move
  8. case events.move.touch: self._touchMove(event, 'touch'); break;
  9. case events.move.mouse: self._touchMove(event, 'mouse'); break;
  10. // end
  11. case events.end.touch: self._touchEnd(event, 'touch'); break;
  12. case events.end.mouse: self._touchEnd(event, 'mouse'); break;
  13. // click
  14. case 'click': self._click(event); break;
  15. }
  16. };

通过event.type进行条件判断,确定执行Flipsnap的四个内部处理方法中的一个

_touchStart()方法

  1. Flipsnap.prototype._touchStart = function(event, type) {
  2. var self = this;
  3. if (self.disableTouch || self.scrolling || gestureStart) {
  4. return;
  5. }
  6. self.element.addEventListener(events.move[type], self, false);
  7. document.addEventListener(events.end[type], self, false);
  8. var tagName = event.target.tagName;
  9. if (type === 'mouse' && tagName !== 'SELECT' && tagName !== 'INPUT' && tagName !== 'TEXTAREA' && tagName !== 'BUTTON') {
  10. event.preventDefault();
  11. }
  12. if (support.cssAnimation) {
  13. self._setStyle({ transitionDuration: '0ms' });
  14. }
  15. else {
  16. self.animation = false;
  17. }
  18. self.scrolling = true;
  19. self.moveReady = false;
  20. self.startPageX = getPage(event, 'pageX');
  21. self.startPageY = getPage(event, 'pageY');
  22. self.basePageX = self.startPageX;
  23. self.directionX = 0;
  24. self.startTime = event.timeStamp;
  25. self._triggerEvent('fstouchstart', true, false);
  26. };

通过touchStart方法记录下触摸开始点开始时间等参数,并触发fstouchstart事件;

getPage()函数如下

  1. function getPage(event, page) {
  2. return event.changedTouches ? event.changedTouches[0][page] : event[page];
  3. }

_touchMove()方法如下

  1. Flipsnap.prototype._touchMove = function(event, type) {
  2. var self = this;
  3. if (!self.scrolling || gestureStart) {
  4. return;
  5. }
  6. var pageX = getPage(event, 'pageX');
  7. var pageY = getPage(event, 'pageY');
  8. var distX;
  9. var newX;
  10. if (self.moveReady) {
  11. event.preventDefault();
  12. distX = pageX - self.basePageX;
  13. newX = self.currentX + distX;
  14. if (newX >= 0 || newX < self._maxX) {
  15. newX = Math.round(self.currentX + distX / 3);
  16. }
  17. // When distX is 0, use one previous value.
  18. // For android firefox. When touchend fired, touchmove also
  19. // fired and distX is certainly set to 0.
  20. self.directionX =
  21. distX === 0 ? self.directionX :
  22. distX > 0 ? -1 : 1;
  23. // if they prevent us then stop it
  24. var isPrevent = !self._triggerEvent('fstouchmove', true, true, {
  25. delta: distX,
  26. direction: self.directionX
  27. });
  28. if (isPrevent) {
  29. self._touchAfter({
  30. moved: false,
  31. originalPoint: self.currentPoint,
  32. newPoint: self.currentPoint,
  33. cancelled: true
  34. });
  35. } else {
  36. self._setX(newX);
  37. }
  38. }
  39. else {
  40. // https://github.com/pxgrid/js-flipsnap/pull/36
  41. var triangle = getTriangleSide(self.startPageX, self.startPageY, pageX, pageY);
  42. if (triangle.z > DISTANCE_THRESHOLD) {
  43. if (getAngle(triangle) > ANGLE_THREHOLD) {
  44. event.preventDefault();
  45. self.moveReady = true;
  46. self.element.addEventListener('click', self, true);
  47. }
  48. else {
  49. self.scrolling = false;
  50. }
  51. }
  52. }
  53. self.basePageX = pageX;
  54. };

其中getTriangleSide()函数如下

  1. function getTriangleSide(x1, y1, x2, y2) {
  2. var x = Math.abs(x1 - x2);
  3. var y = Math.abs(y1 - y2);
  4. var z = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2));
  5. return {
  6. x: x,
  7. y: y,
  8. z: z
  9. };
  10. }

其中getAngle()函数如下

  1. function getAngle(triangle) {
  2. var cos = triangle.y / triangle.z;
  3. var radina = Math.acos(cos);
  4. return 180 / (Math.PI / radina);
  5. }

_touchEnd()方法如下

  1. Flipsnap.prototype._touchEnd = function(event, type) {
  2. var self = this;
  3. self.element.removeEventListener(events.move[type], self, false);
  4. document.removeEventListener(events.end[type], self, false);
  5. if (!self.scrolling) {
  6. return;
  7. }
  8. var newPoint = -self.currentX / self._distance;
  9. newPoint =
  10. (self.directionX > 0) ? Math.ceil(newPoint) :
  11. (self.directionX < 0) ? Math.floor(newPoint) :
  12. Math.round(newPoint);
  13. if (newPoint < 0) {
  14. newPoint = 0;
  15. }
  16. else if (newPoint > self._maxPoint) {
  17. newPoint = self._maxPoint;
  18. }
  19. if (Math.abs(self.startPageX - self.basePageX) < self.threshold) {
  20. newPoint = self.currentPoint;
  21. }
  22. self._touchAfter({
  23. moved: newPoint !== self.currentPoint,
  24. originalPoint: self.currentPoint,
  25. newPoint: newPoint,
  26. cancelled: false
  27. });
  28. self.moveToPoint(newPoint);
  29. };

触发fstouchend事件

其中_touchAfter()方法如下

  1. Flipsnap.prototype._touchAfter = function(params) {
  2. var self = this;
  3. self.scrolling = false;
  4. self.moveReady = false;
  5. setTimeout(function() {
  6. self.element.removeEventListener('click', self, true);
  7. }, 200);
  8. self._triggerEvent('fstouchend', true, false, params);
  9. };

_clic()方法如下

  1. Flipsnap.prototype._click = function(event) {
  2. var self = this;
  3. event.stopPropagation();
  4. event.preventDefault();
  5. };

10.定义Flipsnap类的状态判断方法hasNext()

  1. Flipsnap.prototype.hasNext = function() {
  2. var self = this;
  3. return self.currentPoint < self._maxPoint;
  4. };

11.定义Flipsnap类的状态判断方法hasPrev()

  1. Flipsnap.prototype.hasPrev = function() {
  2. var self = this;
  3. return self.currentPoint > 0;
  4. };

12.定义Flipsnap类的跳转下一个方法toNext()

  1. Flipsnap.prototype.toNext = function(transitionDuration) {
  2. var self = this;
  3. if (!self.hasNext()) {
  4. return;
  5. }
  6. self.moveToPoint(self.currentPoint + 1, transitionDuration);
  7. };

13.定义Flipsnap类的跳转上一个方法toPrev()

  1. Flipsnap.prototype.toPrev = function(transitionDuration) {
  2. var self = this;
  3. if (!self.hasPrev()) {
  4. return;
  5. }
  6. self.moveToPoint(self.currentPoint - 1, transitionDuration);
  7. };

14.定义Flipsnap类的跳转上一个方法destroy()

  1. Flipsnap.prototype.destroy = function() {
  2. var self = this;
  3. eventTypes.forEach(function(type) {
  4. self.element.removeEventListener(events.start[type], self, false);
  5. });
  6. };

取消在各个节点上的事件监听

15.将Flipsnap模块化

  1. if (typeof exports == 'object') {
  2. module.exports = Flipsnap;
  3. }
  4. else if (typeof define == 'function' && define.amd) {
  5. define(function() {
  6. return Flipsnap;
  7. });
  8. }
  9. else {
  10. window.Flipsnap = Flipsnap;
  11. }
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注