最近看完了《Vue实战》这本书,这也是我第一次完整的看完的一本关于前端的书籍(现在还在看的是《CSS世界》,有点想放弃了)。照着书中的demo都写了一遍,虽然很多栗子非常简单,但是值得学习的地方还是挺多的。当然也不得不吐槽很多用法都有点过时,尤其是关于webpack的配置。不过现在的前端发展很快,各种工具版本不断更新,代码过时也是能够理解的。对于学习者来说当然得学习最新的东西。
这篇文章主要是用来记录在自己实现一个【知乎日报移动版】的过程中遇到的一个坑,说坑也算不上,只能叫做坎坷之路吧。因为书中给的实战demo是一个pc版的,自己跟着写了一遍,觉得没有什么难度。然后在github上找了一下,发现用知乎日报来练手的项目很多,原因也很简单--api是现成的。于是我也蠢蠢欲动,决定使用vue亲自撸一个【知乎daily】。
虽然gayhub很多现成的项目,但是我并不打算去抄一遍。我在手机上下载了一个知乎日报的APP,按照不同功能截屏来一一实现。没办法,这就是工(zhuang)匠(bi)情(xin)怀(tai).
开发过程中,整体的框架功能上基本没什么问题,该自己写组件就自己写,绝没有偷懒。也遇到很多问题,比如开发环境下的接口代理、知乎图片的同源策略等。这些问题肯定难不倒我,都不值一提。让我觉得难受的还是首页的轮播图实现。这个问题困扰了我2天(准确时间是一天半,还有半天在工作:逃)。
遇到这个功能的时候第一反应是使用一个开源库,分分钟就搞定了。但是经过10s的思想斗争,工匠情怀终于战胜了理智,下定决心打算自己手动实现。然而,带来的结果就是卡顿了半天,没任何进展。网上也有很多教程,也是很简单的,可以说是基础操作了。可惜我被一篇文章带偏了,走了一点弯路。
这篇文章的思路很常规,很有道理。首先将一个框框用来装你要显示的图片,仅仅只能显示这个框框,而图片呢就放后面排排坐,通过定时器去移动图片,就像一格格的胶卷一样,轮到谁谁就被看到了。
我一开始就是按照这种思路去整,结果怎么整都实现不了(在移动端)。在一筹莫展之际,发现了一个更加屌的思路。这个思路和之前的不一样,区别在于后面的图片不是排排坐好,而是叠加到一块去。下面通过代码去一探究竟。
首先得整一个窗口,简单来说就是用来显示一张图片的容器,它的宽度对移动端而言就是屏幕宽度。他得有一个非常重要的属性overflow:hidden
,当子元素尺寸超过其父亲的大小多余内容就会被隐藏。这里用ul
标签来装图片,其实使用div
也是没问题的。元素li
的position
属性得设置为absolute
.因为使用这个属性就能脱离文档流,也就不会“排排坐”了,而是叠加到一起了,当然,最后的肯定叠在最上面,前提是没有显示的设置z-index
属性。还有很重要的一点,子元素定位设置为absolute
父元素记得也设置一下定位属性,因为子元素的相对位置是按照第一个祖先元素不为static
的元素来的,不然会粗大事。 html代码结构如下:
<div class="window">
<ul class="container" ref="imagesWrapper">
<li v-for="(e,i) in imgs" :key="i">
<img :src="e.image" :alt="e.title">
<div class="desc">{{e.title}}</div>
</li>
</ul>
<ol class="point-wrap">
<li :class="{active:i==currentIndex}" v-for="(e,i) in imgs" :key="i"></li>
</ol>
</div>
</div>
* {
padding: 0;
margin: 0;
list-style-type: none;
}
img {
width: 100%;
}
.container {
position: relative;
min-height: 100%;
}
.container li {
width: 100%;
height: 100%;
position: absolute;
top: 0;
left: 0;
transform: translateX(100%);
}
.window {
position: relative;
height: 100%;
margin: 0 auto;
overflow: hidden;
}
这里有一个小技巧,在静态样式中并没有将图片直接展示出来,而是将所有图片向右偏移一个窗口宽度。这是有玄机的。
说了这么多,还没谈到vue的部分。在container
容器中有一个ref
标签。这是在vue中用来获取dom元素的。也许在vue中操作dom是不被推荐的,但是对于这种动态生成的li
元素我找不到怎么去动态绑定其样式。因此采用了这种比较low的办法。
init() {
let wrappers = this.$refs.imagesWrapper;
let children = wrappers.children;
let total = this.imgs.length;
// 纯js操作 只需要先将三张图片位置确定好
// 最左边按道理说是没有图片的 但是为了无限滚动效果 这里将其置为最后一张
let left = total - 1;
let center = 0;
let right = 1;
// 初始化的时候将li左移动了一个屏幕宽度,就是为了防止叠加的元素挡住要显示的图
// 现在第一张显示的图片实际是最后一张 3张轮播起来就行了 没必要对每个图片进行位置计算
// left处于最左边的位置 不显示
children[left].style.transform = "translateX(" + -this.distance + "px)";
// center处于中间位置 显示
children[center].style.transform = "translateX(" + 0 + "px)";
// right处于右边 不显示
children[right].style.transform = "translateX(" + this.distance + "px)";
this.sliderItem = children;
// this.play();
}
首先就是初始化显示的内容,之前在静态css样式中将图片移动到右边看不到的地方去了,现在就得手动操作让其可见。刚开始我以为直接将这些图片全部“铺开”,然后滚动,后来发现这么做很笨。直接操作三张就行了!为了实现无限滚动的效果,第一张的逻辑上的前一张是最后一张,因此将第一张的“上一张”给放到屏幕左边,下一张放到屏幕右边,要显示的也就是第一张(下标为0)归位到窗口,这和之前设置的全局的样式transform: translateX(100%)
对应起来了。
如此以来,初始化的三张图片就定位好了,逻辑上也是没什么问题的。其余的改不显示还是显示不了,也不会参与移动。下面看看怎么“滚”。
next() {
this.currentIndex++;
// 边界判断
if (this.currentIndex > this.imgs.length - 1) {
this.currentIndex = 0;
}
// center 为显示的图片
let center = this.currentIndex;
// 左边的 如果为负数 就取最后一张图片下标
let left = center - 1 < 0 ? this.imgs.length - 1 : center - 1;
// 右边的 如果超过了最大图片数量 取第一张图片下标
let right = center + 1 == this.imgs.length ? 0 : center + 1;
let children = this.sliderItem;
// 给元素添加过渡
children[center].style.transition = "transform .5s";
children[left].style.transition = "transform .5s";
// 右边的图片是替补图片,不需要走过渡
children[right].style.transition = "none";
// 3张图片同时移动
children[left].style.transform = "translateX(" + -this.distance + "px)";
children[center].style.transform = "translateX(0px)";
children[right].style.transform = "translateX(" + this.distance + "px)";
},
currentIndex
为全局变量,指当前显示的图片下标,每次调用next
会子增,到上限后会回归到0,这些都是很常规的操作。接下来就是计算上一张,下一张图片的下标,也是很容易理解,无非多了一点判断,在最后一张显示的时候下一张的下标得置为0,上一张也是同理,不然就回"空指针"了。接下来就是针对这三张图片改变样式,原则就是移动到哪个下标就显示哪个图片,上一张就移到左边,下一站移动到右边,顺便给加个动画效果。如此而已!
最后就是自动播放的逻辑,也是非常简单,一个定时器就搞定:
play() {
if (this.timer) {
window.clearInterval(this.timer);
this.timer = null;
}
this.timer = window.setInterval(() => {
this.next();
}, this.interval);
}
然后在钩子函数中将这方法加上去就完事了。一个自制的轮播组件就写完了。简陋但是简单。这里有一个不太重要的细节,针对窗口变化的时候得动态改变偏移量。
// 窗口变化 重新初始化
windowChange() {
const that = this;
window.onresize = () => {
return (() => {
window.screenWidth = document.body.clientWidth;
that.distance = window.screenWidth;
this.init();
})();
};
},
这样在pc端下也能正常“滚”动了。
最后,做一下小小的总结。这个组件虽然简单,但是也花了一定时间,毕竟踩坑的路是不能跳过的。其中花了很多时间纠结布局和样式,很是难受,都怪我没有把《CSS世界》看完。虽然简单,但是功能也很局限,比如没有实现手动去滑动。APP上是有这个功能的,那是因为我还没学会怎么在vue下使用touch事件(实际上是懒)。比如没有代码优化等等,总不能要求一个新手来造一个完美的轮子吧(给自己一点上升的空间咯)。造轮子不是目的,理解其中的所以然才是目的,现成的库有很多,完成功能也很容易,但是不能仅仅满足于此,我觉得作为手艺人得有一种格(xi)物(huan)致(zhuang)知(B)的精神。