使用Vue有一段时间了,对于组件的编写不是很熟悉,找一个感兴趣的功能组件,模仿(抄袭)过来作为练习。因为原代码用的是webpack1,所以有一些修改,在还原的过程中,也会按照自己的理解整理思路。
摘要
文件目录结构
. ├── dist ├── src │ ├── components │ │ └── stack.vue │ ├── img │ │ └── //... │ └── main.js │ └── App.vue │ └── exportModule.js ├── webpack.config.js .
原作者并没有使用Vue-cli的webpack配置,所以我也写了一个配置文件,权当复习webpack的配置了吧(雾)
在原配置文件基础上,给webpack添加了这些功能:
- 使用purify-css清除未使用的CSS
- html文件发布
- postcss自动处理CSS3属性前缀
具体的配置在文件中。
在打包的过程中还发现一个问题:
运行时构建不包含模块编译器,所以不支持template选项,只能使用render选项。单文件组件中所写的模板只能在vue模块下的vue.js才可以构建。
注意这一行:1
2
3
4
5
6
7
8
// webpack.config.js
resolve: {
//...
alias: {
'vue': 'vue/dist/vue.js'
}
}
开始
该组件的功能:
- 堆叠图片结构
- 首页图的滑动
- 条件判断成功后滑出,失败回弹
- 下一张图片叠加到顶部
功能实现
1. 叠加效果
①. 在父级层设定prespective
及prespective-orgin
,实现子层的透视,子层各元素设定translate3D
值形成效果。
②. 在子组件上遍历从父组件中传来的图片props属性,得到图片数据,使用:style绑定样式
首先,单文件组件内的参数决定了在可视范围内,能够看见几张图。剩下的图片使用zIndex暂时隐藏,可见的这几张图片样式与参数的关系是判断的先决条件。
在子组件中使用:
现在大概是这个样子:
2. 滑动效果
让首页图片能够有滑动的效果,在图片上添加一些事件,使用translate
将其移动。图片滑出原序列中。步骤如下:
- touchs事件的绑定
- 监听并储存手势位置变化的数值
- 改变首图的translate值,形成滑动的效果
在滑动的过程中,为了避免出现不同图片同时滑动的混乱,在temporaryData
中加入了tracking
变量作为判断。同样,在图片的translate
过程中也要加入这样的判断,变量为animation
。默认值均为false
仍然使用:style绑定样式,完成图片的移动行为
现在效果是这样的:
3. 判断成功则滑出,否则回弹
在以上代码的基础上,多加一个判断,让判断成功的滑出原序列,失败的回弹到序列中。判断的依据比较简单:一段滑动过程结束时,x轴偏移量大于200px,则图片滑出。
效果是这样的:
4.划出后下一张图片浮出,堆叠到顶部
经过以上一步之后,DOM会重新更新,按照之前的做法,只需要把绑定样式的函数中,即transform
和transformIndex
中的currentPage
加1即可,但是这么做会再次触发DOM更新,打断动画的执行。所以仅仅是修改currentPage
是不够的。
原有的第一张图片移出原序列,完成更新DOM(touchend事件中currentPage+1)后到下一张图片浮出,堆叠至顶部动作(tranform函数功能)中间,增加对于函数排序的判断,使移出的照片能顺利执行动画。而新的首图也要形成动画效果,顺利过渡为首图。总结起来就是:
- currentPage + 1
- 增加transform函数排序条件
- 将新的首图顺利过渡至顶部
Vue的响应式原理中,当事件循环结束时,nextTick会在异步队列中排在首位。所以,在这里可以写入新首图的视觉效果初始化。
写到这里发现一个问题:
当点击移动图片时,如果点击的位置非常靠近图片的边缘,移动时会形成图片拖动的效果。
就像这样:
最初我才想是因为边界条件的判断导致了这个问题,最后发现其实很简单,这样的问题只会在鼠标或手势动作脱离图片时出现,所以只需要添加相应事件即可。
issue:
- 只使用x轴偏移量作为判断,图片在x、y轴上进行线性移动,动画效果非常生硬,探探上作出了添加角度偏移的效果优化。
优化提升
为了方便阅读,以下部分代码直出,不使用图片形式展示。
[issue1]:在原版本中,图片是否脱离原序列的判断为:滑动结束时,滑动的图片位移大于200px
1 | if(Math.abs(this.temporaryData.posWidth) >= 200) { |
修改为:图片滑出的面积占图片的比例是否大于0.4
1 |
|
判断的依据绑定在计算属性中:1
2
3
4
5
6
7
8offsetRatio() {
let width = this.$el.offsetWidth
let height = this.$el.offsetHeight
let offsetWidth = width - Math.abs(this.temporaryData.posWidth)
let offsetHeight = height - Math.abs(this.temporaryData.posHeight)
let ratio = 1 - (offsetWidth * offsetHeight) / (width * height) || 0
return ratio > 1 ? 1 : ratio
}
同时,在滑动过程中增加偏移角度的计算。偏移角度 = 偏移方向正负值 划出宽度比例 15 * 划出高度比例:
1 | touchmove(e) { |
所以在计算属性中有这些:
1 | computed: { |
添加了偏移角度的计算,所以在绑定样式的位移函数中也会做出相应的修改,这部分会在代码中注释说明。为了功能更完整,会在图片下添加按钮,达到相应的滑动功能。
1 |
|
主文件中执行事件,在单文件组件中绑定:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// stack.vue
// ...
mounted () {
// 绑定事件
this.$on('next', () => {
this.next()
})
this.$on('prev', () => {
this.prev()
})
},
methods: {
prev () {
this.temporaryData.tracking = false
this.temporaryData.animation = true
let width = this.$el.offsetWidth
this.temporaryData.posWidth = -width
this.temporaryData.posHeight = 0
this.temporaryData.opacity = 0
this.temporaryData.rotate = '-3'
this.temporaryData.swipe = true
this.nextTick()
},
next () {
this.temporaryData.tracking = false
this.temporaryData.animation = true
let width = this.$el.offsetWidth
this.temporaryData.posWidth = width
this.temporaryData.posHeight = 0
this.temporaryData.opacity = 0
this.temporaryData.rotate = '3'
this.temporaryData.swipe = true
this.nextTick()
}
}
总结
组件写到这里就结束了。演示在这里:
其实里面的方法并不复杂,关键点之一在于理清楚DOM更新后的事件执行顺序,在这点上最好要深入到Event Loop