vue3范围选择组件封装

个人项目地址: SubTopH前端开发个人站

(自己开发的前端功能和UI组件,一些有趣的小功能,感兴趣的伙伴可以访问,欢迎提出更好的想法,私信沟通,网站属于静态页面)

SubTopH前端开发个人站https://subtop.gitee.io/subtoph.github.io/#/home

以上 👆 是个人前端项目,欢迎提出您的建议😊

以下是正文内容...............

实现效果

直接上代码

组件文件

<template>
  <div class="swh-range-page" :id="onlyId" ref="rangeRef">
    <div class="swh-range-selection" :id="onlyId + '_selection'">
      <!-- 滑动槽 -->
      <div class="swh-trough" @click="handleTroughClick"></div>
      <!-- 选中范围高亮条 -->
      <p
        class="swh-drag-trough"
        :id="onlyId + '_drag-trough'"
        @click="handleTroughClick"
      ></p>
      <!-- 拖拽按钮 -->
      <p class="swh-drag-btn" :id="onlyId + '_drag-btn'"></p>
      <p class="drag-value" :id="onlyId + '_drag-value'">{{ rangValue }}</p>
    </div>
  </div>
</template>

<script>
import { reactive, toRefs, onBeforeMount, onMounted, ref, nextTick } from 'vue';
import { findCloseNum, handleStepNumber } from '@/utils/common.js';
export default {
  name: '',
  props: {
    minValue: {
      type: Number,
      default: 0,
      explain: '范围最小值',
      otherVal: '---'
    },
    maxValue: {
      type: Number,
      default: 100,
      explain: '范围最大值',
    },
    // 初始值
    initValue: {
      type: Number,
      default: 0,
      explain: '设置初始值',
    },
    // 是否设置初始值
    setInitValue: {
      type: Boolean,
      default: true,
      explain: '是否使用初始值(true时initValue有效)',
    },
    getRangChange: {
      type: Function,
      explain: '数值发生变化'
    }
  },
  setup(props, ctx) {
    const data = reactive({
      dragEle: null, //推拽槽元素
      rangeEle: null, //推拽按钮元素
      dragTrough: null, //推拽的条元素
      clickPos: 0, //点击移动按钮时鼠标距离父级的位置
      moveLeft: 0, //移动的left
      rangeWidth: 0, //范围盒子宽度
      btnWidth: 0, //拖拽按钮尺寸
      rangValue: 0, //选中值
      optionalRange: 0, //实际范围
      rangArr: [], //范围段数组
      onlyId: ''
    });
    const rangeRef = ref(null); // 获取当前组件中外层元素
    onBeforeMount(() => {});
    onMounted(() => {
      nextTick(() => {
        // 获取组件数量
        const ele = document.getElementsByClassName('swh-range-page');
        if (ele.length) {
          for (let i = 0; i < ele.length; i++) {
            // 组件和当前ref获取的组件本身相等就设置id
            if (rangeRef.value === ele[i]) {
              data.onlyId = `swhRangeRef_${i}`; //设置显示框id
            }
          }
        }
        nextTick(() => {
          init();
        });
      });
    });
    const init = () => {
      const { onlyId } = data;
      data.rangeCom = document.querySelector(`#${onlyId}`);
      data.dragEle = document.querySelector(`#${onlyId}_drag-btn`);
      data.dragValueEle = document.querySelector(`#${onlyId}_drag-value`);
      data.rangeEle = document.querySelector(`#${onlyId}_selection`);
      data.dragTrough = document.querySelector(`#${onlyId}_drag-trough`);
      data.btnWidth = data.dragEle.offsetWidth;
      data.rangeWidth = data.rangeEle.offsetWidth;
      data.dragEle.style.left = -data.btnWidth / 2 + 'px';
      // 设置默认值,没有就是最小值
      const { setInitValue, initValue, minValue, maxValue } = props;
      if (setInitValue) {
        // 设置默认值
        if (initValue >= minValue && initValue <= maxValue) {
          // 初始值在范围内
          data.rangValue = initValue;
        } else {
          console.error('未设置初始值或者初始值超出范围');
          data.rangValue = minValue;
        }
      } else {
        // 未设置默认值,默认最小值
        data.rangValue = props.minValue;
      }
      // 最大值减区最小值是 实际范围
      data.optionalRange = props.maxValue - props.minValue;
      // 获取分割范围数组
      data.rangArr = handleStepNumber(data.rangeWidth, data.optionalRange);
      initMovePosition();
      bindEvent();
    };
    // 初始化值所在的位置
    const initMovePosition = () => {
      const index = rangNumArr().indexOf(data.rangValue);
      if (index !== -1) {
        const proportion = index / data.optionalRange;
        const initLeft = data.rangeWidth * proportion;
        data.moveLeft = initLeft;
        moveLeft();
      }
    };
    // 范围数值数组
    const rangNumArr = () => {
      let rNumArr = [];
      for (let i = 0; i < data.optionalRange + 1; i++) {
        rNumArr.push(props.minValue + i);
      }
      return rNumArr;
    };
    // 事件监听
    const bindEvent = () => {
      data.dragEle.addEventListener('mousedown', handleMouseDown, false);
    };
    // 按下事件
    const handleMouseDown = (e) => {
      // 点击时鼠标距离父级left    减去已经实际移动的距离
      data.clickPos = e.clientX - data.rangeEle.offsetLeft - data.moveLeft;
      document.addEventListener('mousemove', handleMouseMove, false);
      document.addEventListener('mouseup', handleMouseUp, false);
    };
    // 移动处理
    const handleMouseMove = (e) => {
      // 获取实际移动的位置,移动后left减去点击时clickPos(left)是实际移动的left
      const inMoveleft = e.clientX - data.rangeEle.offsetLeft;
      // 移动的距离  -  开始点击的位置 = 实际移动距离
      data.moveLeft = inMoveleft - data.clickPos;
      moveLeft();
    };
    // 直接点击范围条,改变拖拽按钮选中位置
    const handleTroughClick = (e) => {
      // 鼠标点击位置减去元素距离body的left,获取点击在跳上的left距离
      const inMoveleft = e.clientX - data.rangeCom.getBoundingClientRect().left;
      // 距离减去按钮宽度未实际移动left
      data.moveLeft = inMoveleft - data.btnWidth;
      moveLeft();
    };
    // 移动位置
    const moveLeft = () => {
      // 调整实际移动的距离
      if (data.moveLeft > data.rangeWidth) {
        // 最大限制
        data.moveLeft = data.rangeWidth;
      } else if (data.moveLeft < 0) {
        // 最小限制
        data.moveLeft = 0;
      } else {
        // 移动至鼠标最接近的范围点上
        data.moveLeft = findCloseNum(data.rangArr, data.moveLeft);
      }
      //按键 移动的距离减去按键一半宽度
      data.dragEle.style.left = data.moveLeft - data.btnWidth / 2 + 'px';
      //设置选中范围条宽度
      data.dragTrough.style.width = data.moveLeft + 'px';
      // 移动的占比
      const proportion = data.moveLeft / data.rangeWidth;
      // 计算移动的值
      data.rangValue =
        parseInt(data.optionalRange * proportion) + props.minValue;
      // 计算提示数值的偏移位置
      const wc = (data.dragValueEle.offsetWidth - data.btnWidth) / 2;
      // 设置显示范围值的提示位置,设置按钮的位置即可
      data.dragValueEle.style.left =
        data.dragEle.offsetLeft - Math.abs(wc) + 'px';
      ctx.emit('getRangChange', data.rangValue);
    };
    // 移除事件监听
    const handleMouseUp = () => {
      document.removeEventListener('mousemove', handleMouseMove, false);
      document.removeEventListener('mouseup', handleMouseUp, false);
    };
    return {
      rangeRef,
      handleTroughClick,
      ...toRefs(data)
    };
  }
};
</script>
<style scoped lang="less">
.swh-range-page {
  position: relative;
  min-width: 160px;
  width: 100%;
  display: flex;
  padding: 0 20px;
  justify-content: center;
  border-radius: 10px;
  .swh-range-selection {
    position: relative;
    width: 100%;
    height: 30px;
    z-index: 9;
    .swh-trough {
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      width: 100%;
      height: 5px;
      background: rgb(243, 243, 243);
      border-radius: 3px;
      cursor: pointer;
    }
    .swh-drag-btn {
      position: absolute;
      top: 50%;
      left: 0;
      width: 20px;
      height: 20px;
      background: #fff;
      border: 2px solid @TSB;
      transform: translateY(-50%);
      border-radius: 50%;
      z-index: 1;
      cursor: pointer;
      box-sizing: border-box;
      &:hover {
        transform: translateY(-50%) scale(1.1);
        transition: 0.3s;
      }
    }
    .swh-drag-trough {
      cursor: pointer;
      position: absolute;
      top: 50%;
      left: 0;
      transform: translateY(-50%);
      background: @TSB;
      height: 5px;
      border-radius: 3px;
    }
    .drag-value {
      position: absolute;
      top: -20px;
      left: 0px;
      border-radius: 5px;
      width: 30px;
      height: 20px;
      background: rgba(0, 0, 0, 0.8);
      font-size: 12px;
      line-height: 20px;
      color: #fff;
      text-align: center;
    }
  }
}
</style>

 组件使用到的findCloseNum方法

// 判断当前数字  最靠近数组中那个数字
export function findCloseNum(arr, num) {
  var index = 0; // 保存最接近数值在数组中的索引
  var old_value = Number.MAX_VALUE; // 保存差值绝对值,默认为最大数值
  for (var i = 0; i < arr.length; i++) {
    var new_value = Math.abs(arr[i] - num); // 新差值
    if (new_value <= old_value) { // 如果新差值绝对值小于等于旧差值绝对值,保存新差值绝对值和索引
      if (new_value === old_value && arr[i] < arr[index]) { // 如果数组中两个数值跟目标数值差值一样,取大
        continue;
      }
      index = i;
      old_value = new_value;
    }
  }
  return arr[index] // 返回最接近的数值
}

 组件使用到的handleStepNumber方法

export function handleStepNumber(w, r) {
  const itemPx = w / r;
  let rangArr = [];
  for (let i = 0; i < r+1; i++) {
    rangArr.push(Math.ceil(itemPx * i));
  }
  return rangArr;
};

1.组件可以实现最小值和最大值的设置

2.可初始化值

3.组件长度根据父组件自定义

4.滑动和点击会改变范围值

根据自己的需求可进行更多扩展

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

K8S如何部署ZooKeeper以及如何进行ZooKeeper的平滑替换

前言 在之前的章节中&#xff0c;我们已经成功地将Dubbo项目迁移到了云环境。在这个过程中&#xff0c;我们选择了单机ZooKeeper作为注册中心。接下来&#xff0c;我们将探讨如何将单机ZooKeeper部署到云端&#xff0c;以及在上云过程中可能遇到的问题及解决方案。 ZooKeeper…

免费开源使用的几款红黑网络流量工具,自动化的多功能网络侦查工具、超级关键词URL采集工具、Burpsuite被动扫描流量转发插件

免费开源使用的几款红黑网络流量工具&#xff0c;自动化的多功能网络侦查工具、超级关键词URL采集工具、Burpsuite被动扫描流量转发插件。 #################### 免责声明&#xff1a;工具本身并无好坏&#xff0c;希望大家以遵守《网络安全法》相关法律为前提来使用该工具&am…

【C++】初步认识模板

&#x1f3d6;️作者&#xff1a;malloc不出对象 ⛺专栏&#xff1a;C的学习之路 &#x1f466;个人简介&#xff1a;一名双非本科院校大二在读的科班编程菜鸟&#xff0c;努力编程只为赶上各位大佬的步伐&#x1f648;&#x1f648; 目录 前言一、泛型编程二、函数模板2.1 函…

【Java】基础练习(十)

1.判断邮箱 输入一个电子邮箱&#xff0c;判断是否是正确电子邮箱地址。 正确的邮箱地址&#xff1a; 必须包含 字符&#xff0c;不能是开头或结尾必须以 .com结尾和.com之间必须有其他字符 (1) Email类&#xff1a; package swp.kaifamiao.codes.Java.d0823; /** 输入一个…

4-4 Representing text Exercise

本文所用资料下载 一. Representing text Let’s load Jane Austen’s Pride and Prejudice. We first split our text into a list of lines and pick an arbitrary line to focus on: with open(D:jane-austen/1342-0.txt, encodingutf8) as f:text f.read() lines text.…

简单了解文件上传漏洞(md版)

简单了解文件上传漏洞 一、什么是文件上传漏洞二、常见功能点三、成功的前提四、文件上传的校验方式五、Pass-1六、Pass-2七、蚁剑结合msf获取目标权限 一、什么是文件上传漏洞 在文件上传的功能处&#xff0c;如果服务端未对上传的文件进行严格的验证和过滤&#xff0c;导致攻…

登录校验-Filter-详解

目录 执行流程 拦截路径 过滤器链 小结 执行流程 过滤器Filter拦截到请求之后&#xff0c;首先执行方放行之前的逻辑&#xff0c;然后执行放行操作&#xff08;doFilter&#xff09;&#xff0c;然后会访问对应的Web资源&#xff08;对应的Controller类&#xff09;&#…

【C++】C++ 引用详解 ⑤ ( 函数 “ 引用类型返回值 “ 当左值被赋值 )

文章目录 一、函数返回值不能是 " 局部变量 " 的引用或指针1、函数返回值常用用法2、分析函数 " 普通返回值 " 做左值的情况3、分析函数 " 引用返回值 " 做左值的情况 函数返回值 能作为 左值 , 是很重要的概念 , 这是实现 " 链式编程 &quo…

Acrobat Pro DC软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Acrobat Pro DC是Adobe公司开发的一款PDF编辑软件&#xff0c;简称为DC&#xff0c;是Acrobat系列软件中的一款&#xff0c;是行业内的标准工具&#xff0c;被广泛应用于文档处理、电子合同、PDF表单等领域。 Acrobat Pro DC软…

【SpringBoot】第一篇:redis使用

背景&#xff1a; 本文是教初学者如何正确使用和接入redis。 一、引入依赖 <!--redis--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-redis</artifactId></dependency><depen…

适配器模式实现stack和queue

适配器模式实现stack和queue 什么是适配器模式&#xff1f;STL标准库中stack和queue的底层结构stack的模拟实现queue的模拟实现 什么是适配器模式&#xff1f; 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff…

基于广义神经网络的网络入侵检测Matlab代码

1.案例背景 1.1 FCM 聚类算法 聚类方法是数据挖掘中经常使用的方法,它将物理的或抽象的对象分为几个种群,每个种群内部个体间具有较高的相似性,不同群体内部间个体相似性较低。模糊c均值聚类算法(Fuzzy C- Mean, FCM)是用隶属度确定每个元素属于某个类别程度的一种聚类算法&am…

Rspack 创建 vue2/3 项目接入 antdv(rspack.config.js 配置 less 主题)

一、简介 Rspack CLI 官方文档。 rspack.config.js 官方文档。 二、创建 vue 项目 创建项目&#xff08;文档中还提供了 Rspack 内置 monorepo 框架 Nx 的创建方式&#xff0c;根据需求进行选择&#xff09; # npm 方式 $ npm create rspacklatest# yarn 方式 $ yarn create…

AI 绘画Stable Diffusion 研究(十四)SD 图生图+剪映制作人物说话视频

大家好&#xff0c;我是风雨无阻。 前一篇&#xff0c;我们详细介绍了使用 SadTlaker制作数字人视频案例&#xff0c;感兴趣的朋友请前往查看:AI 绘画Stable Diffusion 研究&#xff08;十三&#xff09;SD数字人制作工具SadTlaker使用教程。 对于没有安装 SadTlaker 插件的朋友…

Java—实现多线程程序 | 入门

目录 一、前言 二、基本概念 进程 线程 三、Java多线程实现 java.lang.Thread类 获取线程名字及对象 获取main进程名 Thread currentThread() 四、线程优先级 设置优先级 一、前言 前期入门学习的代码中&#xff0c;全部都是单线的程序&#xff0c;也就是从头到尾…

leetcode 767. Reorganize String(重组字符串)

重新排列字符串s中的字母&#xff0c;使得任意两个相邻的字母都不相同。 思路&#xff1a; 让相邻字母不同&#xff0c;能想到的办法是先把相同的字母排列&#xff0c; 然后在相同字母的缝隙中插入另一种字母。 比如"aab", 先把"a a"排出来&#xff0c;再…

界面组件DevExpress Reporting——增强的SQL和实体框架数据源引入

DevExpress Reporting是.NET Framework下功能完善的报表平台&#xff0c;它附带了易于使用的Visual Studio报表设计器和丰富的报表控件集&#xff0c;包括数据透视表、图表&#xff0c;因此您可以构建无与伦比、信息清晰的报表。 本文总结了v23.1中针对DevExpress报表和BI Das…

UI位置与布局

UI位置与布局 引言 发现UGUI的RectTransform定位还是很复杂的&#xff0c;感觉有必要详细了解一下 RectTransform 继承自Transform。他的local position由其他几个变量控制。建议不要直接设置position 目的是为了实现UI自动布局。这套方法将绝对定位&#xff0c;相对定位&a…

vue和react学哪一个比较有助于以后发展?

前言 首先声明vue和react这两个框架都是很优秀的前端框架&#xff0c;使用的人群下载量上数量也是相当的庞大&#xff0c;这篇文章没有贬低或者攻击任何一个框架的意思&#xff0c;只在于根据答主的问题来对这两个框架做出对比&#xff0c;以方便大家更加清晰的了解到当下vue和…

设计模式--适配器模式(Adapter Pattern)

一、什么是适配器模式&#xff08;Adapter Pattern&#xff09; 适配器模式&#xff08;Adapter Pattern&#xff09;是一种结构型设计模式&#xff0c;它允许将一个类的接口转换成客户端所期望的另一个接口。适配器模式主要用于解决不兼容接口之间的问题&#xff0c;使得原本…