开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第3天,点击查看活动详情。
前言
作为一个前端,写轮播(又叫跑马灯)是无法避开的一个需求点。
而随着各种框架以及UI库的出现,已经很少有人会自己去写轮播了。
最近刚好想脱离于VUE/REACT框架轮播图,手写一个基于 jQuery 的扩展,尽可能的提高复用性,并将写的过程中的一些思维发散进行了一个整理,进行记录分享。
需求拆解
其实就是最常见的一个轮播,如下图所示:
- 有一系列的图片做成的轮播,可以逐个展示;
- 交互1:左右按钮,点击切换轮播,按照既定的动画/展示效果进行图片的切换,并和下部工具条进行联动;
- 交互2:点击下部工具条,可以跳转到指定的图片上;
- 交互3:点击每张图片都会有一个回调函数,进行指定操作:跳转/锚点…
就是很常见的一个特效,也很少有人花功夫去重复造轮子了,本着颗粒归仓的原则,简单记录一下。
思路整理
思路主要分为 个部分:
一、技术选型
所谓的技术选型只是因为本次需求希望脱离 VUE/REACT 框架去做,也就是说要基于原生 html+js+css 去实现。这样一来就会脱离 webpack 一类的打包工具,有些兼容性方面的问题就需要去考虑一下了。
最终选择 jQuery 的原因有三个:
- 同页面已经引入了 jquery
- 站在 html+js+css 的技术栈角度,难免会操作dom,$ 是一个不错的选择
- 本案例图片切换效果是 渐入渐出 ,而 jQuery 刚好有这么一对方法 fadeIn/fadeOut
二、使用方法
既然已经确定了技术栈,那么就要想一下,如何去调用这个方法更好呢?而在如何调用方法的前面就是,是直接操作dom,还是以数据去驱动 dom 的渲染?
我经常用的几个方法如下:
- const loop = new LoopEvent(selector, arr, fn)
- $(selector).loopEvent(arr, fn)
- loopEvent(selector, arr, fn)
第二个问题当然是以数据去驱动dom的渲染了。
思考结束,我先放一下我的使用方法:
<div class="loop"></div>
<script>
var loop = new LoopEvent(
$(".loop"), /* 传入 $ 对象,省去方法里面再选择 */
Array(16).fill({ /* 填充一个长度为16的数组 */
id: 1,
imgUrl:
"https://img2.baidu.com/it/u=1768098264,2761758584&fm=253&fmt=auto&app=120&f=JPEG?w=634&h=420",
}),
function (id) { /* 回调函数的方法传入一个 function,点击图片时进行调用 */
window.open("http://www.baidu.com?id=" + id);
}
);
window.loopT =
loopT === undefined
? setInterval(function () {
loop.next();
}, 5000)
: t;
</script>
三、切换/交互效果
这一块其实没什么好考虑的,因为一般情况下已经确定了,比如这次做的就是 渐入渐出 的效果。
但是这里会发散一下,如果没有 jQuery 的 fadeIn/fadeOut 如何去做这个 渐入渐出 嘞?
- jQuery 有个方法 叫 animate,支持链式调用,在 animate 结束以后,调用切换
- 使用 css3 过渡效果,给当前图片加个 样式,再做个定时器,进行切换
- 使用周期性定时器修改透明度
- … (还有啥?)
四、思维发散
首先是本案例的扩展:
- 界面UI效果,支持3D效果,例如:魔方效果,手风琴效果,翻页效果,纸牌效果…
- 图片切换效果,渐入渐出、溶解、飞入飞出(参考 PPT 里面切换效果)
然后是对比一下 原生的写法,和框架的写法:
- 以VUE为代表的数据双向绑定的的写法,首先是数据驱动,会将当前展示的图片保存在一个变量进行记录,然后通过切换变量结合Vue动画来实现轮播效果,而变量是决定整个 demo 的核心,且父组件可以通过操作 props 来操控单签组件;
- 原生则是在封装的方法内部,使用闭包的方式保存一个变量,每次修改变量的同时会操作 dom,实现整个轮播效果;
可见,数据双向绑定即 通过数据劫持结合发布订阅模式 对于原生应用的打击是跨维度的。
代码分析
代码分为两块,一块是结合使用的 css 样式,毕竟把样式都写在 js 里面,不太好看,另一块是 js。
首先是 css,很朴实无华的一段css。
/* loop */
.loop {
position: relative;
height: 400px;
}
.loop-item {
height: 100%;
width: 100%;
background-size: 100% auto;
background-position: center;
position: absolute;
background-repeat: no-repeat;
}
.loop-left, .loop-right {
position: absolute;
z-index: 999;
top: 50%;
margin-top: -25px;
width: 50px;
height: 50px;
transform: rotate(45deg);
cursor: pointer;
}
.loop-left {
left: 100px;
border-bottom: 5px solid #fff;
border-left: 5px solid #fff;
}
.loop-right {
right: 100px;
border-top: 5px solid #fff;
border-right: 5px solid #fff;
}
.loop-left:hover, .loop-right:hover {
border-color: blueviolet;
}
.loop-tools {
position: absolute;
bottom: 30px;
display: flex;
width: 40%;
z-index: 999;
margin-left: 30%;
}
.loop-tools div {
flex: 1;
height: 6px;
background: #ccc;
margin: 0 5px;
cursor: pointer;
}
.loop-active {
background: #fff !important;
}
js 是这个demo的核心。本来准备使用 class 关键字构建一个类的,后来考虑到有些浏览器不认识 class。那就先祝福这个浏览器能与世长存吧!!!
最后使用 fuction 写了个构造器,所有的交互都将放在里面,避免对全局造成污染。
具体细节看注释吧:
/**
* class EventLoop
* @param {$(Selector)} $wrap
* @param {any[]} data
* @param {string | null} clickCallback
*/
function LoopEvent($wrap, data, clickCallback) {
this.$wrap = $wrap; /* 容器 */
this._data = data; /* 原始数据 */
this._active = 0; /* 当前展示项 */
var that = this; /* 用一个变量保存 this,解决 $ 内部 this 指向问题 */
// 初始化容器
this.initEvent = function () {
// 添加所有的轮播项
this.$wrap.append(
this._data
.map(function (item, i) {
return (
'<div data-id="' +
item.id +
'" data-idx="' +
i +
'" class="loop-item" style="background-image: url(' +
item.imgUrl +
')"></div>'
);
})
.join("")
);
// 添加左右按钮
this.$wrap.append(
'<div class="loop-left"></div><div class="loop-right"></div><div class="loop-tools"></div>'
);
// 添加底部工具条
this.$wrap.find(".loop-tools").html(
this._data.map(function (item, i) {
return '<div data-idx="' + i + '"></div>';
})
);
// 初始化轮播图及工具条展示项
this.$wrap.find(".loop-item").eq(this._active).show();
this.$wrap
.find(".loop-item")
.eq(this._active)
.siblings(".loop-item")
.hide();
this.$wrap
.find(".loop-tools")
.children("div")
.eq(this._active)
.addClass("loop-active");
// 为每一张轮播图,绑定回调函数
this.$wrap.find(".loop-item").each(function () {
var id = $(this).attr("data-id");
if (clickCallback) {
$(this).on("click", function () {
return clickCallback(id);
});
}
});
};
// 调用初始化事件
this.initEvent();
// 切换轮播,单独提出切换效果,供切换时回调
this.autoLoop = function () {
this.$wrap.find(".loop-item").eq(this._active).fadeIn();
this.$wrap
.find(".loop-item")
.eq(this._active)
.siblings(".loop-item")
.fadeOut();
this.$wrap
.find(".loop-tools")
.children("div")
.eq(this._active)
.addClass("loop-active");
this.$wrap
.find(".loop-tools")
.children("div")
.eq(this._active)
.siblings("div")
.removeClass("loop-active");
};
// 下一张
this.next = function () {
this._active =
this._active + 1 > this._data.length - 1 ? 0 : this._active + 1;
this.autoLoop();
};
// 上一张
this.last = function () {
this._active =
this._active - 1 < 0 ? this._data.length - 1 : this._active - 1;
this.autoLoop();
};
// 某一张
this.choice = function (n) {
this._active = n;
this.autoLoop();
};
// 绑定左按钮 点击事件
this.$wrap.find(".loop-left").on("click", function () {
return that.last();
});
// 绑定右按钮 点击事件
this.$wrap.find(".loop-right").on("click", function () {
return that.next();
});
// 绑定工具条 点击事件
this.$wrap
.find(".loop-tools")
.children("div")
.each(function () {
$(this).on("click", function () {
that.choice(Number($(this).attr("data-idx")));
});
});
}
// 暴露 出 initLoop 方法,供外部使用
function initLoop() {
var loop = new LoopEvent(
$(".loop"),
Array(16).fill({
id: 1,
imgUrl:
"https://img2.baidu.com/it/u=1768098264,2761758584&fm=253&fmt=auto&app=120&f=JPEG?w=634&h=420",
}),
function (id) {
window.open("http://www.baidu.com?id=" + id);
}
);
window.loopT =
loopT === undefined
? setInterval(function () {
loop.next();
}, 5000)
: t;
}
// 调用
$(function () {
initLoop();
});
总结
如上就是本次分享的主要内容了,想要说的其实在下面。
Vue组件-景深卡片轮播,这个是我前段时间写的一个 vue 组件,刚好能和这个轮播进行对比,感兴趣的也可以对比着看一下。
站在看法的角度上对比一下,其实差别还是蛮明显的。在 vue 组件里面,我们只需要写一个静态模板,然后只需要关注每次交互以后数据的变化就好了。而在 jQuery 里面,我们还需要定义一个 autoLoop
的方法来实现 dom 的切换,这么一看,是不是就知道 vue 帮我们做了什么了?
另外,像本文中这个轮播,如果使用 vue 写一个 组件会更简单。
拜拜!
暂无评论内容