使用Vue3实现可拖拽的九点导航面板

开篇

本文使用Vue3实现了一个可拖拽的九宫导航面板。这个面板在我这里的应用场景是我个人网站的首页的位置,九宫导航对应的是用户最后使用或者最多使用的九个功能,正常应该是由后端接口返回的,不过这里为了简化,写的是固定的数组数据。

效果展示

截图

在这里插入图片描述在这里插入图片描述

视频

九点导航面板

功能概述

该导航面板初始状态为三行三列排布的九个圆点的集合,当鼠标放上去之后,九个圆点就会变成九个功能的图标。同时,该面板具有可拖拽功能,可以拖到浏览器上任何一个位置。

代码实现

<template><div class="nine-point-container"v-draggable:style="{ left: position.x + 'px', top: position.y + 'px' }":class="{ 'expanded': isPanelHovered && !isDragging }"><!-- 九点导航面板 --><div class="nine-point-panel"@mouseenter="handlePanelHover(true)"@mouseleave="handlePanelHover(false)":class="{ 'panel-expanded': isPanelHovered && !isDragging }"><!-- 背景遮罩,只在未展开状态显示 --><div class="background-mask" :class="{ 'mask-hidden': isPanelHovered && !isDragging }"></div><div v-for="(item, index) in navigationItems" :key="index"class="nav-item":style="getItemStyle(index)"><!-- 默认状态显示圆点 --><div class="dot" :class="{ 'dot-hidden': isPanelHovered && !isDragging }":style="getDotStyle(index)"></div><!-- 图标和文字 --><div class="hover-content" :class="{ 'content-visible': isPanelHovered && !isDragging }":style="getContentStyle(index)"><el-icon><component :is="item.icon" /></el-icon><span class="item-text">{{ item.text }}</span></div></div></div></div>
</template><script setup>
import { ref, computed } from 'vue'
import { Clock,FullScreen,CirclePlus,Search,Message,Crop,Delete,Tools,PieChart
} from '@element-plus/icons-vue'// 添加拖动状态
const isDragging = ref(false)// 修改拖拽指令
const vDraggable = {mounted(el) {el.style.position = 'fixed'el.style.cursor = 'move'el.onmousedown = (e) => {isDragging.value = trueconst disX = e.clientX - el.offsetLeftconst disY = e.clientY - el.offsetTopdocument.onmousemove = (e) => {let left = e.clientX - disXlet top = e.clientY - disY// 防止拖出视口const maxX = window.innerWidth - el.offsetWidthconst maxY = window.innerHeight - el.offsetHeightleft = Math.min(maxX, Math.max(0, left))top = Math.min(maxY, Math.max(0, top))position.value.x = leftposition.value.y = top}document.onmouseup = () => {document.onmousemove = nulldocument.onmouseup = null// 添加一个小延时,防止拖动结束后立即触发hover效果setTimeout(() => {isDragging.value = false}, 100)}}}
}// 组件位置状态
const position = ref({x: 20,y: 20
})// 面板悬停状态
const isPanelHovered = ref(false)// 处理面板悬停
const handlePanelHover = (isHovered) => {isPanelHovered.value = isHovered
}// 导航项配置 - 移除 isHovered 属性,因为现在统一控制
const navigationItems = ref([{ icon: FullScreen, text: '全屏' },{ icon: CirclePlus, text: '新建' },{ icon: Clock, text: '时钟' },{ icon: Search, text: '搜索' },{ icon: Message, text: '消息' },{ icon: Crop, text: '裁剪' },{ icon: Delete, text: '删除' },{ icon: Tools, text: '工具' },{ icon: PieChart, text: '图表' }
])// 计算每个项目的动画延迟
const getItemStyle = (index) => {// 计算项目在网格中的位置const row = Math.floor(index / 3)const col = index % 3// 计算到中心点的距离(用于径向动画)const centerRow = 1const centerCol = 1const distance = Math.sqrt(Math.pow(row - centerRow, 2) + Math.pow(col - centerCol, 2))// 基础延迟时间(ms)const baseDelay = distance * 50return {'--item-delay': `${baseDelay}ms`,'--item-row': row,'--item-col': col,'--item-distance': distance}
}// 圆点的特定样式
const getDotStyle = (index) => {return {'--dot-delay': `${index * 30}ms`}
}// 内容的特定样式
const getContentStyle = (index) => {return {'--content-delay': `${index * 30}ms`}
}
</script><style lang="scss" scoped>
.nine-point-container {z-index: 1000;user-select: none;transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);transform-origin: center;width: 120px; // 初始较小的尺寸height: 120px;&.expanded {width: 300px; // 展开后的较大尺寸height: 300px;transform: scale(1.1);}
}.nine-point-panel {position: relative;display: grid;grid-template-columns: repeat(3, 1fr);gap: 8px; // 初始较小的间距padding: 12px; // 初始较小的内边距border-radius: 16px;transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);transform-origin: center;// 背景遮罩.background-mask {position: absolute;top: 0;left: 0;right: 0;bottom: 0;background: rgba(30, 30, 30, 0.9);backdrop-filter: blur(10px);border-radius: 16px;transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);z-index: -1;&.mask-hidden {opacity: 0;transform: scale(1.2);}}&.panel-expanded {gap: 24px;padding: 24px;}
}.nav-item {position: relative;width: 24px; // 初始较小的尺寸height: 24px;display: flex;align-items: center;justify-content: center;transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);transform-origin: center;.dot {width: 6px; // 初始较小的圆点height: 6px;background: rgba(255, 255, 255, 0.5);border-radius: 50%;position: absolute;transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);transition-delay: var(--dot-delay);transform-origin: center;box-shadow: 0 0 10px rgba(255, 255, 255, 0.3);&.dot-hidden {transform: scale(0) rotate(180deg);opacity: 0;}}.hover-content {position: absolute;width: 100%;height: 100%;display: flex;flex-direction: column;align-items: center;justify-content: center;color: #fff;opacity: 0;transform: scale(0.6) rotate(-45deg);transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);transition-delay: var(--content-delay);filter: drop-shadow(0 0 10px rgba(255, 255, 255, 0.2));&.content-visible {opacity: 1;transform: scale(1) rotate(0deg);width: 60px;height: 60px;.el-icon {color: #409EFF; // 默认使用Element Plus的主题蓝色}.item-text {color: #E6E8EB; // 浅灰色文字}}.el-icon {font-size: 24px;margin-bottom: 8px;transition: all 0.3s ease;// 添加渐变色图标效果background: linear-gradient(120deg, #409EFF, #53C1FF);-webkit-background-clip: text;background-clip: text;-webkit-text-fill-color: transparent;filter: drop-shadow(0 0 8px rgba(64, 158, 255, 0.3));}.item-text {font-size: 12px;opacity: 0;transform: translateY(10px);transition: all 0.3s ease;transition-delay: calc(var(--content-delay) + 100ms);white-space: nowrap;text-shadow: 0 0 10px rgba(230, 232, 235, 0.3);}}&:hover {.hover-content {.el-icon {transform: scale(1.2);filter: drop-shadow(0 0 12px rgba(64, 158, 255, 0.5));}.item-text {opacity: 1;transform: translateY(0);color: #FFFFFF;}}background: transparent; // 移除悬停背景色border-radius: 12px;transform: translateZ(20px);}
}// 修改图标颜色样式
.nav-item {&:nth-child(1) .hover-content .el-icon { color: #FF6B6B; }&:nth-child(2) .hover-content .el-icon { color: #4ECDC4; }&:nth-child(3) .hover-content .el-icon { color: #96E6A1; }&:nth-child(4) .hover-content .el-icon { color: #A18CD1; }&:nth-child(5) .hover-content .el-icon { color: #FF9A9E; }&:nth-child(6) .hover-content .el-icon { color: #84FAB0; }&:nth-child(7) .hover-content .el-icon { color: #FF9A9E; }&:nth-child(8) .hover-content .el-icon { color: #43E97B; }&:nth-child(9) .hover-content .el-icon { color: #FA709A; }.hover-content {.el-icon {font-size: 24px;margin-bottom: 8px;transition: all 0.3s ease;// 移除渐变背景filter: drop-shadow(0 0 8px rgba(255, 255, 255, 0.3));}&.content-visible .el-icon {filter: drop-shadow(0 0 12px rgba(255, 255, 255, 0.5));}}&:hover .hover-content .el-icon {filter: drop-shadow(0 0 15px currentColor);}
}// 优化展开动画
.panel-expanded .nav-item .hover-content {transition: all 0.5s cubic-bezier(0.34, 1.56, 0.64, 1);.el-icon {transition: all 0.3s ease, filter 0.5s ease;}
}// 添加拖动时的样式
.nine-point-container.dragging {cursor: grabbing;.nav-item {pointer-events: none;}
}
</style>

后续可优化点

  • 九个功能图标由后端动态返回,可动态展示用户最常用的九个功能或者最近使用的九个功能的快捷入口;
  • 九个功能图标的样式和颜色,也可由后端返回,并增加对应的功能图标的样式配置页面;
  • 可进一步优化由九点到图标的动画效果;

以上便是九点导航面板的全部实现代码,希望能对您有所抛砖引玉的作用~
感谢阅读!

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

小利特惠源码/生活缴费/电话费/油卡燃气/等充值业务类源码附带承兑系统

全新首发小利特惠/生活缴费/电话费/油卡燃气/等充值业务类源码附带U商承兑系统 安装教程如下 图片:

FlinkSql使用中rank/dense_rank函数报错空指针

问题描述 在flink1.16(甚至以前的版本)中&#xff0c;使用rank()或者dense_rank()进行排序时&#xff0c;某些场景会导致报错空指针NPE(NullPointerError) 报错内容如下 该报错没有行号/错误位置&#xff0c;无法排查 现状 目前已经确认为bug&#xff0c;根据github上的PR日…

C语言精粹:深入探索字符串函数

大家好&#xff0c;这里是小编的博客频道 小编的博客&#xff1a;就爱学编程 很高兴在CSDN这个大家庭与大家相识&#xff0c;希望能在这里与大家共同进步&#xff0c;共同收获更好的自己&#xff01;&#xff01;&#xff01; 本文目录 引言正文&#xff08;1&#xff09;常见字…

微信阅读网站小程序的设计与实现(LW+源码+讲解)

专注于大学生项目实战开发,讲解,毕业答疑辅导&#xff0c;欢迎高校老师/同行前辈交流合作✌。 技术范围&#xff1a;SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容&#xff1a;…

RabbitMQ 死信队列

文章目录 前言1、死信交换机 DLX 与死信队列 DLQ2、死信队列的实现2.1、声明原队列信息2.2、声明死信队列信息2.3、完整示例 3、死信消息流转原理 前言 消息过期以后&#xff0c;如果没有任何配置&#xff0c;是会直接丢弃的。我们可以通过配置让这样的消息变成死信&#xff0…

《边界感知的分而治之方法:基于扩散模型的无监督阴影去除解决方案》学习笔记

paper&#xff1a;Boundary-Aware Divide and Conquer: A Diffusion-Based Solution for Unsupervised Shadow Removal 目录 摘要 1、介绍 2、相关工作 2.1 阴影去除 2.2 去噪扩散概率模型&#xff08;Denoising Diffusion Probabilistic Models, DDPM&#xff09; 3、方…

leetcode28-找出字符串中第一个匹配的下标

leetcode 28 思路 首先循环haystack&#xff0c;然后当当前字符和needle的首字母相同的时候截取出长度等于needle的字符串&#xff0c;进行比较是否相等&#xff0c;如果相等则说明当前index为第一个匹配的下标&#xff0c;如果不相等则说明不正确继续进行遍历&#xff0c;直…

【esp32-uniapp】uniapp小程序篇02——引入组件库

一、引入组件库&#xff08;可自行选择其他组件库&#xff09; 接下来介绍colorUI、uview plus的安装&#xff0c;其他的安装可自行查找教程 1.colorUI weilanwl/coloruicss: 鲜亮的高饱和色彩&#xff0c;专注视觉的小程序组件库 下载之后解压&#xff0c;将\coloruicss-ma…

YOLOv8改进,YOLOv8检测头融合DynamicHead,并添加小目标检测层(四头检测),适合目标检测、分割等,全网独发

摘要 作者提出一种新的检测头,称为“动态头”,旨在将尺度感知、空间感知和任务感知统一在一起。如果我们将骨干网络的输出(即检测头的输入)视为一个三维张量,其维度为级别 空间 通道,这样的统一检测头可以看作是一个注意力学习问题,直观的解决方案是对该张量进行全自…

Vue2官网教程查漏补缺学习笔记 - 3Vue实例4模板语法5计算属性监听器

3 Vue实例 3.1 创建一个 Vue 实例 每个 Vue 应用都是通过用 Vue 函数创建一个新的 Vue 实例开始的&#xff1a; var vm new Vue({// 选项 })虽然没有完全遵循 MVVM 模型&#xff0c;但是 Vue 的设计也受到了它的启发。因此在文档中经常会使用 vm (ViewModel 的缩写) 这个变…

【高项】6.3 排列活动顺序 ITTO

输入 项目管理计划组件&#xff1a; ① 进度管理计划&#xff1b;② 范围基准 项目文件&#xff1a; ① 假设日志&#xff1b;② 活动属性&#xff1b;③ 活动清单&#xff1b;④ 里程碑清单 工具与技术 紧前关系绘图法&#xff08;PDM&#xff09; ① 完成到开始&…

将Deepseek接入本地Vscode

第一步&#xff1a;获取Deepseek APIKEY 1.1 登录Deepseek官网 https://www.deepseek.com/ 1.2 选择API开放平台 1.3 注册账号并登录 1.4 登录成功后的就界面 1.5 点击左侧菜单栏“API keys”&#xff0c;并创建API key 名称自定义输入 生成API key 复制保存&#xff0c;丢失…

docker使用笔记

文章目录 1.Docker 与容器2.核心概念与安装配置2.1 核心概念2.2 docker 安装ubuntu使用官方的脚本自动安装准备条件准备安装安装Docker安装Docker 命令补全工具允许非Root用户执行docker 命令最后一步 更新.bashrc文件 [修改docker 默认的存储路径](https://www.cnblogs.com/du…

vim如何设置制表符表示的空格数量

:set tabstop4 设置制表符表示的空格数量 制表符就是tab键&#xff0c;一般默认是四个空格的数量 示例&#xff1a; &#xff08;vim如何使设置制表符表示的空格数量永久生效&#xff1a;vim如何使相关设置永久生效-CSDN博客&#xff09;

PPT添加与管理批注的操作指南

​​​ 批注是PPT中一个非常实用的功能&#xff0c;它不仅能帮助我们在演讲和设计过程中记录想法&#xff0c;还能与他人协作时提供有价值的反馈。无论是团队讨论、审稿&#xff0c;还是个人思考&#xff0c;批注的运用都能让我们的PPT更加完善和高效。我会详细介绍如何在PPT中…

CASAIM与友达光电达成深度合作,CASAIM IS自动化蓝光测量技术为创新显示技术发展注入新的活力

近期&#xff0c;CASAIM与友达光电股份有限公司在液晶显示面板智能自动三维检测技术上达成深度合作&#xff0c;联合打造CASAIM IS全自动化智能检测系统,助力光电产品显示面板制造全自动化3d测量&#xff0c;实现高精度、高效率测量和检测&#xff0c;进一步提升产品质量和生产…

【已解决】OSS配置问题

OSS SDK快速入门_对象存储(OSS)-阿里云帮助中心 阿里官方的SDK使用方法还得配置环境变量access Key、access Secret &#xff0c;我没有配置&#xff0c;仅把access Key和access Secret写到了yml文件读取&#xff0c;结果上传图片时还是出现下面的问题。 [ ERROR ] [ com.s…

STM32 硬件I2C读写

单片机学习&#xff01; 目录 前言 一、步骤 二、配置I2C外设 2.1 开启I2C外设和GPIO口时钟 2.2 GPIO口初始化为复用开漏模式 2.3 结构体配置I2C 2.4 使能I2C 2.5 配置I2C外设总代码 三、指定地址写时序 3.1 生产起始条件S 3.2 监测EV5事件 3.3 发送从机地址 3.4 …

C语言程序设计十大排序—冒泡排序

文章目录 1.概念✅2.冒泡排序&#x1f388;3.代码实现✅3.1 直接写✨3.2 函数✨ 4.总结✅ 1.概念✅ 排序是数据处理的基本操作之一&#xff0c;每次算法竞赛都很多题目用到排序。排序算法是计算机科学中基础且常用的算法&#xff0c;排序后的数据更易于处理和查找。在计算机发展…

Python网络自动化运维---用户交互模块

文章目录 目录 文章目录 前言 实验环境准备 一.input函数 代码分段解析 二.getpass模块 前言 在前面的SSH模块章节中&#xff0c;我们都是将提供SSH服务的设备的账户/密码直接写入到python代码中&#xff0c;这样很容易导致账户/密码泄露&#xff0c;而使用Python中的用户交…