likes
comments
collection
share

微信小程序实现圆形菜单弹出选中动画

作者站长头像
站长
· 阅读数 12

背景

👏前几天在某短视频平台刷到一个类似的效果,觉得蛮好玩的,将它应用到你的小程序吧~
文末分享源代码。记得点赞+关注+收藏!

1.实现效果

微信小程序实现圆形菜单弹出选中动画

2.实现原理☕️

2.1 引入阿里巴巴矢量图标

可参考文章:微信小程序引入外部icon(阿里巴巴矢量图标)

注:生成彩色的图标,需在项目设置中勾选彩色哦~

微信小程序实现圆形菜单弹出选中动画

2.2 圆环菜单布局

  • 将菜单基于圆环布局,假设此圆只有一个菜单,让它定位到下图黄色菜单所示位置

注意:transform执行顺序从最外面字段开始~

微信小程序实现圆形菜单弹出选中动画

微信小程序实现圆形菜单弹出选中动画

  • rotate角度的获取:一个圆为360deg,将其等分,2个菜单偏移角度为180deg,4个菜单偏移角度90deg,以此类推可以得到:

rotate角度为: 360deog/菜单的个数

微信小程序实现圆形菜单弹出选中动画

  • rotate旋转带动子元素图标的转动,将子元素设置反方向的转动

微信小程序实现圆形菜单弹出选中动画

2.3 CSS3 2D 转换+transaction

CSS3 transition 属性

transition 属性设置元素当过渡效果。注:始终指定transition-duration属性,否则持续时间为0,transition不会有任何效果。

CSS3 2D 转换:

函数描述
translateX(n)定义 2D 转换,沿着 X 轴移动元素。
scale(x,y)定义 2D 缩放转换,改变元素的宽度和高度。

3.实现步骤🔔

  • 定义菜单显示与否字段show_menu(默认false),当前所选菜单索引currIndex(默认' ')

  • 画一个盒子包括以下内容,默认设置属性为:

opacity: 0;
transform: scale(0);
visibility: hidden;
transition: all 0.5s;

微信小程序实现圆形菜单弹出选中动画

  • 画一个盒子元素,里面放一个可爱的图标,盒子设置伪元素 "点我吧",当show_menu为true,为该盒子添加激活样式,即伪元素opacity设置为0

微信小程序实现圆形菜单弹出选中动画

  • 当show_menu为true,设置圆形菜单scale(1);opacity:1; visibility: visible;计算菜单个数,设置rotate值,并调整合适的translateX偏移

  • rotate角度根据菜单个数定

微信小程序实现圆形菜单弹出选中动画

  • 偏移量设置

微信小程序实现圆形菜单弹出选中动画

--n:{{index}};//index为菜单当前索引
--deg:{{360/ menu.length}}deg;//menu为菜单列表
transform: rotate(calc(var(--deg) * var(--n))) translateX(-140rpx);
  • 设置选中菜单样式

微信小程序实现圆形菜单弹出选中动画

  • 当currIndex===index,选中菜单背景,基于圆形absolute垂直居中定位,设置rotate+translateX,rotate角度为-deg*currIndex相关,translateX设置偏移量向外扩大一丢丢即可
<view class="menu-box-active" style="--n:{{currIndex}};--deg:{{360/ menu.length}}deg"</view>
 transform: rotate(calc(var(--deg) * var(--n))) translateX(-200rpx);
 opacity: 1;
 visibility: visible;
  • 当currIndex===index,选中菜单,translateX设置偏移量向外扩大一丢丢即可,为图标并添加scale缩放效果
 <view style="--n:{{index}};--deg:{{360/ menu.length}}deg" class="menu-box-item {{currIndex===index && 'active'}}" catchtap="clickActive" data-index="{{index}}">
  <icon class="iconfont icon {{item.icon}}" style="--deg:{{-360/ menu.length}}deg"></icon>
 </view>
.menu-box-item.active {
  transform: rotate(calc(var(--deg) * var(--n))) translateX(-200rpx);
}
.menu-box-item.active icon {
  animation: scale 1s ease-in-out;
}
@keyframes scale {
  100% {
    transform: scale(1.9);
  }
}

4.实现代码

<view class="container">
  <view class="menu-box {{show_menu && 'active'}}">
    <block wx:for="{{menu}}" wx:key="menu">
      <view style="--n:{{index}};--deg:{{360/ menu.length}}deg" class="menu-box-item {{currIndex===index && 'active'}}" catchtap="clickActive" data-index="{{index}}">
        <icon class="iconfont icon {{item.icon}}" style="--deg:{{-360/ menu.length}}deg"></icon>
      </view>
    </block>
    <view class="menu-box-active" style="--n:{{currIndex}};--deg:{{360/ menu.length}}deg"></view>
  </view>
  <view class="menu-add-box {{show_menu && 'active'}}" catchtap="showMenu">
    <icon class="iconfont icon {{menu_add}}"></icon>
  </view>
</view>
@import "../utils/icon-font.wxss";
page {
  --bg: pink;
  background: var(--bg);
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  --active: orange;
  --bgcolor: rgb(190, 127, 67);
}
/* 圆形菜单 */
.container {
  width: 400rpx;
  height: 400rpx;
  position: relative;
}
.menu-box {
  width: 400rpx;
  height: 400rpx;
  position: relative;
  opacity: 0;
  transform: scale(0);
  visibility: hidden;
  transition: all 0.5s;
}

.menu-box.active {
  transform: scale(1);
  opacity: 1;
  visibility: visible;
}
.icon {
  font-size: 56rpx;
  transition: all 0.5s;
}
/* 点我吧 */
.menu-add-box {
  width: 120rpx;
  height: 120rpx;
  border-radius: 50%;
  display: flex;
  align-items: center;
  justify-content: center;
  transition: all 0.5s;
  position: absolute;
  top: calc(50% - 60rpx);
  left: calc(50% - 60rpx);
  background: var(--bg);
}
.menu-add-box::after {
  content: '^点我吧^';
  position: absolute;
  bottom: -20rpx;
  font-size: 22rpx;
  color: #222;
  font-weight: bold;
  font-style: italic;
  transition: all .5s;
  opacity: 1;
}
.menu-add-box.active::after {
  font-size: 0rpx;
  opacity: 0;
}
.menu-add-box icon {
  transform: scale(1.5);
}
.menu-add-box.active icon {
  transform: rotate(135deg);
}
/* 每一项菜单 */
.menu-box-item {
  width: 80rpx;
  height: 80rpx;
  position: absolute;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  top: calc(50% - 40rpx);
  left: calc(50% - 40rpx);
  transition: all 0.5s;
  transform: rotate(calc(var(--deg) * var(--n))) translateX(-140rpx);
}
.menu-box-item icon {
  transform: rotate(calc(var(--deg) * var(--n)));
  transition: all 0.5s;
}
.menu-box-item.active {
  transform: rotate(calc(var(--deg) * var(--n))) translateX(-200rpx);
}
.menu-box-item.active icon {
  animation: scale 1s ease-in-out;
}
@keyframes scale {
  100% {
    transform: scale(1.9);
  }
}
/* 选中的背景框 */
.menu-box-active {
  position: absolute;
  width: 100rpx;
  height: 100rpx;
  background: var(--active);
  box-shadow: 0 0 0 10rpx var(--bg);
  border-radius: 50%;
  pointer-events: none;
  transition: all 0.5s;
  transform-origin: center;
  top: calc(50% - 50rpx);
  left: calc(50% - 50rpx);
  z-index: 1;
  opacity: 0;
  visibility: hidden;
}
.menu-box-item.active~.menu-box-active {
  transform: rotate(calc(var(--deg) * var(--n))) translateX(-200rpx);
  opacity: 1;
  visibility: visible;
}
/*filter(滤镜)修饰父元素背景,影响子元素问题解决 */
.menu-box::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 100%;
  height: 100%;
  background: var(--bgcolor);
  border-radius: 50%;
  filter: drop-shadow(0px 0px 8px var(--active));
  -webkit-filter: drop-shadow(0px 0px 8px var(--active));
}


Page({
  data: {
    menu_add: "icon-susumakalong",
    menu: [
      {
        icon: "icon-susushutiao",
        name: "薯条",
      },
      {
        icon: "icon-susutiantong",
        name: "甜筒",
      },
      {
        icon: "icon-susutongluoshao",
        name: "铜锣烧",
      },
      {
        icon: "icon-susuxiaoxiongruantang-46",
        name: "小熊软糖",
      },
      {
        icon: "icon-susushiwutubiao-49",
        name: "提拉米苏",
      },
      {
        icon: "icon-susunailao",
        name: "奶酪",
      },
      {
        icon: "icon-susuhanbao",
        name: "汉堡",
      },
      {
        icon: "icon-susupaofu-53",
        name: "泡芙",
      },
    ],
    show_menu: false,
    currIndex: "",
  },
  showMenu() {
    let { show_menu } = this.data;
    this.setData({
      show_menu: !show_menu,
      currIndex: "",
    });
  },
  clickActive(e) {
    let { index } = e.currentTarget.dataset;
    if (this.data.currIndex === index || index === undefined) return false;
    this.setData({
      currIndex: index,
    });
  },
});

5.写在最后

看完本文如果觉得有用,记得点赞+关注+收藏鸭
更多小程序相关,关注💯苏苏的bug,💢苏苏的github,😎苏苏的码云~

参考链接:

  1. 微信小程序引入外部icon(阿里巴巴矢量图标)
  2. CSS3 transition 属性
  3. CSS3 2D 转换