记一次分别使用三种事件编写首页一键定制模块的经历

写在前面

  这段时间忙着做公司的新项目(业务端 + 后台 + 工厂),在处理业务端首页一键定制模块的过程中,遇到了一些问题。特此记录,方便日后查阅。

需求分析 & 思路重点

1.需求分析

  先上效果图:
image.png
  需求分析:

  • 在首页一键定制模块,网页会默认展示第一张定制图片和白板图片的组合。随着滑块的移动或者拖拽,显示不同的定制效果。
  • 切换左侧的定制图片,会在右侧的图片区域同步显示,定制图片和白板图片各占据一半江山,滑块默认处于居中位置。拖动或者拖拽滑块,同样会显示不同的定制效果。

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事件的影响:
image.png

  缺陷2: 当滑块拖拽时,无法消除mousedown事件的影响:
image.png

error.png

  如果为滑块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.mousemovedocument.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事件得到的效果较好,熟好熟坏就看个人习惯和业务需求了。

© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享