CSS进阶向--配合Vue动态样式实现“超炫酷”圆环菜单

简介: CSS进阶向--配合Vue动态样式实现“超炫酷”圆环菜单

前言


在前面的两节 【CSS 进阶向】 中,我们通过纯粹的 CSS 结合 HTML 元素实现了两种不同的“流光边框”效果的按钮。两者都是通过元素(或者伪元素)配合 CSS 动画属性 animation 与自定义动画帧 @keyframes 来实现的。


这次我们通过 Vue 的动态样式和计算属性,来实现一个点击展开的圆环菜单,并且实现菜单图标的顺序显示。


先上最终效果和代码:


image.png


Markup:

<div id="app">
  <circle-menu-button />
</div>


Style:

body,
#app  {
  width: 100vw;
  height: 100vh;
}
.circle-menu-button {
  width: 100%;
  height: 100%;
  box-sizing: border-box;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  background: linear-gradient(to left, #2bc0e4, #eaecc6);
}
.menu {
  position: relative;
}
.menu-dots {
  width: 5rem;
  height: 5rem;
  border-radius: 50%;
  box-shadow: 0 0 0 0.3rem #161e3f;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transform: rotate(90deg);
  transition: 0.3s;
  cursor: pointer;
}
.menu-dots:hover {
  box-shadow: 0 0 0 0.3rem #161e3f, 0 0 0 1rem rgba(#161e3f, 0.16);
  transform: scale(1.2) rotate(90deg);
}
.menu-dot {
  width: 0.45rem;
  height: 0.45rem;
  background-color: #161e3f;
  border-radius: 50%;
}
.menu-dot + .menu-dot {
  margin-top: 0.3rem;
}
.menu.active > .menu-dots {
  transform: none;
  box-shadow: 0 0 0 3.4rem #161e3f;
}
.menu-items,
.menu-item {
  position: absolute;
  top: -3.4rem;
  left: -3.4rem;
  right: -3.4rem;
  bottom: -3.4rem;
  transition: 0.3s;
  pointer-events: none;
}
.menu-items {
  opacity: 0;
  color: #ffffff;
}
.menu.active .menu-items {
  opacity: 1;
}
.menu-item {
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0.9rem;
  font-size: 1.6rem;
  filter: blur(2px);
  opacity: 0;
  display: inline-block;
  text-align: center;
  i {
    pointer-events: auto;
    display: inline-block;
    cursor: pointer;
  }
  &:hover {
    color: #5c67ff;
  }
}
.menu.active .menu-items > .menu-item {
  opacity: 1;
  transform: none;
  filter: none;
}


Script:

<template>
  <div class="circle-menu-button">
    <nav :class="{ menu: true, active: isActive }">
      <label class="menu-dots" for="menu" @click="isActive = !isActive">
        <span class="menu-dot"></span>
        <span class="menu-dot"></span>
        <span class="menu-dot"></span>
      </label>
      <ul class="menu-items">
        <li v-for="(m, k) in menus" class="menu-item" :style="iconStyle(k)" :key="m.name">
          <i :class="m.icon" />
        </li>
      </ul>
    </nav>
  </div>
</template>
<script>
export default {
  name: "CircleMenuButton",
  data() {
    return {
      menus: [
        { icon: "el-icon-question", name: "1" },
        { icon: "el-icon-platform-eleme", name: "2" },
        { icon: "el-icon-camera-solid", name: "3" },
        { icon: "el-icon-info", name: "4" },
        { icon: "el-icon-s-shop", name: "5" },
        { icon: "el-icon-message-solid", name: "6" }
      ],
      isActive: false
    };
  },
  computed: {
    iconStyle() {
      const length = this.menus.length;
      const transitionStep = 0.5 / (this.menus.length - 1);
      return (i) => {
        return { transform: `rotate(${Math.floor((i * 360) / length)}deg)`, transitionDelay: `${transitionStep * i}s` };
      };
    }
  }
};
</script>


实现


在实现过程中,我们按照以下步骤:


  1. 整体结构 和 控制菜单展开/首起的按钮


  1. 控制按钮的 hover 状态


  1. 展开后的圆环


  1. 每个菜单的样式计算与动画配置


1. 整体结构与基础按钮


整个菜单内部包含两个部分:控制按钮 和 菜单列表,分别用一个 labelul 填充。

label 内部使用三个 span 标签实现圆点;并用动态样式绑定一个展开状态的 class 类名。


<template>
  <div class="circle-menu-button">
    <div :class="{ menu: true, active: isActive }">
      <label class="menu-dots" for="menu" @click="isActive = !isActive">
        <span class="menu-dot"></span>
        <span class="menu-dot"></span>
        <span class="menu-dot"></span>
      </label>
      <ul class="menu-items">
      </ul>
    </div>
  </div>
</template>
<script>
export default {
  name: "CircleMenuButton",
  data() {
    return {
      isActive: false
    };
  }
};
</script>
<style scoped lang="scss">
.menu {
  position: relative;
}
.menu-dots {
  width: 5rem;
  height: 5rem;
  border-radius: 50%;
  box-shadow: 0 0 0 0.3rem #161e3f;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  transform: rotate(90deg);
  transition: 0.3s;
  cursor: pointer;
}
.menu-dot {
  width: 0.45rem;
  height: 0.45rem;
  background-color: #161e3f;
  border-radius: 50%;
}
.menu-dot + .menu-dot {
  margin-top: 0.3rem;
}
</style>


此时得到如下的样式:


网络异常,图片无法展示
|


2. 添加按钮 hover 样式


为了让用户直观的感受到这个元素是一个可以点击的元素,我们可以增加一个 hover 状态的特殊样式,以提示用户。


.menu-dots:hover {
  box-shadow: 0 0 0 0.3rem #161e3f, 0 0 0 1rem rgba(#161e3f, 0.16);
  transform: scale(1.2) rotate(90deg);
}


此时会方法整个dom元素,并扩大外层阴影,提示用户点击:


网络异常,图片无法展示
|


3. 展开状态的菜单放置圆环


在用户点击后,可以旋转中心的 label 标签表示菜单已展开,并且在外层显示 ul 对应的菜单列表和圆环。


.menu.active > .menu-dots {
  transform: none;
  box-shadow: 0 0 0 3.4rem #161e3f;
}
.menu-items {
  position: absolute;
  top: -3.4rem;
  left: -3.4rem;
  right: -3.4rem;
  bottom: -3.4rem;
  transition: 0.3s;
  opacity: 0;
  pointer-events: none;
}


为了增加辨识度,这时会取消掉最外层的浅色 box-shadow 阴影。


ul 元素会通过绝对定位的形式,填满整个元素与 box-shadow 覆盖的区域。


需要禁用该元素的鼠标事件,避免无法点击到内部的 label 元素,造成无法收起菜单。并且设置 opacity 为 0,隐藏内部的菜单按钮部分


此时会得到这样一个效果:


网络异常,图片无法展示
|


乍一看,有点像个 iPod 播放器的按钮 ?


4. 每个菜单元素的样式计算


首先,我们先模拟一下菜单数据:


menus: [
  { icon: "el-icon-question", name: "1" },
  { icon: "el-icon-platform-eleme", name: "2" },
  { icon: "el-icon-camera-solid", name: "3" },
  { icon: "el-icon-info", name: "4" },
  { icon: "el-icon-s-shop", name: "5" },
  { icon: "el-icon-message-solid", name: "6" }
]


然后,在 template 中渲染对应的菜单项:


<ul class="menu-items">
  <li v-for="(m, k) in menus" class="menu-item" :style="iconStyle(k)" :key="m.name">
    <i :class="m.icon" />
  </li>
</ul>


随后是样式部分:


.menu.active .menu-items {
  opacity: 1;
}
.menu-item {
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  padding: 0.9rem;
  font-size: 1.6rem;
  filter: blur(2px);
  opacity: 0;
  display: inline-block;
  text-align: center;
  transition: 0.3s;
  pointer-events: none;
  i {
    pointer-events: auto;
    display: inline-block;
    cursor: pointer;
  }
  &:hover {
    color: #5c67ff;
  }
}
.menu.active .menu-items > .menu-item {
  opacity: 1;
  transform: none;
  filter: none;
}


这时得到这样的效果:


网络异常,图片无法展示
|


我们会发现所有的菜单图标都挤在一个位置,并且也无法看到顺序出现的动画。


这时就需要使用 Vue 的计算属性了。我们可以通过计算属性返回一个依赖 menus 菜单数组的函数,接收当前菜单元素下标并返回对应的样式。


export default {
  name: "CircleMenuButton",
  data() {
    return {
      menus: [
        { icon: "el-icon-question", name: "1" },
        { icon: "el-icon-platform-eleme", name: "2" },
        { icon: "el-icon-camera-solid", name: "3" },
        { icon: "el-icon-info", name: "4" },
        { icon: "el-icon-s-shop", name: "5" },
        { icon: "el-icon-message-solid", name: "6" }
      ],
      isActive: false
    };
  },
  computed: {
    iconStyle() {
      const length = this.menus.length;
      const transitionStep = 0.5 / (this.menus.length - 1);
      return (i) => {
        return { transform: `rotate(${Math.floor((i * 360) / length)}deg)`, transitionDelay: `${transitionStep * i}s` };
      };
    }
  }
};


该函数返回一个 transform 属性和一个 transitionDelay 属性。


为了保证形成一个完整的圆环,需要通过 menus 的长度计算每个菜单之间的旋转角度,使用 transform:rotate() 进行旋转;


transitionDelay 则是 transition 动画的延迟时间控制,为了避免菜单过多时导致动画时长太长,可以通过一个总的时间(这里是 0.5s)与菜单长度计算延迟时间单位,保证动画总时长不超过固定时长。


扩展


整个组件的数据都在内部,后续其实也可以通过 props 的形式控制菜单项与动画总时长等;颜色调整也可以使用变量的形式让其更加可控。


内部为了保证每个菜单项都在圆环上,采用了固定数值的定位配置;并且每个 menu-item 的大小都与整体区域大小一致,所以也需要取消他们的 dom 事件,在最内层的 i 标签上恢复事件响应,整个设计上来说个人感觉并不是很完美。也希望大家有好主意的话可以告诉我,后续再对其进行一些优化。


目录
相关文章
|
2天前
|
JavaScript
vue里样式不起作用的方法,可以通过deep穿透的方式
vue里样式不起作用的方法,可以通过deep穿透的方式
14 0
|
21小时前
|
前端开发
CSS优先级:如何解决样式冲突?
CSS优先级:如何解决样式冲突?
|
2天前
|
前端开发 数据安全/隐私保护
利用 HBuilderX 设置CSS样式会员注册页面
利用 HBuilderX 设置CSS样式会员注册页面
9 1
|
2天前
|
前端开发
css设置内嵌样式阴影
css设置内嵌样式阴影
11 0
|
2天前
|
前端开发
css样式实现一个滑动按钮
css样式实现一个滑动按钮
13 0
|
2天前
|
前端开发 UED
CSS 支持伪类和伪元素,可用于指定文档中不同状态的样式
【5月更文挑战第7天】CSS 提供关键帧和过渡动画两种方式创建动态效果。关键帧动画通过定义一系列样式的关键帧,浏览器自动插入过渡帧形成动画,如示例中背景颜色变化的循环。过渡动画则在属性改变时(如鼠标悬停)触发,展示平滑转换,如 div 元素尺寸变化。通过调整帧时间、顺序和样式,可实现更复杂的动画,增强网站交互体验。
30 4
|
2天前
|
前端开发 JavaScript 开发者
【专栏:HTML与CSS前端技术趋势篇】前端框架(React/Vue/Angular)与HTML/CSS的结合使用
【4月更文挑战第30天】前端框架React、Vue和Angular助力UI开发,通过组件化、状态管理和虚拟DOM提升效率。这些框架与HTML/CSS结合,使用模板语法、样式管理及组件化思想。未来趋势包括框架简化、Web组件标准采用和CSS在框架中角色的演变。开发者需紧跟技术发展,掌握新工具,提升开发效能。
|
2天前
|
编解码 前端开发 UED
【专栏:HTML 与 CSS 移动端开发篇】CSS 媒体查询与移动端特定样式
【4月更文挑战第30天】CSS媒体查询在移动端开发中至关重要,它基于设备特性(如屏幕尺寸、分辨率、方向)应用特定样式,实现响应式设计。通过`@media`规则定义条件,如`(max-width: 600px)`,当屏幕宽度小于或等于600px时应用相应样式。常见条件包括屏幕宽度、高度、方向和分辨率。媒体查询可用于响应式布局、导航菜单优化、图片加载及字体调整。在实践中,需注意保持查询简洁,充分测试,渐进增强,并提前规划。掌握媒体查询能提升移动端用户体验,创造更优秀的网页设计。
|
2天前
|
开发框架 前端开发 搜索推荐
标题:【专栏:CSS进阶篇】CSS样式重置与框架:快速构建统一风格的网页
【4月更文挑战第30天】本文探讨了CSS样式重置和框架在确保网页跨浏览器一致性中的作用。样式重置通过消除默认样式差异实现一致外观,而CSS框架如Bootstrap提供预设样式和组件,加速开发并保证页面一致性。框架还有响应式设计和易于维护的优点,但也可能限制自定义和增加性能开销。选择使用哪种工具应根据项目需求、团队技能和设计复杂度来决定。开发者可结合使用两者以平衡灵活性和控制。
|
2天前
|
前端开发 UED
【专栏:CSS 基础篇】CSS 字体与文本样式:美化你的网页内容
【4月更文挑战第30天】网页设计中,字体和文本样式至关重要,影响视觉效果和用户体验。CSS允许设计师设置字体家族、大小、颜色、加粗、倾斜、行高和对齐方式等。高级特性包括引入外部字体和使用字体变体。响应式设计适应不同设备,确保良好阅读体验。实际案例和最佳实践强调易读性和一致性。掌握这些技巧能提升网页美感和用户交互,创造更多可能。