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

news2025/1/31 2:02:21

开篇

本文使用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 = true
      const disX = e.clientX - el.offsetLeft
      const disY = e.clientY - el.offsetTop
      
      document.onmousemove = (e) => {
        let left = e.clientX - disX
        let top = e.clientY - disY
        
        // 防止拖出视口
        const maxX = window.innerWidth - el.offsetWidth
        const maxY = window.innerHeight - el.offsetHeight
        
        left = Math.min(maxX, Math.max(0, left))
        top = Math.min(maxY, Math.max(0, top))
        
        position.value.x = left
        position.value.y = top
      }
      
      document.onmouseup = () => {
        document.onmousemove = null
        document.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 = 1
  const centerCol = 1
  const distance = Math.sqrt(
    Math.pow(row - centerRow, 2) + 
    Math.pow(col - centerCol, 2)
  )
  
  // 基础延迟时间(ms)
  const baseDelay = distance * 50

  return {
    '--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.coloradmin.cn/o/2284635.html

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

相关文章

68-《贝壳花》

贝壳花 贝壳花&#xff08;学名&#xff1a;Moluccella laevis Linn.&#xff09;是属于唇形科&#xff0c;贝壳花是一、二年的草本。植株高5至60cm&#xff0c;茎四棱&#xff0c;不分枝。叶对生&#xff0c;心脏状圆形&#xff0c;边缘疏生齿牙&#xff1b;叶柄和叶近等长。花…

【自然语言处理(NLP)】深度循环神经网络(Deep Recurrent Neural Network,DRNN)原理和实现

文章目录 介绍深度循环神经网络&#xff08;DRNN&#xff09;原理和实现结构特点工作原理符号含义公式含义 应用领域优势与挑战DRNN 代码实现 个人主页&#xff1a;道友老李 欢迎加入社区&#xff1a;道友老李的学习社区 介绍 **自然语言处理&#xff08;Natural Language Pr…

2025数学建模美赛|F题成品论文

国家安全政策与网络安全 摘要 随着互联网技术的迅猛发展&#xff0c;网络犯罪问题已成为全球网络安全中的重要研究课题&#xff0c;且网络犯罪的形式和影响日益复杂和严重。本文针对网络犯罪中的问题&#xff0c;基于多元回归分析和差异中的差异&#xff08;DiD&#xff09;思…

自定义数据集 使用pytorch框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测

代码&#xff1a; import torch import numpy as np import torch.nn as nn# 定义数据&#xff1a;x_data 是特征&#xff0c;y_data 是标签&#xff08;目标值&#xff09; data [[-0.5, 7.7],[1.8, 98.5],[0.9, 57.8],[0.4, 39.2],[-1.4, -15.7],[-1.4, -37.3],[-1.8, -49.…

关于使用PHP时WordPress排错——“这意味着您在wp-config.php文件中指定的用户名和密码信息不正确”的解决办法

本来是看到一位好友的自己建站&#xff0c;所以突发奇想&#xff0c;在本地装个WordPress玩玩吧&#xff0c;就尝试着装了一下&#xff0c;因为之前电脑上就有MySQL&#xff0c;所以在自己使用PHP建立MySQL时报错了。 最开始是我的php启动mysql时有问题&#xff0c;也就是启动过…

【蓝桥杯】43694.正则问题

题目描述 考虑一种简单的正则表达式&#xff1a; 只由 x ( ) | 组成的正则表达式。 小明想求出这个正则表达式能接受的最长字符串的长度。 例如 ((xx|xxx)x|(x|xx))xx 能接受的最长字符串是&#xff1a; xxxxxx&#xff0c;长度是 6。 输入描述 一个由 x()| 组成的正则表达式。…

服务器虚拟化技术详解与实战:架构、部署与优化

&#x1f4dd;个人主页&#x1f339;&#xff1a;一ge科研小菜鸡-CSDN博客 &#x1f339;&#x1f339;期待您的关注 &#x1f339;&#x1f339; 引言 在现代 IT 基础架构中&#xff0c;服务器虚拟化已成为提高资源利用率、降低运维成本、提升系统灵活性的重要手段。通过服务…

jvm--类的生命周期

学习类的生命周期之前&#xff0c;需要了解一下jvm的几个重要的内存区域&#xff1a; &#xff08;1&#xff09;方法区&#xff1a;存放已经加载的类信息、常量、静态变量以及方法代码的内存区域 &#xff08;2&#xff09;常量池&#xff1a;常量池是方法区的一部分&#x…

TensorFlow实现逻辑回归模型

逻辑回归是一种经典的分类算法&#xff0c;广泛应用于二分类问题。本文将介绍如何使用TensorFlow框架实现逻辑回归模型&#xff0c;并通过动态绘制决策边界和损失曲线来直观地观察模型的训练过程。 数据准备 首先&#xff0c;我们准备两类数据点&#xff0c;分别表示两个不同…

《十七》浏览器基础

浏览器&#xff1a;是安装在电脑里面的一个软件&#xff0c;能够将页面内容渲染出来呈现给用户查看&#xff0c;并让用户与网页进行交互。 常见的主流浏览器&#xff1a; 常见的主流浏览器有&#xff1a;Chrome、Safari、Firefox、Opera、Edge 等。 输入 URL&#xff0c;浏览…

网络安全 | F5-Attack Signatures-Set详解

关注&#xff1a;CodingTechWork 创建和分配攻击签名集 可以通过两种方式创建攻击签名集&#xff1a;使用过滤器或手动选择要包含的签名。  基于过滤器的签名集仅基于在签名过滤器中定义的标准。基于过滤器的签名集的优点在于&#xff0c;可以专注于定义用户感兴趣的攻击签名…

STranslate 中文绿色版即时翻译/ OCR 工具 v1.3.1.120

STranslate 是一款功能强大且用户友好的翻译工具&#xff0c;它支持多种语言的即时翻译&#xff0c;提供丰富的翻译功能和便捷的使用体验。STranslate 特别适合需要频繁进行多语言交流的个人用户、商务人士和翻译工作者。 软件功能 1. 即时翻译&#xff1a; 文本翻译&#xff…

基于微信小程序的助农扶贫系统设计与实现(LW+源码+讲解)

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

我谈区域偏心率

偏心率的数学定义 禹晶、肖创柏、廖庆敏《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》P312 区域的拟合椭圆看这里。 Rafael Gonzalez的二阶中心矩的表达不说人话。 我认为半长轴和半短轴不等于特征值&#xff0c;而是特征值的根号。…

关于低代码技术架构的思考

我们经常会看到很多低代码系统的技术架构图&#xff0c;而且经常看不懂。是因为技术架构图没有画好&#xff0c;还是因为技术不够先进&#xff0c;有时候往往都不是。 比如下图&#xff1a; 一个开发者&#xff0c;看到的视角往往都是技术层面&#xff0c;你给用户讲React18、M…

若依路由配置教程

1. 路由配置文件 2. 配置内容介绍 { path: "/tool/gen-edit", component: Layout, //在路由下&#xff0c;引用组件的名称&#xff0c;在页面中包括这个组件的内容(页面框架内容) hidden: true, //此页面的内容&#xff0c;在左边的菜单中不用显示。 …

基于ESP8266的多功能环境监测与反馈系统开发指南

项目概述 本系统集成了物联网开发板、高精度时钟模块、环境传感器和可视化显示模块&#xff0c;构建了一个智能环境监测与反馈装置。通过ESP8266 NodeMCU作为核心控制器&#xff0c;结合DS3231实时时钟、DHT11温湿度传感器、光敏电阻和OLED显示屏&#xff0c;实现了环境参数的…

HTML5 Web Worker 的使用与实践

引言 在现代 Web 开发中&#xff0c;用户体验是至关重要的。如果页面在执行复杂计算或处理大量数据时变得卡顿或无响应&#xff0c;用户很可能会流失。HTML5 引入了 Web Worker&#xff0c;它允许我们在后台运行 JavaScript 代码&#xff0c;从而避免阻塞主线程&#xff0c;保…

flutter_学习记录_00_环境搭建

1.参考文档 Mac端Flutter的环境配置看这一篇就够了 flutter的中文官方文档 2. 本人环境搭建的背景 本人的电脑的是Mac的&#xff0c;iOS开发&#xff0c;所以iOS开发环境本身是可用的&#xff1b;外加Mac电脑本身就会配置Java的环境。所以&#xff0c;后面剩下的就是&#x…