vue简单实现一个类似微信左右滑动更多功能

news2024/11/26 14:32:59

1、需求背景

产品需要在购物车加一个左右滑动更多的功能,由于是PC端,大致扫描了下使用的UI库,貌似没有单独提供此类组件,反正有时间,就自己造一个轮子试试

2、先看效果在这里插入图片描述

大致有一个橡皮筋的效果,可能没那么细致,凑合着用吧

3、思路分析

由于添加功能,所以最好不动以前代码,那么自然就想到单独封一个带插槽的组件,由上图效果我们可以大致得出几点

  1. 可分为三部,left、content、right,自然需要提供三个插槽
  2. 滑动方向,这个就比较简单了,记录一个鼠标按下初始位置,监听移动事件,同时计算移动位置与初始值之差,就可以得出移动方向
  3. 动画效果,我们可以使用css过渡实现,包括回弹效果

4、html结构确定

<template>
  <div class="swiper-cell-box"
    @mousedown="onMouseDown"
    @mousemove="onMouseMove"
    @mouseout="onMouseUp"
    @mouseup="onMouseUp" 
    @touchstart="onMouseDown"
    @touchmove="onMouseMove"
    @touchend="onMouseUp">
    <div class="wrapper" :style="getTransformStyle" >
        <div class="left" ref="left" >
          <slot name="left" ></slot> 
        </div>
        <div class="content">
          <slot name="content" ></slot> 
        </div>
        <div class="right" ref="right" >
          <slot name="right" ></slot> 
        </div>
    </div>
  </div>
</template>

我们第一个div块,用于我们事件监听(可以看到兼容了移动端),内部再用一个div,用于实现左右平移,最后内部就是三个插槽了

5、实现之滑动方向确定

这个,就不在累赘,简单代码如下

onMouseDown(e) {
	// 记录初始位置、及添加一个正在移动的标识
    this.startPageX = e.pageX || e.changedTouches[0].pageX
    this.isDragging = true
  },
 onMouseMove(e) {
   if(!this.isDragging) return 
   const pageX = e.pageX || e.changedTouches[0].pageX
   let offset = pageX - this.startPageX 
   // 确定方向
   this.direction = offset < 0 ? 'left' : 'right'
 },
 onMouseUp() {
   if(!this.isDragging) return 
   // 有开,就有关
   this.isDragging = false
   this.startPageX = 0
 },

6、实现之跟手移动

简单说就是,我边移动,它跟着走,我移动10px,它也得乖乖移动10px,那么问题自然变成了我计算移动的距离,再动态修改css位移量
这一块主要是 移动时的事情

onMouseMove(e) {
	if(!this.isDragging) return 
	const pageX = e.pageX || e.changedTouches[0].pageX
	let offset = pageX - this.startPageX 
	this.direction = offset < 0 ? 'left' : 'right'
	// 要动态赋值
	this.translateX = offset 
},

上述代码,当我们往左滑时,offset 值为负,div就会往左滑动,基本能实现跟手滑,不过一般这种滑动都会有一个最大响应距离,人话就是,不可能dom一直跟随你滑动吧!

所以,左右滑动的最大距离就是左右隐藏两块dom的宽度,这个好解决

data() {
    return {
      sidesWidth: {
        left: 0,
        right: 0
      }
    }
}
mounted() {
	// 获取一下,方便做边界值对比
	this.$nextTick(() => {
	  Object.keys(this.sidesWidth).forEach(key => {
	    this.sidesWidth[key] = this.getWidth(this.$refs[key])
	  })
	})
},

上面代码,我们能实现基本跟手动,同时也引入了边界的限定,但是还有几个小细节,比如:应该是轻轻滑动一下(或者超过明确的响应距离),就应该处于展开或者收起状态,这个就需要在滑动结束时,判断当前滑动的距离,以确定最终的状态

7、实现之滑动最终优化

问题一:最终状态的确定

我们可以通过滑动距离以及当前的状态(展开、收起),来确定最终是展开还是收起

问题二:滑动最大距离的限定

可以在移动时,拿滑动距离与边界值比较,不管是向左还是向右,均不能超过边界值,可以想象成坐标轴(-10,10),最大不能超过10,最小能小于-10

这个逻辑可以用这个公式得出translateX = Math.max(-10, Math.min(10, 当前计算出要位移的距离)),慢慢体会

onMouseMove(e) {
    if(!this.isDragging) return 
    const pageX = e.pageX || e.changedTouches[0].pageX
    let offset = pageX - this.startPageX 
    this.direction = offset < 0 ? 'left' : 'right'
    // 将要位移的距离 这里直接加,就不用考虑正负问题
    // 假设右边处于收起状态 this.currentX = 50 + 0
    // 假设右边处于展开状态(假设滑动方向往右,则offset 为正) this.currentX = 50 + -80 = -30
    this.currentX = offset + this.startTranslateX
    // 边界限定
    const min = Math.min(0, -this.sidesWidth.right)
    const max = Math.max(0, this.sidesWidth.left)
    this.currentX = Math.max(min, Math.min(max, this.currentX))
    this.translateX = this.currentX
  },
  onMouseUp() {
    if(!this.isDragging) return 
    const offset = Math.abs(Math.abs(this.translateX) - Math.abs(this.startTranslateX))
    // 设置展开、收起状态
    // 当为收起状态,偏移量大于30,则为展开状态
    // 当已经为展开状态时 偏移量小于30,应仍为展开状态
    let isExpanded = (!this.startTranslateX && offset > this.offsetValue) || (this.startTranslateX && offset < this.offsetValue) 
      ? true
      : false
    this.translateX = isExpanded
      ? Math.sign(this.currentX) * this.sidesWidth[this.direction === 'right' ? 'left' : 'right']
      : 0
    this.isDragging = false
    this.startPageX = 0
  },

8、技术总结

左右跟手滑动核心思路是通过计算滑动的距离,动态设置css位移量,这个过程看似简单,但也有几个小细节,比如边界值的限定弹性效果其实是设置的响应距离、元素移动的距离不是一味的使用偏移量(当处于收起状态时,移动距离 = 偏移量,当处于某一侧展开时,移动距离 = 初始位移距离 + 偏移量)

最后也可以再扩展一些api,比如:手动打开、关闭、以及结束后的回调

完整代码如下

<template>
  <div class="swiper-cell-box"
    @mousedown="onMouseDown"
    @mousemove="onMouseMove"
    @mouseout="onMouseUp"
    @mouseup="onMouseUp" 
    @touchstart="onMouseDown"
    @touchmove="onMouseMove"
    @touchend="onMouseUp">
    <div class="wrapper" :style="getTransformStyle" >
        <div class="left" ref="left" >
          <slot name="left" ></slot> 
        </div>
        <div class="content">
          <slot name="content" ></slot> 
        </div>
        <div class="right" ref="right" >
          <slot name="right" ></slot> 
        </div>
    </div>
  </div>
</template>

<script>
  export default {
    name: 'RetailSiwperCell',
    props: {
      /** 偏移量 **/ 
      offsetValue: {
        type: [Number,String],
        default: 30
      }
    },
    data() {
      return {
        isDragging: false,
        startPageX: 0,
        translateX: 0,
        sidesWidth: {
          left: 0,
          right: 0
        },
        direction: '',
        startTranslateX: 0,
        currentX: 0
      }
    },
    mounted() {
      this.$nextTick(() => {
        Object.keys(this.sidesWidth).forEach(key => {
          this.sidesWidth[key] = this.getWidth(this.$refs[key])
        })
      })
    },
    computed: {
      getTransformStyle() {
        return {
          transform: `translate3d(${this.translateX}px, 0px, 0px)`,
          'transition-duration':  `${this.isDragging ? '0s' : '0.6s'}`
        }
      }
    },
    methods: {
      onMouseDown(e) {
        this.startPageX = e.pageX || e.changedTouches[0].pageX
        this.isDragging = true
        this.startTranslateX = this.translateX
      },
      onMouseMove(e) {
        if(!this.isDragging) return 
        const pageX = e.pageX || e.changedTouches[0].pageX
        let offset = pageX - this.startPageX 
        this.direction = offset < 0 ? 'left' : 'right'
        // 将要位移的距离
        this.currentX = offset + this.startTranslateX
        // 边界限定
        const min = Math.min(0, -this.sidesWidth.right)
        const max = Math.max(0, this.sidesWidth.left)
        this.currentX = Math.max(min, Math.min(max, this.currentX))
        this.translateX = this.currentX
      },
      onMouseUp() {
        if(!this.isDragging) return 
        const offset = Math.abs(Math.abs(this.translateX) - Math.abs(this.startTranslateX))
        // 设置展开、收起状态
        // 当为收起状态,偏移量大于30,则为展开状态
        // 当已经为展开状态时 偏移量小于30,应仍为展开状态
        let isExpanded = (!this.startTranslateX && offset > this.offsetValue) || (this.startTranslateX && offset < this.offsetValue) 
          ? true
          : false
        this.translateX = isExpanded
          ? Math.sign(this.currentX) * this.sidesWidth[this.direction === 'right' ? 'left' : 'right']
          : 0
        this.isDragging = false
        this.startPageX = 0
      },
      getWidth(el) {
        return el.getBoundingClientRect().width || 0
      },
      /**
       * @description: 关闭
       * @return {*}
       */      
      close() {
        this.translateX = 0
      },
      /**
       * @description: 打开
       * @param {*} position left | right
       * @return {*}
       */      
      open(position = '') {
        if(!position) return
        const width = this.sidesWidth[position]
        this.translateX = position === 'right' ? -width : width
      }
    }
  }
</script>

<style>
  .swiper-cell-box{
    overflow: hidden;
    border: 1px solid #eee;
  }
  .wrapper{
    position: relative;
    user-select: none;
  }
  .left{
    position: absolute;
    left: 0;
    top: 0;
    height: 100%;
    transform: translateX(-100%);
  }
  .right{
    position: absolute;
    right: 0;
    top: 0;
    height: 100%;
    transform: translateX(100%);
  }
</style>

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/608316.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

分布式锁方案学习

很久没有写文章了&#xff0c;前些天的面试被问到了分布式锁的解决方案&#xff0c;回答的比较简单&#xff0c;只知道Redis&#xff0c;Mysql&#xff0c;Zookeeper能够作为分布式锁应用&#xff0c;今天就来详细的学习一下这三种分布式锁的设计思想及原理。 能够来看这篇文章…

05WEB系统的通信原理图

WEB系统的通信原理 名称作用URL统一资源定位符, 例如:http://www.baidu.com域名在https://www.baidu.com/这个网址中www.baidu.com 是一个域名IP地址计算机在网络当中的一个身份证号, 在同一个网络当中IP地址是唯一的, 有了IP地址两台计算机直接才能建立连接通信端口号一个计算…

如何让你的汇报更有说服力?数据监控是关键!

第5讲中玩过一个扫雷游戏&#xff0c;目标是排除计划中的“延期地雷”&#xff0c;但是&#xff0c;总有些“雷”防不胜防。我们在做计划的时候&#xff0c;明明已经想得非常周全了&#xff0c;可是&#xff0c;真正开工几天之后才发现&#xff0c;很多事情并没有那么简单。 1…

4-1 活动安排问题

1.什么是贪心算法 我的理解&#xff1a; 贪心算法是一种常用的问题求解方法&#xff0c;它在每个步骤上都选择当前看起来最优的解&#xff0c;而不考虑整体的最优解。简单来说&#xff0c;贪心算法采取局部最优的决策&#xff0c;希望通过每个局部最优解的选择&#xff0c;最终…

网络安全面试题大全(整理版)500+面试题附答案详解,最全面详细,看完稳了

前言 随着国家政策的扶持&#xff0c;网络安全行业也越来越为大众所熟知&#xff0c;想要进入到网络安全行业的人也越来越多。 为了拿到心仪的Offer之外&#xff0c;除了学好网络安全知识以外&#xff0c;还要应对好企业的面试。 作为一个安全老鸟&#xff0c;工作这么多年&…

全网最全的网络安全技术栈内容梳理(持续更新中)

前言 本文篇幅比较长~~耐心看完哦~ 网络安全真的那么好吗 据我了解现在我国网络安全人才缺口相当大&#xff0c;预计在2023年这方面人才缺口达到327万&#xff0c;我每年这方面的大学生才2W多。现在各政企都在发展数字化变革&#xff0c;对网络安全方面人才也是垂涎若渴&…

【31】核心易中期刊推荐——电子信息技术计算机技术

🚀🚀🚀NEW!!!核心易中期刊推荐栏目来啦 ~ 📚🍀 核心期刊在国内的应用范围非常广,核心期刊发表论文是国内很多作者晋升的硬性要求,并且在国内属于顶尖论文发表,具有很高的学术价值。在中文核心目录体系中,权威代表有CSSCI、CSCD和北大核心。其中,中文期刊的数…

06SpringCloud rabbitmq安装

rabbitmq安装 说明&#xff1a;请使用资料里提供的CentOS-7-x86_64-DVD-1810.iso 安装虚拟机. 1. 安装依赖环境 在线安装依赖环境&#xff1a; yum install build-essential openssl openssl-devel unixODBC unixODBC-devel make gcc gcc-c kernel-devel m4 ncurses-devel …

动态规划-概率DP

Bag of mice 题面翻译 https://www.luogu.com.cn/problem/CF148D 袋子里有 w w w 只白鼠和 b b b 只黑鼠 &#xff0c;A和B轮流从袋子里抓&#xff0c;谁先抓到白色谁就赢。A每次随机抓一只&#xff0c;B每次随机抓完一只之后会有另一只随机老鼠跑出来。如果两个人都没有抓到…

【小沐学GIS】基于Cesium实现三维数字地球Earth(CesiumJS入门安装)

文章目录 1、简介1.1 平台1.1.1 Cesium ion1.1.2 CesiumJS1.1.3 Cesium for Unity1.1.4 Cesium for Unreal1.1.4 Cesium for Omniverse1.1.5 Cesium for O3DE 1.2 支持的数据格式 2、CesiumJS安装3、代码测试3.1 安装node3.2 安装依赖项3.3 运行测试示例3.4 注册获取token 4、扩…

常见的前端框架

随着前端行业的发展&#xff0c;前端框架越来越多出现&#xff0c;为我们的项目开发工作带来了极大的便利&#xff0c;那目前主流的前端框架有哪些呢&#xff1f; 工作中我们常用的前端框架有vue框架、React框架、Bootstrap框架、Angular框架等&#xff0c;下面给大家简单介绍…

【AI绘图】二、stable diffusion环境准备与安装

前一篇&#xff1a;一、stable diffusion的发展史 放一张SD的效果图 硬件配置要求 Stable Diffusion是使用显卡生成图片&#xff0c;对电脑硬件有一定要求。 电脑配置最核心的关键点&#xff1a;看显卡、看内存、看硬盘、看 CPU。 显卡&#xff1a;N 卡&#xff08;英伟达 N…

基于内点法求解最优潮流研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

Spring源码(二)— AbstractApplicationContext

上一篇文章简单的介绍了一下Spring框架大体的一个执行流程&#xff0c;整个专栏的内容也会根据第一篇序言中的流程图一步一步的向下梳理&#xff0c;并会慢慢补充更多的细节进去。 Test 创建ClassPathXmlApplicationContext来解析xml。 public class Test {public static vo…

剖析 OpenShift 中的 DNS

深入分析 OpenShift 内部 DNS OpenShift 中的DNS 相关组件及其配置1.1 Pod 中的 DNS 配置1.2 Pod 所在宿主机上的 DNS 配置及服务1.2.1 resolv.conf 文件 DNS 配置DNS 查询流程为什么需要内部 DNS&#xff1f; 本文基于 OpenShift 3.11&#xff0c;Kubernetes 1.11 进行测试 O…

2023/6/4周报

目录 摘要 论文阅读 1、标题和现存问题 2、使用GNN进行文本分类 3、INDUCT-GCN 4、实验准备 5、实验结果 深度学习 1、时空图的种类 2、图在环境中的应用 3、STGNN 总结 摘要 本周在论文阅读上&#xff0c;阅读了一篇InducT-GCN:归纳图卷积文本分类网络的论文。基…

python-pandas按各种时间统计和案例

使用到的库 pandas、matplotlib、numpy 使用到的函数 df.resample(“H”).sum() 参数 B business day frequency C custom business day frequency (experimental) D calendar day frequency W weekly frequency M month end frequency BM business month end frequency CBM…

【奶奶看了都会】云服务器ChatGLM模型fine-tuning微调,让你拥有自己的知识库

1.背景 大家好啊&#xff0c;上次给大家写了ChatGLM-6B的部署使用教程&#xff0c;【奶奶看了都会】云服务器部署开源ChatGLM-6B&#xff0c;让你拥有自己的ChatGPT 但是因为模型比较小的问题&#xff0c;所以日常工作中可能用不上。而且大家更希望的是模型能训练自己的数据&…

【Python Bokeh】零基础也能轻松掌握的学习路线与参考资料

Python Bokeh是一款为开发者提供数据可视化的Python库。它可以帮助开发者轻松地创建交互式网页应用程序&#xff0c;而无需编写大量的JavaScript代码。Bokeh支持各种绘图类型和工具&#xff0c;包括线图、散点图、条形图等。Python Bokeh非常适合在大数据分析、商业智能和数据科…

chatgpt赋能python:Python去除重复元素的几种方法

Python去除重复元素的几种方法 在Python编程中&#xff0c;去除列表、集合、字典等数据结构中的重复元素是一个常见的操作。本文将介绍Python中去除重复元素的几种方法&#xff0c;并分析它们的优缺点。 方法一&#xff1a;使用set去重 Set是Python中的一种集合类数据结构&a…