Omi-touch实战 移动端图片轮播组件的封装
pc端的轮播,移动端的轮播都很常见。一年前,我还为手机端没有左滑,右滑事件从而封装了一个swipe库,可以自定义超过多少滑动时间就不触发,也可以设置滑动多少距离才触发,这一个功能的代码就达到400多行了,当我遇到finger.js后,就直接抛弃自己写的那个库了。
扯了那么多,是时候进入正题,这里用touch插件来写一个轮播组件的封装。
使用姿势如下:
var banner = new Banner({ images: ['ci5.jpg', 'ci6.jpg', 'ci7.jpg', 'ci8.jpg'], title: ['原来你不爱我-威仔', '八年的爱', '终结敷衍', '我也憧憬过'], defaultIndex: 2, imgPath: './images/', style: { height: '210px', } }); setTimeout(() => { banner.setData({ images: ['47.jpg', '48.jpg', '49.jpg'], title: ['怪我', '异性朋友', '我很快乐'], defaultIndex: 3, imgPath: './images/', style: { height: '220px', top: '10%' } }) }, 1000);
说明:实例化轮播对象时,可以传入数据。
images: 图片名,
title: 标题,
defaultIndex: 轮播默认显示第几个图,
imgPath: 图片的路径(为了你方便少写点重复的路径名),
style: 样式, height:指轮播图的高度, top指轮播距离屏幕顶部的距离,单位随意,可以px, rem, %.
有时候换数据,则使用实例的setData方法,传入参数即可,轮播会自己换数据和title.
来看看具体实现:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <title>touch_banner</title> <style> * { margin: 0; padding: 0; } </style> <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=0"> </head> <body> <script src="../src/omi_soda.js"></script> <script> Omi.OmiTouch.init(); class Banner extends Omi.Component { constructor(data) { super(data); this.bannerVar = { aA: null, at: null, tickId: null }; this.data.imgWidth = this.data.imgCount = this.data.divWidth = this.data.minValue = 0; this.setData(null, true); } installed() { this.bannerVar.aA = this.refs.aTagDiv.getElementsByTagName('a'); this.fixDefaultIndexImage(); this.fixStyle(); } setData(option, first) { clearInterval(this.bannerVar.tickId); option = option || this.data; if (option.imgPath) { option.images = option.images.map(function(imgDom) { return option.imgPath + imgDom; }); }; this.data.title = option.title; this.data.images = option.images; this.data.style = option.style; if (option.defaultIndex >= 0 && option.defaultIndex <= option.images.length - 1) { this.data.defaultIndex = option.defaultIndex; } else { this.data.defaultIndex = 0; }; this.data.imgCount = option.images.length; this.data.imgWidth = 100 / this.data.imgCount; this.data.divWidth = 100 * this.data.imgCount; this.data.minValue = -(window.innerWidth) * (this.data.imgCount - 1); this.touch && (this.touch.min = -(window.innerWidth) * (this.data.imgCount - 1)); if (!first) { banner.update(); this.fixDefaultIndexImage(); this.fixStyle(); }; } fixDefaultIndexImage() { this.touch.currentPage = this.data.defaultIndex; this.touch.to(- (this.touch.currentPage)*(this.touch.step)); this.animationEnd(); this.loop(); } fixStyle() { if (this.data.style && this.data.style.top) this.refs.carousel_container.style.top = this.data.style.top; if (this.data.style && this.data.style.height) { var aImg = Array.prototype.slice.call(this.refs.scroller.getElementsByTagName('img')); aImg.forEach(function(img) { img.style.height = this.data.style.height; }, this); }; } style() { return ` .carousel_container { position: absolute; top: 0%; } .carousel { overflow: hidden; position: relative; } .carousel_div { position: relative; font-size: 0; } .nav { position: absolute; bottom: .3rem; right: .5rem; } .nav a { display: inline-block; width: 6px; height: 6px; background: #ffffff; cursor: pointer; -moz-border-radius: 5px; -webkit-border-radius: 5px; border-radius: 5px; border: 1px solid #808080; transition: all .5s ease-in; margin: 0 .15rem; } .nav a.active { background-color: #ffd800; width: 10px; } .title { position: absolute; bottom: .0rem; left: .3rem; color: #fff; width: auto; height: 1.8rem; line-height: 1.8rem; font-size: 15px; /*background: red;*/ } `; } touchStart() { clearInterval(this.bannerVar.tickId); } touchEnd(evt, v, index) { var value = -(this.touch.step * index); var dt = v - value; // console.log(dt); if (v > this.touch.max) { // 属性值大于最大值取最大值 this.touch.to(this.touch.max); /*this.touch.target.translateX = this.touch.min + this.touch.step; this.touch.to(this.touch.min);*/ } else if (v < this.touch.min) { // 属性值小于最小值取最小值 this.touch.to(this.touch.min); /*this.touch.target.translateX = -this.touch.step; this.touch.to(this.touch.max);*/ } else if (Math.abs(dt) < 30) { // 2边空隙小于30就回到当前值 this.touch.to(value); } else if (dt > 0) { // 大于0往右边滚动一个 this.touch.to(value + this.touch.step); } else { // 小于0就往左边滚动一个 this.touch.to(value - this.touch.step); }; this.loop(); return false; } animationEnd(v) { Array.prototype.slice.call(this.bannerVar.aA).forEach(function(item) { item.className = ''; }); this.bannerVar.aA[this.touch.currentPage].className = 'active'; this.refs.title.innerHTML = this.data.title[this.touch.currentPage]; } // 循环播放 loop() { this.bannerVar.tickId = setInterval(function() { this.touch.currentPage += 1; if (this.touch.currentPage > this.data.imgCount - 1) { this.touch.currentPage = 0; this.touch.target.translateX = -this.touch.step; }; this.touch.to(-(this.touch.currentPage * this.touch.step)); }.bind(this), 2000); } render() { return ` <div omi-touch ref="carousel_container" motionRef="scroller" touchInstance="touch" vertical="false" property="translateX" max="0" step="window.innerWidth" min="{{minValue}}" touchStart="touchStart" touchEnd="touchEnd" animationEnd="animationEnd" class="carousel_container"> <div class="carousel"> <div ref="scroller" style="width: {{divWidth}}%" class="carousel_div"> <img o-repeat="imgItem in images" style="width: {{imgWidth}}%" src="{{imgItem}}"> </div> <div id="nav" class="nav" ref="aTagDiv"> <a o-repeat="aItem in images" data-index="{{$index}}" class=" "></a> </div> <div class="title"> <span ref="title"></span> </div> </div> </div> `; } }; var banner = new Banner({ images: ['ci5.jpg', 'ci6.jpg', 'ci7.jpg', 'ci8.jpg'], title: ['原来你不爱我-威仔', '八年的爱', '终结敷衍', '我也憧憬过'], defaultIndex: 2, imgPath: './images/', style: { height: '210px', } }); setTimeout(() => { banner.setData({ images: ['47.jpg', '48.jpg', '49.jpg'], title: ['怪我', '异性朋友', '我很快乐'], defaultIndex: 3, imgPath: './images/', style: { height: '220px', top: '10%' } }) }, 1000); setTimeout(() => { banner.setData({ images: ['47.jpg', '48.jpg', '49.jpg', '50.jpg'], title: ['怪我', '异性朋友', '我很快乐', '隔阂'], defaultIndex: 3, imgPath: './images/', style: { height: '220px', top: '20%' } }) }, 2000); setTimeout(() => { banner.setData({ images: ['47.jpg', '48.jpg', '49.jpg', '50.jpg', '48.jpg'], title: ['怪我', '异性朋友', '我很快乐', '隔阂', '如果这是命'], defaultIndex: 3, imgPath: './images/', style: { height: '250px', top: '30%' } }) }, 3000); setTimeout(() => { banner.setData({ images: ['47.jpg', '48.jpg', '49.jpg', '50.jpg', '48.jpg', '51.jpg'], title: ['怪我', '异性朋友', '我很快乐', '隔阂', '如果这是命', '别爱'], defaultIndex: 3, imgPath: './images/', style: { height: '270px', top: '40%' } }) }, 4000); Omi.render(banner, 'body'); </script> </body> </html>
注意:
使用dnt原作者的omi-touch,以上代码并不能成功运行。
原因: 1. AlloyTouch 的实例没有抛出,或者挂在到Component实例对象上去。
(改进版的,挂在到实例上去了,用户可以指定实例名,不指定会自动生成一个touchInstancs+id , id自增)
2. dom上的属性,只能是字符串,原作者只是简单的把字符串转换为数值,其实也够用了,也许当时作者没想太多。
(改进版的,属性名对应的属性值可以是任意表达式,会自动计算的)
3. 组件每一次更新时,都会实例化一个AlloyTouch对象,比较浪费
(改进版的,如果实例名一样的话,则不再重新实例化,只需把要运动的属性值更新即可)
说了那么多废话,上一下omi-touch源码(已修改部分代码,但是不会影响别的)
(function () { var OmiTouch = {}; // OmiTouch集合对象 var AlloyTouch = Omi.AlloyTouch; var Transform = Omi.Transform; var noop = function() { }; // 空函数 OmiTouch._instanceId = 0; // touch实例id OmiTouch.getInstanceId = function () { return OmiTouch._instanceId ++; // 自增 }; var preTouchInstanceName = null; // 获取绑定的函数 var getHandler = function(name, dom, instance) { // name: 属性值, dom: 反馈触摸的dom, instance实例 var value = dom.getAttribute(name); // 获取属性值 if (value === null) { // 没有函数就绑定noop空函数 return noop; }else{ return instance[value].bind(instance); // 否则返回一个新函数 } }; // 获取数值 /* var getNum = function(name, dom){ // name: 属性值, dom: 反馈触摸的dom var value = dom.getAttribute(name); // 获取属性值 if (value) { return parseFloat(value); // 把字符串转成数字返回 }; };*/ var getNum = function(name, dom){ // name: 属性值, dom: 反馈触摸的dom var value = eval('(' + dom.getAttribute(name) + ')'); // 获取属性值 return Object.prototype.toString.call(value) !== '[object Null]' ? value : undefined; }; OmiTouch.init = function(){ Omi.extendPlugin('omi-touch',function(dom, instance){ var target = instance.refs[dom.getAttribute('motionRef')]; // 找到要运动的dom var touchInstanceName = dom.getAttribute('touchInstance') || 'touchInstance' + OmiTouch.getInstanceId(); // 获取touch实例名, 默认touchInstance+id 自增 Transform(target, target.getAttribute('perspective') ? false : true); // 不在运动对象上写perspective属性,默认不要透视(perspective="true/false" 则开启透视,只有不写才关闭) var initialValue = dom.getAttribute('initialValue'); // 初始值 if (initialValue) { target[dom.getAttribute('property') || "translateY"] = parseInt(initialValue); // 默认有初始值,是上下滑动 }; if (preTouchInstanceName !== touchInstanceName) { // 实例名一样的话,就没必要重新生成实例了 instance[touchInstanceName] = new AlloyTouch({ touch: dom,//反馈触摸的dom vertical: dom.getAttribute('vertical') === 'false' ? false : true,//不必需,默认是true代表监听竖直方向touch target: target, //运动的对象 property: dom.getAttribute('property') || "translateY", //被运动的属性 min: getNum('min', dom), //不必需,运动属性的最小值 max: getNum('max', dom), //不必需,滚动属性的最大值 sensitivity: getNum('sensitivity', dom) ,//不必需,触摸区域的灵敏度,默认值为1,可以为负数 factor: getNum('factor', dom) ,//不必需,表示触摸位移与被运动属性映射关系,默认值是1 step: getNum('step', dom),//用于校正到step的整数倍 bindSelf: dom.getAttribute('bindSelf') === 'true' ? true : false, touchStart: getHandler('touchStart', dom, instance), change: getHandler('change', dom, instance), touchMove: getHandler('touchMove', dom, instance), touchEnd: getHandler('touchEnd', dom, instance), tap: getHandler('tap', dom, instance), pressMove: getHandler('pressMove', dom, instance), animationEnd: getHandler('animationEnd', dom, instance) }); preTouchInstanceName = touchInstanceName; }; }); } OmiTouch.destroy = function() { // 从Omi的插件集合移除该插件 delete Omi.plugins['omi-touch']; }; Omi.OmiTouch = OmiTouch; })();
这样就可以了,虽然改了些库代码。