前两天在另一个开发者平台发布过小程序快播首页标签云的特效代码(感谢先人们给的思路)。后来给产品看,产品表示要是能跟球一样就好了,我黑人问号???球?让产品把这个球说得具体点的我好实现呀,然后就开始了balabala...反正到最后我也没怎么听懂他在说啥哈哈哈,在我理解就是字体从小到大,再从下到上,然后再从大到小,再绕个圈圈过来滚啊滚~

  行,感觉也挺有意思的,于是乎,本着先做出来再说就开工。一开始想到的当然是用CSS3强大的动画效果animation啦。但敲过快播标签云的开发就尝试过,CSS3可以是可以,就其中太多的细节要自己把握好,写起来感觉也挺麻烦的。(对快播标签云效果感兴趣的移步到我另一个小黑屋里哟:https://juejin.im/post/5cc120615188252e803d2f26

SRE实战 互联网时代守护先锋,助力企业售后服务体系运筹帷幄!一键直达领取阿里云限量特价优惠。

  所以,还是本着JS为王的思想,依旧是通过计算各种Cos,Sin,PI等细微值不断更改top/left/opacity/z-index来改变位置和透明度,但要让人感觉有3D效果,那就必须也把font-size也改了来做伪3D。

  先上个效果图开开胃:(用的QQ切出来的5秒GIF,可能在这里显示有点卡。在小程序M4真机测试是不卡的~)

                           微信小程序仿快播标签云滚动特效加强3D版 随笔

 

  看完效果图,说个我觉得要说的点,虽说改字体大小吧,如果改的大小数值差距太大,就导致有种卡住的错觉。而且在小程序里面,通过JS赋值rpx到页面后最终还是转换成px单位的,而px又喜欢四舍五入简单粗暴,那就变成不是+1就是-1。想了想,后面才用的rem

  开胃菜差不多消化完了吧?那我就直接上主菜啦(主要注释都在里面,可以直接复制到小程序demo项目运行)

WXML:

<!--index.wxml-->
<view class="container">
  <view class="wrapper" hidden="{{tagState}}">
    <view wx:for="{{tagEle}}" wx:key="{{key}}" wx:index="{{index}}" id="tag{{index}}"
      style="opacity:{{item.opacity}};top: {{item.top}};left: {{item.left}}; z-index: {{item.zIndex}};font-size: {{item.fontSize}}">
      {{item.title}}</view>
  </view>
</view>

WXSS:

.wrapper {
  width: 650rpx;
  margin: 0 auto;
  position: relative;
  top: 0;
  left: 0;
  margin-top: 100rpx;
}

.wrapper view {
  position: absolute;
  top: 60%;
  left: 40%;
  display: inline-block;
  padding: 11rpx 30rpx;
  color: #333;
  font-size: 16rpx;
  font-weight: bold;
  border: 1rpx solid #e6e7e8;
  border-radius: 18rpx;
  background-color: #f2f4f8;
  text-decoration: none;
}

JS:

Page({
  data: {
    tagEle: [], // 标签标题数据
    tagState: true, // 是否显示标签云
    countTime: null, // 计算定时器
  },

  // Style样式计算过程
  calculation(tagData, countData, num) {
    let countList = countData // 计算结果数组
    const radius = num // 滚动区域范围,默认单位为px,数值越大滚动范围越大
    let fontsize = 15 // 字体大小,默认单位为px,后期转换rem。数值越大字体越大
    let depth = 2 * radius // 滚动深度
    let ispeed = 16 // 滚动速度,数值越大滚动速度越快,不能小于2
    let direction = 360 // 滚动方向, 取值角度(0-360): 0和360对应即从下到上, 90对应垂直X-Y,180对应从上倒下,其他数值随意测试...
    let directionX = ispeed * Math.sin(direction * Math.PI / 180) // 计算X轴Sin值
    let directioneY = -ispeed * Math.cos(direction * Math.PI / 180) // 计算Y轴Cos值
    let a = -(Math.min(Math.max(-directioneY, -radius), radius) / radius) * ispeed // 计算a值用以后续判断计算
    let b = (Math.min(Math.max(-directionX, -radius), radius) / radius) * ispeed // 计算b值用以后续判断计算
    let dtr = Math.PI / 180 // 计算圆周率
    let PIList = [ // 计算圆周率数组
      Math.sin(a * dtr),
      Math.cos(a * dtr),
      Math.sin(b * dtr),
      Math.cos(b * dtr)
    ]
    // 若ab值太小,即相关配置如速度/范围等太低,直接return不执行动效
    if (Math.abs(a) <= 0.01 && Math.abs(b) <= 0.01) { return; }
    // 循环遍历每个元素前面所计算出来的各值
    for (let j = 0; j < countList.length; j++) {
      let rz1 = countList[j].top * PIList[0] + countList[j].z * PIList[1] // 计算前置数据
      let rz2 = rz1 * PIList[3] - countList[j].left * PIList[2] // 计算前置数据
      let per = depth / (depth + rz2) // 计算前置数据

      countList[j].left = countList[j].left * PIList[3] + rz1 * PIList[2] // 计算left用以后面计算赋值left
      countList[j].top = countList[j].top * PIList[1] + countList[j].z * (-PIList[0]) // 计算top用以后面计算赋值top
      countList[j].z = rz2 // 赋值计算列表中Z值新数据
      countList[j].fontsize = (per * 2 + fontsize) / 30 // 计算fontsize用以后面计算赋值font-size。注:最后除以30是用以后续rem单位计算,具体rem单位计算可参照官方计算。
      countList[j].alpha = 1.5 * per - 0.5 // 计算alpha用以后面计算赋值opacity
      countList[j].zIndex = Math.ceil(per * 10 - 5) // 计算zIndex用以后面计算赋值z-index
    }

    this.voluation(tagData, countList)
  },

  // Style样式赋值运算
  voluation(tagData, countData) {
    const tagEle = tagData
    const countList = countData
    let styleList = [] // 存储完整渲染列表的文字和样式结构
    for (let i = 0; i < countList.length; i++) {
      styleList.push({
        title: tagEle[i].title, // 标题文字内容
        left: countList[i].left + (500 - countList[i].offsetWidth) / 2 + "rpx",  // 500越大,则距离左边越远 
        top: countList[i].top + (240 - countList[i].offsetHeight) / 2 + "rpx", // 240越大,则距离上边越远
        zIndex: countList[i].zIndex, // z-index值
        opacity: countList[i].alpha,  // opacity值
        fontSize: countList[i].fontsize + "rem" // font-size值。注:不采用rpx值是因为在小程序最后会被改为四舍五入后的px值,不支持小数点单位,在放大缩小中不是很美观。于是采用转换rem值。
      })
    }
    this.setData({ // 赋值给到页面渲染
      tagEle: styleList
    })
  },

  onLoad: function () {
    const that = this
    let countList = [] // 计算列表数据集合
    let radius = 130 // 初始化滚动半径作用区域
    let tagEle = [ // 标题元素数组
      { title: '我是007' }, { title: 'Web前端' }, { title: 'JavaScript 教程' }, { title: '微信小程序' }, { title: '爱上大树的小猪' }, { title: '王者荣耀' }, { title: '伪程序猿' }, { title: '老人与代码' }, { title: '虚拟DOM' }, { title: 'CSS3不服' }, { title: 'Animation的锅' }, { title: '感谢先人踩的坑' }, { title: '后人接着挖点坑' }, { title: '博客园' }, { title: '复仇者联盟' }
    ]
    this.setData({ // 首次赋值给到页面用于后续获取高宽值
      tagEle: tagEle
    })
    const length = tagEle.length // 标题数组长度

    // 首次循环获取所有元素高宽值并计算得出首次计算列表数据
    for (let i = 0; i < length; i++) {
      let query = wx.createSelectorQuery() // 小程序API获得组件对象
      query.select(`#tag${i}`).boundingClientRect(rect => { // 使用选择器获得每个id元素的高宽值
        let acos = Math.acos(-1 + (2 * i + 1) / length) // 计算反余弦 
        let sqrt = Math.sqrt((length + 1) * Math.PI) * acos // 计算平方根
        countList.push({
          offsetWidth: rect.width, // 当前id元素的宽度
          offsetHeight: rect.height, // 当前id元素的高度
          left: radius * 1.5 * Math.cos(sqrt) * Math.sin(acos), // 当前id元素的left值
          top: radius * 1.5 * Math.sin(sqrt) * Math.sin(acos), // 当前id元素的top值
          z: radius * 1.5 * Math.cos(acos), // 计算Z轴值
        })
      }).exec()
    }

    // 下列为主要运算赋值程序,定时器是由于小程序API获取高宽的异步执行,这里暂时没改为同步。即用定时器来做延时运行。
    setTimeout(() => {
      that.countTime = setInterval(() => {
        this.calculation(tagEle, countList, radius) // 调用计算函数
      }, 50) // 每50毫秒执行一次,考虑性能消耗问题,不建议更改时间,要控制速度更改ispeed值
      this.setData({
        tagState: false
      })
    }, 500)
  },

  onUnload() {
    clearInterval(this.countTime) // 清除计算定时器
    this.countTime = null // 清除计算定时器
  },
});

 

以上就是伪3D版的标签云滚动特效全部代码。看官们满意的评个论点个推荐呗。有可以改进的地方也请提出来,互相学习再接再厉共抗秃头~

END

扫码关注我们
微信号:SRE实战
拒绝背锅 运筹帷幄