写在前面
这段时间忙着做公司的新项目(业务端 + 后台 + 工厂),在处理业务端首页一键定制模块的过程中,遇到了一些问题。特此记录,方便日后查阅。
需求分析 & 思路重点
1.需求分析
先上效果图:
需求分析:
- 在首页一键定制模块,网页会默认展示第一张定制图片和白板图片的组合。随着滑块的移动或者拖拽,显示不同的定制效果。
- 切换左侧的定制图片,会在右侧的图片区域同步显示,定制图片和白板图片各占据一半江山,滑块默认处于居中位置。拖动或者拖拽滑块,同样会显示不同的定制效果。
2.思路重点
- 布局方面: 布局重心主要落在右侧的定制区域,由一个采取相对定位的大盒子组成。
(1)滑块应处于最上方,定制图片层级居中,白板图片的层级最低,这三张图片都由一个小盒子包裹,并采取绝对定位的方式。html
中先写的元素会位于上方,也会先和大盒子绝对定位,先写的就位于盒子下方了,这也就意味着我们在html
代码中要先书写白板图片,再书写定制图片,最后再写滑块。
(2)定制效果的展示其实是通过控制包裹定制图片的盒子的宽度决定。 白板图片、定制图片以及滑块图片的宽高都是固定的,变化的只是包裹定制图片的盒子的宽度,盒子超出的部分隐藏即可展示定制效果。
- 逻辑方面: 采用
js
计算滑块在大盒子中的位置,在vue
中,最好通过ref
获取dom元素
。要处理好滑块的边界问题, 特别需要阻止事件的默认行为。否则在拖拉滑块的过程中,当滑块处于大图左侧或者右侧边界时,会出现图片对应的缩略图。
第一种方法: 在滑块盒子上添加drag事件
<template>
<div class="designed-warpper">
<div class="middle-warpper">
<h2 class="topic-title">打造简单、适合操作的设计定制器</h2>
<div class="topic-information">
一键加图,同步预览,批量定制产品;海量专业设计图库,设计即创意
</div>
<div class="whole-warpper">
<div class="image-warpper">
<el-image
v-for="(item, index) in imageList"
:key="index"
:src="item.image"
@click="handleClicked(item.clothes)"
></el-image>
</div>
<div
class="big-box"
ref="bigBox"
@drag.prevent="onDrag"
>
<div class="basic-box">
<el-image :src="require('../images/basic.jpg')"></el-image>
</div>
<div class="designed-box" ref="designedBox">
<el-image :src="src"></el-image>
</div>
<div class="slide-box" ref="slideBox">
<el-image :src="require('../images/slide.png')"></el-image>
</div>
</div>
</div>
<el-button type="primary" @click="$router.push('/design/index')"
>立即定制</el-button
>
</div>
</div>
</template>
<script>
export default {
data() {
return {
src: require('../images/1-1.jpg'),
imageList: [
{
image: require('../images/1.png'),
clothes: require('../images/1-1.jpg')
},
{
image: require('../images/2.png'),
clothes: require('../images/1-2.jpg')
},
{
image: require('../images/3.png'),
clothes: require('../images/1-3.jpg')
},
{
image: require('../images/4.png'),
clothes: require('../images/1-4.jpg')
},
{
image: require('../images/5.png'),
clothes: require('../images/1-5.jpg')
},
{
image: require('../images/6.png'),
clothes: require('../images/1-6.jpg')
},
{
image: require('../images/7.png'),
clothes: require('../images/1-7.jpg')
},
{
image: require('../images/8.png'),
clothes: require('../images/1-8.jpg')
},
{
image: require('../images/9.png'),
clothes: require('../images/1-9.jpg')
}
]
}
},
methods: {
onDrag(event) {
//阻止事件的默认行为,其实写不写都一样,因为无法阻止
event.preventDefault()
const designedBox = this.$refs.designedBox
const slideBox = this.$refs.slideBox
const bigBox = this.$refs.bigBox
let left = event.clientX - bigBox.offsetLeft
//拖拽的过程中,event.clientX的值会随着拖拽位置的变化而变化
//拖拽后松开鼠标,event.clientX的值为0,left的值为负数
//此处需要做判断处理,否则left的值会被松开鼠标得到的值覆盖掉
if (left < 0) {
return
}
//16为滑块尖角的宽度
if (left < 16) {
left = 16
}
//504为盒子宽度和滑块尖角宽度的差值
if (left > 504) {
left = 504
}
//注意需要带单位
designedBox.style.width = left + 'px'
slideBox.style.left = left + 'px'
},
handleClicked(clothes) {
this.src = clothes
this.$refs.designedBox.style.width = '260px'
this.$refs.slideBox.style.left = '50%'
}
}
}
</script>
<style lang="scss" scoped>
.designed-warpper {
height: 1000px;
background: #f5f9fe;
text-align: center;
.middle-warpper {
width: 1200px;
margin: 0 auto;
.topic-title {
padding: 110px 0 40px 0;
}
.topic-information {
margin-bottom: 69px;
}
.whole-warpper {
display: flex;
align-items: center;
margin-bottom: 50px;
.image-warpper {
height: 530px;
width: 530px;
display: flex;
flex-wrap: wrap;
align-content: space-between;
justify-content: space-between;
margin-right: 82px;
}
.big-box {
height: 520px;
width: 520px;
position: relative;
overflow: hidden;
.basic-box,
.designed-box {
height: 520px;
width: 520px;
position: absolute;
left: 0;
top: 0;
.el-image {
height: 520px;
width: 520px;
}
}
.designed-box {
width: 260px;
overflow: hidden;
}
.slide-box {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
height: 520px;
cursor: pointer;
.el-image {
height: 520px;
width: 35px;
}
}
}
}
}
}
</style>
复制代码
- 总结:
使用drag事件
能够实现基本的业务逻辑,但不能较好地把握细节。
缺陷1: 当滑块拖拽的位置处于盒子外围,无法消除drag事件
的影响:
缺陷2: 当滑块拖拽时,无法消除mousedown事件
的影响:
如果为滑块drag事件
和middle-warpper版心盒子
添加一个@mousedown.prevent
,又无法触发滑块的drag事件
。
第二种方法: 在大盒子上添加mousemove事件
注意: 为什么不直接在滑块盒子而选择在大盒子上添加mousemove事件
呢?这是因为滑块盒子的宽度太小了,当鼠标移动较快而超出滑块盒子的范围后,就不会触发滑块盒子的mousemove事件
。因此,选择在大盒子上添加mousemove事件
。
<template>
<div class="designed-warpper" @mousedown.prevent>
<div class="middle-warpper">
<h2 class="topic-title">打造简单、适合操作的设计定制器</h2>
<div class="topic-information">
一键加图,同步预览,批量定制产品;海量专业设计图库,设计即创意
</div>
<div class="whole-warpper">
<div class="image-warpper">
<el-image
v-for="(item, index) in imageList"
:key="index"
:src="item.image"
@click="handleClicked(item.clothes)"
></el-image>
</div>
<div
class="big-box"
ref="bigBox"
@mousemove.prevent="onMouseMove"
@mousedown.prevent
>
<div class="basic-box">
<el-image :src="require('../images/basic.jpg')"></el-image>
</div>
<div class="designed-box" ref="designedBox">
<el-image :src="src"></el-image>
</div>
<div class="slide-box" ref="slideBox">
<el-image :src="require('../images/slide.png')"></el-image>
</div>
</div>
</div>
<el-button type="primary" @click="$router.push('/design/index')"
>立即定制</el-button
>
</div>
</div>
</template>
<script>
export default {
data() {
return {
src: require('../images/1-1.jpg'),
imageList: [
{
image: require('../images/1.png'),
clothes: require('../images/1-1.jpg')
},
{
image: require('../images/2.png'),
clothes: require('../images/1-2.jpg')
},
{
image: require('../images/3.png'),
clothes: require('../images/1-3.jpg')
},
{
image: require('../images/4.png'),
clothes: require('../images/1-4.jpg')
},
{
image: require('../images/5.png'),
clothes: require('../images/1-5.jpg')
},
{
image: require('../images/6.png'),
clothes: require('../images/1-6.jpg')
},
{
image: require('../images/7.png'),
clothes: require('../images/1-7.jpg')
},
{
image: require('../images/8.png'),
clothes: require('../images/1-8.jpg')
},
{
image: require('../images/9.png'),
clothes: require('../images/1-9.jpg')
}
]
}
},
methods: {
onMouseMove(event) {
const designedBox = this.$refs.designedBox
const slideBox = this.$refs.slideBox
const bigBox = this.$refs.bigBox
let left = event.clientX - bigBox.offsetLeft
//此处不需要判断left的值是否小于0,因为鼠标移动事件的event.clientX是正确的
if (left < 16) {
left = 16
}
if (left > 504) {
left = 504
}
designedBox.style.width = left + 'px'
slideBox.style.left = left + 'px'
},
handleClicked(clothes) {
this.src = clothes
this.$refs.designedBox.style.width = '260px'
this.$refs.slideBox.style.left = '50%'
}
}
}
</script>
<style lang="scss" scoped>
.designed-warpper {
height: 1000px;
background: #f5f9fe;
text-align: center;
.middle-warpper {
width: 1200px;
margin: 0 auto;
.topic-title {
padding: 110px 0 40px 0;
}
.topic-information {
margin-bottom: 69px;
}
.whole-warpper {
display: flex;
align-items: center;
margin-bottom: 50px;
.image-warpper {
height: 530px;
width: 530px;
display: flex;
flex-wrap: wrap;
align-content: space-between;
justify-content: space-between;
margin-right: 82px;
}
.big-box {
height: 520px;
width: 520px;
position: relative;
overflow: hidden;
.basic-box,
.designed-box {
height: 520px;
width: 520px;
position: absolute;
left: 0;
top: 0;
.el-image {
height: 520px;
width: 520px;
}
}
.designed-box {
width: 260px;
overflow: hidden;
}
.slide-box {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
height: 520px;
cursor: pointer;
.el-image {
height: 520px;
width: 35px;
}
}
}
}
}
}
</style>
复制代码
- 总结:
使用mousemove事件
能够较好地完成业务逻辑,且不会受到mousedown事件
的影响。在用户体验上,鼠标滑动时就能体味到定制效果。随着鼠标移动,定制结果就像一幅画一样徐徐展开。只是停止滑动的方式有点反人类设计,需要保持鼠标的静止状态或者直接将鼠标移出大盒子。
第三种方法: 在滑块盒子上添加mousedown事件
注意:
- 第一种方法和第三种方法都是只要鼠标拖拽了(拖拽中也能触发)或者点下去了(事件嵌套事件)就能分别触发滑块盒子的
drag事件
和mousedown事件
,所以直接给滑块盒子添加事件即可。 click事件
其实是由mousedown事件
和mouseup事件
组成。- 这种方法其实是由三个事件组成,点击鼠标触发滑块盒子的
mousedown事件
,移动滑块触发mousedown事件
,计算滑块盒子当前距离大盒子左侧的距离,松开鼠标触发mouseup事件
,禁用滑块的mousedown事件
。 - 在
mousedown事件
中,需要使用document.mousemove
和document.mouseup
,一方面由于滑块盒子是在整个网页中移动,另一方面的原因已经在第二种方法里提及。
<template>
<div class="designed-warpper" @mousedown.prevent>
<div class="middle-warpper">
<h2 class="topic-title">打造简单、适合操作的设计定制器</h2>
<div class="topic-information">
一键加图,同步预览,批量定制产品;海量专业设计图库,设计即创意
</div>
<div class="whole-warpper">
<div class="image-warpper">
<el-image
v-for="(item, index) in imageList"
:key="index"
:src="item.image"
@click="handleClicked(item.clothes)"
></el-image>
</div>
<div class="big-box" ref="bigBox">
<div class="basic-box">
<el-image :src="require('../images/basic.jpg')"></el-image>
</div>
<div class="designed-box" ref="designedBox">
<el-image :src="src"></el-image>
</div>
<div
class="slide-box"
ref="slideBox"
@mousedown.prevent="onMouseDown"
>
<el-image :src="require('../images/slide.png')"></el-image>
</div>
</div>
</div>
<el-button type="primary" @click="$router.push('/design/index')"
>立即定制</el-button
>
</div>
</div>
</template>
<script>
export default {
data() {
return {
src: require('../images/1-1.jpg'),
imageList: [
{
image: require('../images/1.png'),
clothes: require('../images/1-1.jpg')
},
{
image: require('../images/2.png'),
clothes: require('../images/1-2.jpg')
},
{
image: require('../images/3.png'),
clothes: require('../images/1-3.jpg')
},
{
image: require('../images/4.png'),
clothes: require('../images/1-4.jpg')
},
{
image: require('../images/5.png'),
clothes: require('../images/1-5.jpg')
},
{
image: require('../images/6.png'),
clothes: require('../images/1-6.jpg')
},
{
image: require('../images/7.png'),
clothes: require('../images/1-7.jpg')
},
{
image: require('../images/8.png'),
clothes: require('../images/1-8.jpg')
},
{
image: require('../images/9.png'),
clothes: require('../images/1-9.jpg')
}
]
}
},
methods: {
onMouseDown() {
const designedBox = this.$refs.designedBox
const slideBox = this.$refs.slideBox
const bigBox = this.$refs.bigBox
document.onmousemove = function (event) {
event.preventDefault()
let left = event.clientX - bigBox.offsetLeft
if (left < 16) {
left = 16
}
if (left > 504) {
left = 504
}
designedBox.style.width = left + 'px'
slideBox.style.left = left + 'px'
}
document.onmouseup = function () {
document.onmousemove = null
}
},
handleClicked(clothes) {
this.src = clothes
this.$refs.designedBox.style.width = '260px'
this.$refs.slideBox.style.left = '50%'
}
}
}
</script>
<style lang="scss" scoped>
.designed-warpper {
height: 1000px;
background: #f5f9fe;
text-align: center;
.middle-warpper {
width: 1200px;
margin: 0 auto;
.topic-title {
padding: 110px 0 40px 0;
}
.topic-information {
margin-bottom: 69px;
}
.whole-warpper {
display: flex;
align-items: center;
margin-bottom: 50px;
.image-warpper {
height: 530px;
width: 530px;
display: flex;
flex-wrap: wrap;
align-content: space-between;
justify-content: space-between;
margin-right: 82px;
}
.big-box {
height: 520px;
width: 520px;
position: relative;
overflow: hidden;
.basic-box,
.designed-box {
height: 520px;
width: 520px;
position: absolute;
left: 0;
top: 0;
.el-image {
height: 520px;
width: 520px;
}
}
.designed-box {
width: 260px;
overflow: hidden;
}
.slide-box {
position: absolute;
left: 50%;
top: 0;
transform: translateX(-50%);
height: 520px;
cursor: pointer;
.el-image {
height: 520px;
width: 35px;
}
}
}
}
}
}
</style>
复制代码
- 总结:
使用mousedown事件
同样能够较好地完成业务逻辑,且不会受到mousedown事件
的影响。用户可以按住滑块,然后拖动滑块展示不同的定制效果,最后在松开鼠标后,禁止滑块的移动。这种方法似乎更加符合用户的操作习惯,但是代码相对使用mousemove事件
而言,会更加复杂一些。
总结
以上就是分别使用三种事件编写首页一键定制模块的方法。相比起来,使用drag事件
得到的定制效果不够丝滑。而使用mousemove事件
和mousedown事件
得到的效果较好,熟好熟坏就看个人习惯和业务需求了。