AlloyTouch.js 源码 学习笔记及原理说明
alloyTouch这个库其实可以做很多事的, 比较抽象, 需要我们用户好好的思考作者提供的实例属性和一些回调方法(touchStart,
change, touchMove, pressMove, tap, touchEnd, touchCancel, reboundEnd, animationEnd, correctionEnd).
哇, 提供了这么多回调啊, 因为我也刚玩,用到的不多。
change回调很常用(比如上拉,下拉刷新要用到),配合touchStart, animationEnd等,看需求吧
touchEnd, animationEnd(写轮播用到)。
因为我也刚用, 暂时没怎么研究其他回调的用法,但是我都在源码中标记了什麽时候触发回调的。
原理说明:
alloyTouch库其实是一个数字运动的库,我再看源码时,看见源码里_end()中计算各种比值和目标值,目的就是让dom在什么时间内运动到什么目标值。
当然对外也提供了to方法,让dom运动到用户指定的目标值。具体看源码注释。
源码笔记:
由于demo没全看完,所以有的注释理解的不太到位。有问题,欢迎指正。
1 /* AlloyTouch v0.2.1 2 * By AlloyTeam http://www.alloyteam.com/ 3 * Github: https://github.com/AlloyTeam/AlloyTouch 4 * MIT Licensed. 5 * Sorrow.X --- 添加注释,注释纯属个人理解 6 */ 7 8 // 兼容不支持requestAnimationFrame的浏览器 9 ;(function () { 10 'use strict'; 11 12 if (!Date.now) 13 Date.now = function () { return new Date().getTime(); }; 14 15 var vendors = ['webkit', 'moz']; 16 for (var i = 0; i < vendors.length && !window.requestAnimationFrame; ++i) { 17 var vp = vendors[i]; 18 window.requestAnimationFrame = window[vp + 'RequestAnimationFrame']; 19 window.cancelAnimationFrame = (window[vp + 'CancelAnimationFrame'] 20 || window[vp + 'CancelRequestAnimationFrame']); 21 } 22 if (/iP(ad|hone|od).*OS 6/.test(window.navigator.userAgent) // iOS6 is buggy 23 || !window.requestAnimationFrame || !window.cancelAnimationFrame) { 24 var lastTime = 0; 25 window.requestAnimationFrame = function (callback) { 26 var now = Date.now(); 27 var nextTime = Math.max(lastTime + 16, now); 28 return setTimeout(function () { callback(lastTime = nextTime); }, 29 nextTime - now); 30 }; 31 window.cancelAnimationFrame = clearTimeout; 32 } 33 }()); 34 35 (function () { 36 37 // 给元素绑定事件, 默认冒泡 38 function bind(element, type, callback) { 39 element.addEventListener(type, callback, false); 40 }; 41 42 // 三次贝塞尔 43 function ease(x) { 44 return Math.sqrt(1 - Math.pow(x - 1, 2)); 45 }; 46 47 // 相反的三次贝塞尔 48 function reverseEase(y) { 49 return 1 - Math.sqrt(1 - y * y); 50 }; 51 52 // INPUT|TEXTAREA|BUTTON|SELECT这几个标签就不用阻止默认事件了 53 function preventDefaultTest(el, exceptions) { 54 for (var i in exceptions) { 55 if (exceptions[i].test(el[i])) { 56 return true; 57 }; 58 }; 59 return false; 60 }; 61 62 var AlloyTouch = function (option) { 63 64 this.element = typeof option.touch === "string" ? document.querySelector(option.touch) : option.touch; // 反馈触摸的dom 65 this.target = this._getValue(option.target, this.element); // 运动的对象 66 this.vertical = this._getValue(option.vertical, true); // 不必需,默认是true代表监听竖直方向touch 67 this.property = option.property; // 被滚动的属性 68 this.tickID = 0; 69 70 this.initialValue = this._getValue(option.initialValue, this.target[this.property]); // 被运动的属性的初始值,默认从Transform原始属性拿值 71 this.target[this.property] = this.initialValue; // 给运动的属性赋值 72 this.fixed = this._getValue(option.fixed, false); 73 this.sensitivity = this._getValue(option.sensitivity, 1); // 默认是1, 灵敏度 74 this.moveFactor = this._getValue(option.moveFactor, 1); // move时的运动系数 75 this.factor = this._getValue(option.factor, 1); // 系数 76 this.outFactor = this._getValue(option.outFactor, 0.3); // 外部系数 77 this.min = option.min; // 不必需,滚动属性的最小值,越界会回弹 78 this.max = option.max; // 不必需,运动属性的最大值,越界会回弹, 一般为0 79 this.deceleration = 0.0006; // 减速系数 80 this.maxRegion = this._getValue(option.maxRegion, 600); // 最大区域, 默认60 81 this.springMaxRegion = this._getValue(option.springMaxRegion, 60); // 弹动的最大值区域, 默认60 82 this.maxSpeed = option.maxSpeed; // 最大速度 83 this.hasMaxSpeed = !(this.maxSpeed === undefined); // 是否有最大速度属性 84 this.lockDirection = this._getValue(option.lockDirection, true); // 锁定方向 85 86 var noop = function () { }; // 空函数 87 this.touchStart = option.touchStart || noop; 88 this.change = option.change || noop; 89 this.touchMove = option.touchMove || noop; 90 this.pressMove = option.pressMove || noop; 91 this.tap = option.tap || noop; 92 this.touchEnd = option.touchEnd || noop; 93 this.touchCancel = option.touchCancel || noop; 94 this.reboundEnd = option.reboundEnd || noop; // 回弹回调 95 this.animationEnd = option.animationEnd || noop; 96 this.correctionEnd = option.correctionEnd || noop; // 修改回调 97 98 this.preventDefault = this._getValue(option.preventDefault, true); // 默认是true,是否阻止默认事件 99 this.preventDefaultException = { tagName: /^(INPUT|TEXTAREA|BUTTON|SELECT)$/ }; // 这几个tag标签,阻止默认事件例外 100 this.hasMin = !(this.min === undefined); // 是否有min,和max属性 101 this.hasMax = !(this.max === undefined); 102 if (this.hasMin && this.hasMax && this.min > this.max) { // 最小值不能比最大值大啊 103 throw "the min value can't be greater than the max value." 104 }; 105 this.isTouchStart = false; // 触摸是否开始 106 this.step = option.step; // 步数(回弹) 107 this.inertia = this._getValue(option.inertia, true); // 默认true,开启惯性效果 108 109 this._calculateIndex(); // 添加this.currentPage属性,如果写轮播的话 110 111 this.eventTarget = window; 112 if(option.bindSelf){ 113 this.eventTarget = this.element; // 默认touchmove, touchend, touchcancel绑定在 window 上的, 如果option.bindSelf为真值,则绑定到反馈触摸的dom 114 }; 115 116 this._moveHandler = this._move.bind(this); // 函数赋值 117 // 反馈触摸的dom只绑定了touchstart(_start), window绑定了 touchmove(_move), touchend(_end), touchcancel(_cancel)方法 118 bind(this.element, "touchstart", this._start.bind(this)); 119 bind(this.eventTarget, "touchend", this._end.bind(this)); 120 bind(this.eventTarget, "touchcancel", this._cancel.bind(this)); 121 this.eventTarget.addEventListener("touchmove", this._moveHandler, { passive: false, capture: false }); // 使用 passive 改善的滚屏性能 122 this.x1 = this.x2 = this.y1 = this.y2 = null; // start时的坐标和move时的坐标 123 }; 124 125 AlloyTouch.prototype = { 126 _getValue: function (obj, defaultValue) { // 取用户的值还是使用默认值 127 return obj === undefined ? defaultValue : obj; 128 }, 129 _start: function (evt) { 130 this.isTouchStart = true; // 触摸开始 131 this.touchStart.call(this, evt, this.target[this.property]); // (1. touchStart(evt, propValue)回调) 132 cancelAnimationFrame(this.tickID); // 只要触摸就停止动画 133 this._calculateIndex(); // 重新计算this.currentPage属性值 134 this.startTime = new Date().getTime(); // 开始的时间戳 135 this.x1 = this.preX = evt.touches[0].pageX; // 开始前的坐标保存到x,y 和 preXY去 136 this.y1 = this.preY = evt.touches[0].pageY; 137 this.start = this.vertical ? this.preY : this.preX; // 如果监听竖直方向则取y坐标,否则横向方向取x坐标 138 this._firstTouchMove = true; // 开始move(这个条件为_move做铺垫) 139 this._preventMove = false; // 不阻止dom继续运动(开启惯性运动之旅的条件之一 哈哈) 140 }, 141 _move: function (evt) { 142 if (this.isTouchStart) { // 触摸开始了 143 var len = evt.touches.length, // 手指数量 144 currentX = evt.touches[0].pageX, // move时的坐标 145 currentY = evt.touches[0].pageY; 146 147 if (this._firstTouchMove && this.lockDirection) { // 开始move 且 锁定方向 148 var dDis = Math.abs(currentX - this.x1) - Math.abs(currentY - this.y1); // 是左右滑动还是上下滑动(x>y为水平, y>x为竖直) 149 if (dDis > 0 && this.vertical) { // 左右滑动 且 监听竖直方向 150 this._preventMove = true; // 阻止dom继续运动 151 } else if (dDis < 0 && !this.vertical) { // 竖直滑动 且 监听横向方向 152 this._preventMove = true; 153 }; // 以上2种情况直接不开启惯性运动之旅(因为左右滑动的话this.vertical需为false,竖直滑动的话this.vertical需为true) 154 this._firstTouchMove = false; // 变成false, 为了手指连续移动中,此方法就不用进来了 155 }; 156 if(!this._preventMove) { // move时 属性运动(关闭惯性运动后, 其实只有此运动了, 手指移动才会运动, 离开则不会运动了) 157 158 var d = (this.vertical ? currentY - this.preY : currentX - this.preX) * this.sensitivity; // 根据竖直还是左右来确定差值 * 灵敏度 159 var f = this.moveFactor; // 变量f的值为 move时的运动系数(默认1) 160 if (this.hasMax && this.target[this.property] > this.max && d > 0) { // 有最大值 且 运动属性值>最大值 且 坐标差值d>0 161 f = this.outFactor; 162 } else if (this.hasMin && this.target[this.property] < this.min && d < 0) { // 有最小值 且 运动属性值<最小值 且 坐标差值d<0 163 f = this.outFactor; // 满足以上2中条件 变量f 的值就变成 this.outFactor(默认0.3) 164 }; 165 d *= f; // 坐标差值再乘以运动系数 166 this.preX = currentX; // 把move时的坐标保存到preXY去 167 this.preY = currentY; 168 if (!this.fixed) { // this.fixed默认false(fixed一旦固定了,move时, dom也不会运动) 169 this.target[this.property] += d; //把坐标的差值且乘以运动系数后的结果累加给运动的对象(被transform.js加工后的dom对象) 170 // console.log('_move: ' + this.target[this.property]); 171 }; 172 this.change.call(this, this.target[this.property]); // (2. move时的change(evt, propValue)回调) 173 var timestamp = new Date().getTime(); // move时的时间戳 174 if (timestamp - this.startTime > 300) { // move时的时间戳和start时的时间戳大于300的话 175 this.startTime = timestamp; // move时的时间戳赋值给start时的时间戳 176 this.start = this.vertical ? this.preY : this.preX; // 重新计算this.start值 177 }; 178 this.touchMove.call(this, evt, this.target[this.property]); // (3. touchMove(evt, propValue)回调) 179 }; 180 181 if (this.preventDefault && !preventDefaultTest(evt.target, this.preventDefaultException)) { //阻止默认事件除了INPUT|TEXTAREA|BUTTON|SELECT这几个标签 182 evt.preventDefault(); 183 }; 184 185 if (len === 1) { // 一根手指 186 if (this.x2 !== null) { //一开始为null 187 evt.deltaX = currentX - this.x2; // move移动时的差值 188 evt.deltaY = currentY - this.y2; 189 190 } else { 191 evt.deltaX = 0; // 一开始差值为0啦 192 evt.deltaY = 0; 193 } 194 this.pressMove.call(this, evt, this.target[this.property]); // (4. pressMove(evt, propValue)回调) 195 } 196 this.x2 = currentX; //把本次坐标赋值给x2,y2 197 this.y2 = currentY; 198 } 199 }, 200 _end: function (evt) { 201 if (this.isTouchStart) { // 触摸开始了 202 203 this.isTouchStart = false; // 触摸开始变量置为false(_move方法进不去了) 204 var self = this, // 存个实例 205 current = this.target[this.property], // 当前运动对象的运动属性的值 206 triggerTap = (Math.abs(evt.changedTouches[0].pageX - this.x1) < 30 && Math.abs(evt.changedTouches[0].pageY - this.y1) < 30); // 是否触发tap事件回调 207 if (triggerTap) { // 触发tap事件 208 this.tap.call(this, evt, current); // (5. tap(evt, propValue)回调) 209 }; 210 211 if (this.touchEnd.call(this, evt, current, this.currentPage) === false) return; // (6. touchEnd(evt, propValue, 当前第几页)回调)这个主要给轮播用的吧 212 213 if (this.hasMax && current > this.max) { // 有最大值 且 当前运动对象的运动属性的值大于最大值 214 215 this._to(this.max, 200, ease, this.change, function (value) { // (最大小值, time, 曲线, change函数, fn) 216 this.reboundEnd.call(this, value); 217 this.animationEnd.call(this, value); 218 }.bind(this)); 219 220 } else if (this.hasMin && current < this.min) { // 有最小值 且 当前运动对象的运动属性的值小于最小值 221 222 this._to(this.min, 200, ease, this.change, function (value) { 223 this.reboundEnd.call(this, value); 224 this.animationEnd.call(this, value); 225 }.bind(this)); 226 227 } else if (this.inertia && !triggerTap && !this._preventMove) { // 开启惯性效果(默认为true) 且 不触发tap事件 且 this._preventMove为false; 228 229 var dt = new Date().getTime() - this.startTime; // _end时的时间戳和_move时的时间戳的差值 230 if (dt < 300) { // 小于300ms就执行惯性运动 231 var distance = ((this.vertical ? evt.changedTouches[0].pageY : evt.changedTouches[0].pageX) - this.start) * this.sensitivity, // _end中的坐标与_move中坐标的差值乘以灵敏度 232 speed = Math.abs(distance) / dt, // 速度为坐标差值/时间戳差值 233 speed2 = this.factor * speed; // 速度2为 系数(默认1)乘以速度 234 if(this.hasMaxSpeed && speed2 > this.maxSpeed) { // 有最大速度 且 速度2大于最大速度 235 speed2 = this.maxSpeed; // 速度2就为最大速度 236 }; 237 238 // 目标值destination = 当前运动对象的运动属性的值 + (速度2*速度2)/(2*减速系数)*(-1||1); 239 var destination = current + (speed2 * speed2) / (2 * this.deceleration) * (distance < 0 ? -1 : 1); 240 // console.log('distance: '+ distance); 241 // console.log('目标值destination: '+ destination); 242 // console.log('差值: '+ destination > current); 243 244 var tRatio = 1; // 比例 245 if (destination < this.min ) { // 目标值 比 最小值 小 246 if (destination < this.min - this.maxRegion) { 247 tRatio = reverseEase((current - this.min + this.springMaxRegion) / (current - destination)); 248 destination = this.min - this.springMaxRegion; 249 } else { 250 tRatio = reverseEase((current - this.min + this.springMaxRegion * (this.min - destination) / this.maxRegion) / (current - destination)); 251 destination = this.min - this.springMaxRegion * (this.min - destination) / this.maxRegion; 252 } 253 } else if (destination > this.max) { // 目标值 比 最大值 大 254 if (destination > this.max + this.maxRegion) { 255 tRatio = reverseEase((this.max + this.springMaxRegion - current) / (destination - current)); 256 destination = this.max + this.springMaxRegion; 257 } else { 258 tRatio = reverseEase((this.max + this.springMaxRegion * ( destination-this.max) / this.maxRegion - current) / (destination - current)); 259 destination = this.max + this.springMaxRegion * (destination - this.max) / this.maxRegion; 260 261 } 262 }; 263 264 // 持续时间duration = 数字舍入(速度/减速系数) * 比例; 265 var duration = Math.round(speed / self.deceleration) * tRatio; 266 // console.log('持续时间duration: ' + duration); 267 268 // end方法计算好的目标值和持续时间传入_to方法,运动起来吧 269 self._to(Math.round(destination), duration, ease, self.change, function (value) { // 回调函数的value 就是 destination 270 271 if (self.hasMax && self.target[self.property] > self.max) { // 有最大值 且 运动属性的值大于最大值 272 273 cancelAnimationFrame(self.tickID); 274 self._to(self.max, 600, ease, self.change, self.animationEnd); 275 276 } else if (self.hasMin && self.target[self.property] < self.min) { // 有最小值 且 运动属性的值小于最小值 277 278 cancelAnimationFrame(self.tickID); 279 self._to(self.min, 600, ease, self.change, self.animationEnd); 280 281 } else { 282 self._correction(); // 回弹 283 }; 284 285 self.change.call(this, value); // (7. change(运动属性的值)回调函数) 286 }); 287 } else { 288 self._correction(); // 回弹 289 } 290 } else { 291 self._correction(); // 回弹 292 }; 293 294 // 阻止默认事件 295 if (this.preventDefault && !preventDefaultTest(evt.target, this.preventDefaultException)) { 296 evt.preventDefault(); 297 }; 298 299 }; 300 // 坐标置null 301 this.x1 = this.x2 = this.y1 = this.y2 = null; 302 }, 303 // 提供目标值, 持续时间, 然后根据时间戳和time持续时间的差值比较, 时间戳< time的话就一直调用动画,否则结束 304 _to: function (value, time, ease, onChange, onEnd) { // value:目标值, time:持续时间, ease: 曲线动画, onChange: this.change回调函数(用户的), onEnd回调 305 if (this.fixed) return; // fixed(默认false)有真值就return掉 306 var el = this.target, // 运动的对象 307 property = this.property; // 运动的属性 308 var current = el[property]; // 运动对象运动属性当前的值 309 var dv = value - current; // 目标值与当前属性的差值 310 var beginTime = new Date(); // 开始时间戳 311 var self = this; // 存个实例 312 var toTick = function () { 313 314 var dt = new Date() - beginTime; // 时间戳差值 315 if (dt >= time) { // 时间戳差值大于持续时间 316 el[property] = value; // 把目标值赋值给dom属性 317 onChange && onChange.call(self, value); // (7. change(目标值)回调函数) 318 onEnd && onEnd.call(self, value); // onEnd回调 319 return; 320 }; 321 el[property] = dv * ease(dt / time) + current; 322 // console.log(el[property]); 323 self.tickID = requestAnimationFrame(toTick); // 动画自调用 324 onChange && onChange.call(self, el[property]); //(7. change(属性值)回调函数) 325 }; 326 toTick(); // 调用 327 }, 328 // 该函数用来当动画完成后根据this.step修正一点(回弹效果) 329 _correction: function () { 330 if (this.step === undefined) return; // step没赋值的话就return掉 331 var el = this.target, // 运动的对象 332 property = this.property; // 运动对象的运动属性 333 var value = el[property]; // 运动对象运动属性的值 334 var rpt = Math.floor(Math.abs(value / this.step)); // 向下取整(取绝对值(运动对象运动属性的值/ this.step值)) 335 var dy = value % this.step; // 运动对象运动属性的值取余数 336 337 if (Math.abs(dy) > this.step / 2) { // 我想这里又应用了啥物理原理根据条件判断,来计算value目标值的,然后调用_to方法执行惯性运动 338 this._to((value < 0 ? -1 : 1) * (rpt + 1) * this.step, 400, ease, this.change, function (value) { 339 this._calculateIndex(); 340 this.correctionEnd.call(this, value); 341 this.animationEnd.call(this, value); 342 }.bind(this)); 343 } else { 344 this._to((value < 0 ? -1 : 1) * rpt * this.step, 400, ease, this.change, function (value) { 345 this._calculateIndex(); // 重新计算this.currentPage值 346 this.correctionEnd.call(this, value); // (8. correctionEnd(属性值)回调函数) 347 this.animationEnd.call(this, value); // (9. animationEnd(属性值)回调函数) 348 }.bind(this)); 349 } 350 }, 351 _cancel: function (evt) { 352 var current = this.target[this.property]; 353 this.touchCancel.call(this, evt, current); 354 this._end(evt); 355 }, 356 // 给用户使用的, 控制dom以不同的曲线动画运动 357 to: function (v, time, user_ease) { 358 this._to(v, this._getValue(time, 600), user_ease || ease, this.change, function (value) { 359 this._calculateIndex(); 360 this.reboundEnd.call(this, value); // (10. reboundEnd(属性值)回调函数) 361 this.animationEnd.call(this, value); // (9. animationEnd(属性值)回调函数) 362 }.bind(this)); 363 364 }, 365 // 计算this.currentPage值 366 _calculateIndex: function () { 367 if (this.hasMax && this.hasMin) { 368 this.currentPage = Math.round((this.max - this.target[this.property]) / this.step); // 当前第几页,比如轮播图的第几个,从0开始 369 } 370 } 371 372 }; 373 374 // 抛出去 375 if (typeof module !== 'undefined' && typeof exports === 'object') { 376 module.exports = AlloyTouch; 377 } else { 378 window.AlloyTouch = AlloyTouch; 379 }; 380 381 })();
使用姿势:(由于此库比较抽象,使用姿势需要根据需求来使用,以下提供了3个demo的使用姿势,基本差不多,可以用手机扫二维码看看(由于编写html页面时,没考虑手机屏幕的适应,所以凑合这点吧,哈哈))
1 // es5 语法 2 // 列表加载更多 3 var List = function() { 4 this.list_Target = document.querySelector("#list_target"); 5 this.Ul = this.list_Target.children[0]; 6 this.oList = document.querySelector("#list"); 7 //给element注入transform属性 8 Transform(this.list_Target); 9 10 this.at = null; 11 this.loading = false; 12 this.index = 21; 13 }; 14 15 Object.assign(List.prototype, { 16 init: function() { 17 var self = this; 18 this.at = new AlloyTouch({ 19 touch: '#list',//反馈触摸的dom 20 vertical: true,//不必需,默认是true代表监听竖直方向touch 21 target: this.list_Target, //运动的对象 22 property: "translateY", //被滚动的属性 23 factor: 1,//不必需,默认值是1代表touch区域的1px的对应target.y的1 24 inertia: true, 25 min: this.oList.offsetHeight - this.list_Target.offsetHeight, //不必需,滚动属性的最小值 26 max: 0, //不必需,滚动属性的最大值 27 step: 2, 28 touchStart: function() { 29 self.getMin(); 30 }, 31 change: function(v) { 32 // console.log(v); 33 if (v < this.min && !self.loading) { 34 self.loading = true; 35 self.loadMore(); 36 }; 37 } 38 }); 39 }, 40 getMin: function() { 41 this.at.min = -parseInt(getComputedStyle(this.list_Target).height) + this.oList.offsetHeight; 42 }, 43 loadMore: function() { 44 setTimeout(function() { 45 this.loading = false; 46 var Oli = null, 47 i = 0, 48 len = 20; 49 50 for (; i < len; i ++) { 51 this.index += 1; 52 Oli = document.createElement('li'); 53 Oli.innerHTML = this.index; 54 this.Ul.appendChild(Oli); // 这里测试,不推荐这么写啊, 太消耗性能了 55 }; 56 57 this.getMin(); 58 }.bind(this), 500); 59 } 60 }); 61 62 new List().init(); 63 64 // es6 语法 65 /*let flower = (new class { 66 constructor() { 67 this.at = null; 68 this.target = this.$('testImg'); 69 Transform(this.target); 70 } 71 72 $(id) { 73 return document.querySelector('#' + id); 74 } 75 76 init() { 77 this.at = new AlloyTouch({ 78 touch: this.target,//反馈触摸的dom 79 vertical: false,//不必需,默认是true代表监听竖直方向touch 80 target: this.target, //运动的对象 81 property: "rotateY", //被滚动的属性 82 factor: 1,//不必需,默认值是1代表touch区域的1px的对应target.y的1 83 inertia: true, 84 step: 100 85 }); 86 } 87 }).init();*/ 88 89 90 // 花朵 91 var flower = function() { 92 93 var target = $('testImg'); 94 Transform(target); 95 96 function $(id) { 97 return document.querySelector('#' + id); 98 }; 99 100 return function() { 101 return new AlloyTouch({ 102 touch: target,//反馈触摸的dom 103 vertical: false,//不必需,默认是true代表监听竖直方向touch 104 target: target, //运动的对象 105 property: "rotateY", //被滚动的属性 106 factor: 1,//不必需,默认值是1代表touch区域的1px的对应target.y的1 107 inertia: true, 108 step: 100 109 }); 110 }; 111 }()(); 112 113 114 // 轮播 115 var carousel = function() { 116 var scroller = document.querySelector('#carousel_scroller'); 117 Transform(scroller); 118 var aA = document.querySelectorAll('#nav a'); 119 var at = null; 120 var tickId = null; 121 122 function init() { 123 at = new AlloyTouch({ 124 touch: '#carousel_container', 125 target: scroller, 126 vertical: false, 127 property: 'translateX', 128 step: window.innerWidth, 129 max: 0, 130 min: - window.innerWidth * 3, 131 touchStart: function() { 132 clearInterval(tickId); 133 }, 134 touchEnd: function(evt, v, index) { 135 var value = -(this.step * index); 136 var dt = v - value; 137 console.log(dt); 138 139 if (v > this.max) { // 属性值大于最大值取最大值 140 this.to(this.max); 141 } else if (v < this.min) { // 属性值小于最小值取最小值 142 this.to(this.min); 143 } else if (Math.abs(dt) < 30) { // 2边空隙小于30就回到当前值 144 this.to(value); 145 } else if (dt > 0) { // 大于0往右边滚动一个 146 this.to(value + this.step); 147 } else { // 小于0就往左边滚动一个 148 this.to(value - this.step); 149 }; 150 loop(); 151 return false; 152 }, 153 animationEnd: function(evt, v) { 154 Array.prototype.slice.call(aA).forEach(function(item, index) { 155 item.className = ''; 156 }); 157 aA[this.currentPage].className = 'active'; 158 } 159 }); 160 }; 161 162 // 循环播放 163 function loop() { 164 tickId = setInterval(function() { 165 this.currentPage += 1; 166 if (this.currentPage > 3) { 167 this.currentPage = 0; 168 }; 169 this.to(-(this.currentPage * this.step)); 170 }.bind(at), 2000); 171 }; 172 173 return { 174 init: init, 175 loop: loop 176 }; 177 }(); 178 carousel.init(); 179 carousel.loop();
我觉得还是用手机体验一把alloyTouch的灵活和强大吧,手机对准扫一扫吧。
3个关于alloyTouch的小demo。
更多demo,请去github看看。
ps:
此库还是很有用的,很喜欢。
github: https://github.com/AlloyTeam/AlloyTouch