三开关VUE组件

news2024/11/28 1:18:57

一、使用效果

请添加图片描述请添加图片描述

请添加图片描述

<template>
  <QqThreeSwitch v-model="value" />
  <!-- <SqThreeSwitch v-model="value" :options="['test1', 'test2', 'test3']">
    <template #left-action>
      <div style="display: flex">
        <IconMoon />
      </div>
    </template>
    <template #middle-action>
      <div style="display: flex">
        <IconSunny />
      </div>
    </template>
    <template #right-action>
      <div style="display: flex">
        <IconSystem />
      </div>
    </template>
  </SqThreeSwitch> -->
</template>

<script setup>
import SqThreeSwitch from './components/SqThreeSwitch.vue'
import { ref } from 'vue'

const value = ref(0)
</script>

二、SqThreeSwitch.vue源码

<template>
  <div class="sq-three-switch">
    <button class="focus-btn" :style="focusBtnStyle" @click="handleBtnClick">
      按下空格切换主题, 当前选择:{{ selectedOption }}
    </button>
    <div v-show="isMouseEnter" class="tooltip" tabindex="-1" :style="tooltipStyle">
      <div class="tip-text">{{ tooltipText }}</div>
      <svg class="tip-arrow" width="16px" height="8px" :style="tipArrowStyle">
        <polygon points="0,-1 8,7 16,-1" />
      </svg>
    </div>
    <div ref="selectedOptionRef" class="selected-option">
      <span>{{ selectedOption }}</span>
    </div>
    <div
      ref="controlRef"
      class="control plane-border"
      @click="handleClick"
      @mouseenter="handleMouseEnter"
      @mouseleave="handleMouseLeave"
      @mousemove="debouncedHandleMouseMove"
    ></div>
    <div class="plane"></div>
    <div class="badge-dots">
      <div
        v-for="(dot, index) in [0, 1, 2]"
        :key="index"
        class="dot"
        :class="{ 'dot-animate': dotAnimateFlag }"
        @animationend="handleAnimationEnd"
      ></div>
    </div>
    <div class="handle" :style="handleStyle">
      <slot v-if="modelValue === 0" name="left-action"></slot>
      <slot v-if="modelValue === 1" name="middle-action"></slot>
      <slot v-if="modelValue === 2" name="right-action"></slot>
    </div>
  </div>
</template>

<script setup>
import { ref, watch, computed, nextTick, onMounted, onBeforeUnmount } from 'vue'
import { useDebounceFn } from '@vueuse/core'

const props = defineProps({
  modelValue: {
    type: Number,
    default: 0
  },
  options: {
    type: Array,
    default: () => ['选项A', '选项B', '选项C']
  }
})
const emit = defineEmits(['update:modelValue'])

const selectedOptionRef = ref(null)

const focusBtnStyle = ref({})
nextTick(() => {
  focusBtnStyle.value = {
    width: `${selectedOptionRef.value.getBoundingClientRect().width + 50}px`
  }
})
watch(
  () => props.modelValue,
  () => {
    nextTick(() => {
      focusBtnStyle.value = {
        width: `${selectedOptionRef.value.getBoundingClientRect().width + 50}px`
      }
    })
  }
)

const controlRef = ref(null)
const haveTooltipSpace = ref(false)
const tipArrowStyle = computed(() => {
  return {
    transform: haveTooltipSpace.value ? '' : 'translateY(-26px) rotate(180deg)'
  }
})
function checkTooltipSpace(deadline) {
  if (deadline.timeRemaining() > 0) {
  
    const rect = controlRef.value?.getBoundingClientRect()
    if (rect) {
      haveTooltipSpace.value = rect.top >= 20
    }
  }
}
const debouncedCheckTooltipSpace = useDebounceFn(
  () => requestIdleCallback(checkTooltipSpace, { timeout: 200 }),
  200
)
let intervalId
onMounted(() => {
  debouncedCheckTooltipSpace()
  window.addEventListener('scroll', debouncedCheckTooltipSpace)
  window.addEventListener('resize', debouncedCheckTooltipSpace)
  intervalId = setInterval(debouncedCheckTooltipSpace, 2000)
  console.log('作者主页: https://blog.csdn.net/qq_39124701')
})
onBeforeUnmount(() => {
  window.removeEventListener('scroll', debouncedCheckTooltipSpace)
  window.removeEventListener('resize', debouncedCheckTooltipSpace)
  if (intervalId !== null) {
    clearInterval(intervalId)
  }
})

const isMouseEnter = ref(false)
const tooltipText = ref(props.options[props.modelValue])
const tooltipStyle = ref({
  left: props.modelValue === 0 ? '0px' : props.modelValue === 1 ? '20px' : '40px',
  top: haveTooltipSpace.value ? '0px' : '54px'
})

const selectedOption = computed(() => {
  return props.options[props.modelValue]
})

const dotAnimateFlag = ref(false)

const handleStyle = ref({
  left:
    props.modelValue === 0
      ? '2px'
      : props.modelValue === 1
        ? 'calc(50% - 9px)'
        : 'calc(100% - 19px)'
})
watch(
  () => props.modelValue,
  (newValue) => {
    handleStyle.value = {
      left: newValue === 0 ? '2px' : newValue === 1 ? 'calc(50% - 9px)' : 'calc(100% - 19px)'
    }
  }
)

function handleClick(/** @type { MouseEvent } */ event) {
  /** @type { Element } */
  const eventTarget = event.target
  const rect = eventTarget.getBoundingClientRect()
  const clickX = event.clientX - rect.left
  const oneThirdWidth = rect.width / 3

  if (clickX < oneThirdWidth) {
    if (props.modelValue === 0) {
      dotAnimateFlag.value = true
    }
    emit('update:modelValue', 0)
  } else if (clickX > oneThirdWidth * 2) {
    if (props.modelValue === 2) {
      dotAnimateFlag.value = true
    }
    emit('update:modelValue', 2)
  } else {
    if (props.modelValue === 1) {
      dotAnimateFlag.value = true
    }
    emit('update:modelValue', 1)
  }
}
function handleBtnClick() {
  if (props.modelValue === 0) {
    emit('update:modelValue', 1)
  } else if (props.modelValue === 1) {
    emit('update:modelValue', 2)
  } else if (props.modelValue === 2) {
    emit('update:modelValue', 0)
  }
}
function handleMouseEnter() {
  isMouseEnter.value = true
}
function handleMouseLeave() {
  isMouseEnter.value = false
}
const debouncedHandleMouseMove = useDebounceFn(handleMouseMove, 40)
function handleMouseMove(event) {
  if (!isMouseEnter.value) {
    return
  }
  const rect = event.target.getBoundingClientRect()
  const clickX = event.clientX - rect.left
  const oneThirdWidth = rect.width / 3

  if (clickX < oneThirdWidth) {
    tooltipText.value = props.options[0]
    tooltipStyle.value = { left: '0px', top: haveTooltipSpace.value ? '0px' : '54px' }
  } else if (clickX > oneThirdWidth * 2) {
    tooltipText.value = props.options[2]
    tooltipStyle.value = { left: 'calc(100% - 21px)', top: haveTooltipSpace.value ? '0px' : '54px' }
  } else {
    tooltipText.value = props.options[1]
    tooltipStyle.value = { left: 'calc(50% - 11px)', top: haveTooltipSpace.value ? '0px' : '54px' }
  }
}

function handleAnimationEnd() {
  dotAnimateFlag.value = false
}
</script>

<style scoped>
.sq-three-switch {
  position: relative;
  width: 60px;
  height: 20px;
  /* scale: 5;
  transform-origin: 0% 0%;
  z-index: 1; */
}
.sq-three-switch > * {
  position: absolute;
}
.sq-three-switch > .plane,
.sq-three-switch > .badge-dots,
.sq-three-switch > .handle {
  pointer-events: none;
}
.sq-three-switch > .focus-btn {
  height: 100%;
  border-radius: 10px;
  border: 0;
  outline-offset: 1px;
  font-size: 0;
}
.sq-three-switch > .focus-btn:focus {
  outline: 2px solid #409eff;
}
.sq-three-switch > .tooltip {
  z-index: 1;
  transform: translateY(-27px);
  white-space: nowrap;
  background-color: #e6e6e6;
  border: 1px solid gray;
  border-radius: 4px;
  padding: 1px 11px;
  transition: left 0.2s;
}
.sq-three-switch > .tooltip > .tip-text {
  font-size: 12px;
  color: black;
}
.sq-three-switch > .tooltip > .tip-arrow {
  position: absolute;
  top: 18px;
  left: 1px;
}
.sq-three-switch > .tooltip > .tip-arrow polygon {
  fill: #e6e6e6;
  stroke: gray;
  stroke-width: 1;
}
.sq-three-switch > .selected-option {
  height: 100%;
  background: linear-gradient(to right, #a8d4ff, #409eff 16px);
  border-radius: 10px;
  border-top-left-radius: 0;
  border-bottom-left-radius: 0;
  transform: translateX(50px);
  display: flex;
  justify-content: center;
  font-size: 14px;
  color: white;
  white-space: nowrap;
}
.sq-three-switch > .selected-option > span {
  padding-left: 16px;
  padding-right: 10px;
  user-select: none;
}
.sq-three-switch > .control {
  width: 100%;
  height: 20px;
  border-radius: 10px;
  background: #409eff;
  cursor: pointer;
}
.sq-three-switch > .plane {
  top: 1px;
  left: 1px;
  width: calc(100% - 2px);
  height: 18px;
  border-radius: 10px;
  background: #409eff;
}
.sq-three-switch > .badge-dots > .dot {
  position: absolute;
  top: 8px;
  left: 8px;
  width: 4px;
  height: 4px;
  border-radius: 100%;
  transition: all 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
  background-color: white;
}
.sq-three-switch > .badge-dots > .dot:nth-child(2) {
  left: 27px;
}
.sq-three-switch > .badge-dots > .dot:nth-child(3) {
  left: 47px;
}
.dot-animate {
  animation: dotAnimation 0.3s;
}
@keyframes dotAnimation {
  0% {
    background-color: white;
  }
  25% {
    background-color: black;
  }
  50% {
    background-color: white;
  }
  75% {
    background-color: black;
  }
  100% {
    background-color: white;
  }
}
.sq-three-switch > .handle {
  top: 2px;
  left: 2px;
  width: 16px;
  height: 16px;
  border-radius: 100%;
  transition: all 0.3s cubic-bezier(0.22, 0.61, 0.36, 1);
  background-color: white;
}

html.dark .sq-three-switch > .tooltip {
  background-color: #303133;
}
html.dark .sq-three-switch > .tooltip > .tip-arrow polygon {
  fill: #303133;
}
html.dark .sq-three-switch > .tooltip > .tip-text {
  color: white;
}
</style>

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

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

相关文章

线段树与树状数组 (C++)

线段树&#xff1a;基于分治思想的二叉树&#xff0c;用于维护区间信息&#xff08;区间和&#xff0c;区间最值等&#xff09;&#xff0c;区间修改和区间查询的时间复杂度为logn 叶子节点存储元素本身&#xff0c;非叶子节点存取区间信息 1.节点&#xff1a;是一个结构体&a…

vue3 uniapp 扫普通链接或二维码打开小程序并获取携带参数

vue3 uniapp 扫普通链接或二维码打开小程序并获取携带参数 微信公众平台添加配置 微信公众平台 > 开发管理 > 开发设置 > 扫普通链接二维码打开小程序 配置链接规则需要下载校验文档给后端存入服务器中&#xff0c;保存配置的时候会校验一次&#xff0c;确定当前的配…

数据结构(初阶6)---二叉树(遍历——递归的艺术)(详解)

二叉树的遍历与练习 一.二叉树的基本遍历形式1.前序遍历(深度优先遍历)2.中序遍历(深度优先遍历)3.后序遍历(深度优先遍历)4.层序遍历&#xff01;&#xff01;(广度优先遍历) 二.二叉树的leetcode小练习1.判断平衡二叉树1&#xff09;正常解法2&#xff09;优化解法 2.对称二叉…

spring boot2.7集成OpenFeign 3.1.7

1.Feign Feign是一个声明式web服务客户端。它使编写web服务客户端更容易。要使用Feign&#xff0c;请创建一个接口并对其进行注释。它具有可插入注释支持&#xff0c;包括Feign注释和JAX-RS注释。Feign还支持可插拔编码器和解码器。Spring Cloud增加了对Spring MVC注释的支持&…

基于Redis内核的热key统计实现方案|得物技术

一、Redis热key介绍 Redis热key问题是指单位时间内&#xff0c;某个特定key的访问量特别高&#xff0c;占用大量的CPU资源&#xff0c;影响其他请求并导致整体性能降低。而且&#xff0c;如果访问热key的命令是时间复杂度较高的命令&#xff0c;会使得CPU消耗变得更加严重&…

CTF-Hub SQL 报错注入(纯手动注入)

​ 当输入1时&#xff0c;发现只有查询正确&#xff0c;基本上可以判断出没有回显 开始注入(工具hackerBar) 题目是报错注入&#xff0c;方向就比较明显&#xff0c;大致说一下用到的函数和原理。 常见报错注入函数&#xff1a; 通过 floor() 报错注入通过 extractValue() …

Elasticsearch中的节点(比如共20个),其中的10个选了一个master,另外10个选了另一个master,怎么办?

大家好&#xff0c;我是锋哥。今天分享关于【Elasticsearch中的节点&#xff08;比如共20个&#xff09;&#xff0c;其中的10个选了一个master&#xff0c;另外10个选了另一个master&#xff0c;怎么办&#xff1f;】面试题。希望对大家有帮助&#xff1b; Elasticsearch中的节…

linux安装mysql8.0.40

一、下载MySQL安装包 1.查看glibc版本 rpm -qa | grep glibc 2.到mysql官网下载安装包 ​ 二、解压安装 1.上传压缩包纸/usr/local 目录下&#xff0c;解压&#xff1a; tar -xvf mysql-8.0.40-linux-glibc2.17-x86_64.tar.xz 2.重命名&#xff1a; mv mysql-8.0.40-linux-…

【大数据学习 | Spark-Core】RDD的五大特性(包含宽窄依赖)

分析一下rdd的特性和执行流程 A list of partitions 存在一系列的分区列表A function for computing each split 每个rdd上面都存在compute方法进行计算A list of dependencies on other RDDs 每个rdd上面都存在一系列的依赖关系Optionally, a Partitioner for key-value RDDs…

在 Taro 中实现系统主题适配:亮/暗模式

目录 背景实现方案方案一&#xff1a;CSS 变量 prefers-color-scheme 媒体查询什么是 prefers-color-scheme&#xff1f;代码示例 方案二&#xff1a;通过 JavaScript 监听系统主题切换 背景 用Taro开发的微信小程序&#xff0c;需求是页面的UI主题想要跟随手机系统的主题适配…

【C语言】int *p[ ] 与 int (*p)[ ] 的区分辨析

博客主页&#xff1a; [小ᶻ☡꙳ᵃⁱᵍᶜ꙳] 本文专栏: C语言 文章目录 &#x1f4af;前言&#x1f4af;基本概念&#xff1a;数组与指针&#x1f4af;理解 int *p[10] 与 int (*p)[10]1. int *p[10]&#xff1a;存放指针的数组2. int (*p)[10]&#xff1a;指向数组的指针 …

Vue3 el-table 默认选中 传入的数组

一、效果&#xff1a; 二、官网是VUE2 现更改为Vue3写法 <template><el-table:data"tableData"border striperow-key"id"ref"tableRef":cell-style"{ text-align: center }":header-cell-style"{background: #b7babd…

MT6769/MTK6769核心板规格参数_联发科安卓主板开发板方案

MT6769安卓核心板具有集成的蓝牙、FM、WLAN和GPS模块&#xff0c;是一个高度集成的基带平台&#xff0c;结合了调制解调器和应用处理子系统&#xff0c;以支持LTE/LTE-A和C2K智能手机应用。 该芯片集成了两个工作频率高达2.0GHz的ARMCortex-A75内核、六个工作频率高达1.70GHz的…

在Excel中处理不规范的日期格式数据并判断格式是否正确

有一个Excel表&#xff0c;录入的日期格式很混乱&#xff0c;有些看着差不多&#xff0c;但实际多一个空格少一个字符很难发现&#xff0c;希望的理想格式是 1980-01-01&#xff0c;10位&#xff0c;即&#xff1a;“YYYY-mm-dd”&#xff0c;实际上数据表中这样的格式都有 19…

flask请求头回显的学习和探究如何进行错误页面污染回显

请求头 首先我们要了解一些flask的请求和响应是利用了什么。 flask的请求和响应主要利用了werkzeug&#xff0c;那么我们就要先了解一下什么是werkzeug&#xff0c;其结构又是什么。 werkzeug是一个基于python开发的一个web工具包&#xff0c;其是flask的核心组件之一。其功能…

【Unity踩坑】Unity中父对象是非均匀缩放时出现倾斜或剪切现象

The game object is deformed when the parent object is in non-uniform scaling. 先来看一下现象 有两个Cube, Cube1&#xff08;Scale2,1,1)&#xff0c;Cube2&#xff08;Scale1,1,1&#xff09; 将Cube2拖拽为Cube2的子对象。并且将position设置为&#xff08;-0.6,1,0&a…

uni-app 蓝牙开发

一. 前言 Uni-App 是一个使用 Vue.js 开发&#xff08;所有&#xff09;前端应用的框架&#xff0c;能够编译到 iOS、Android、快应用以及各种小程序等多个平台。因此&#xff0c;如果你需要快速开发一款跨平台的应用&#xff0c;比如在 H5、小程序、iOS、Android 等多个平台上…

解决SSL VPN客户端一直提示无法连接服务器的问题

近期服务器更新VPN后&#xff0c;我的win10电脑一致无法连接到VPN服务器&#xff0c; SSL VPN客户端总是提示无法连接到服务端。网上百度尝试了各种方法后&#xff0c;终于通过以下设置方式解决了问题&#xff1a; 1、首先&#xff0c;在控制面板中打开“网络和共享中心”窗口&…

spring boot框架漏洞复现

spring - java开源框架有五种 Spring MVC、SpringBoot、SpringFramework、SpringSecurity、SpringCloud spring boot版本 版本1: 直接就在根下 / 版本2:根下的必须目录 /actuator/ 端口:9093 spring boot搭建 1:直接下载源码打包 2:运行编译好的jar包:actuator-testb…

大语言模型LLM的微调代码详解

代码的摘要说明 一、整体功能概述 这段 Python 代码主要实现了基于 Hugging Face Transformers 库对预训练语言模型&#xff08;具体为 TAIDE-LX-7B-Chat 模型&#xff09;进行微调&#xff08;Fine-tuning&#xff09;的功能&#xff0c;使其能更好地应用于生成唐诗相关内容的…