微信小程序仿快播标签词云滚动特效加强3D版
前两天在另一个开发者平台发布过小程序快播首页标签云的特效代码(感谢先人们给的思路)。后来给产品看,产品表示要是能跟球一样就好了,我黑人问号???球?让产品把这个球说得具体点的我好实现呀,然后就开始了balabala...反正到最后我也没怎么听懂他在说啥哈哈哈,在我理解就是字体从小到大,再从下到上,然后再从大到小,再绕个圈圈过来滚啊滚~
行,感觉也挺有意思的,于是乎,本着先做出来再说就开工。一开始想到的当然是用CSS3强大的动画效果animation啦。但敲过快播标签云的开发就尝试过,CSS3可以是可以,就其中太多的细节要自己把握好,写起来感觉也挺麻烦的。(对快播标签云效果感兴趣的移步到我另一个小黑屋里哟:https://juejin.im/post/5cc120615188252e803d2f26)
所以,还是本着JS为王的思想,依旧是通过计算各种Cos,Sin,PI等细微值不断更改top/left/opacity/z-index来改变位置和透明度,但要让人感觉有3D效果,那就必须也把font-size也改了来做伪3D。
先上个效果图开开胃:(用的QQ切出来的5秒GIF,可能在这里显示有点卡。在小程序M4真机测试是不卡的~)
看完效果图,说个我觉得要说的点,虽说改字体大小吧,如果改的大小数值差距太大,就导致有种卡住的错觉。而且在小程序里面,通过JS赋值rpx到页面后最终还是转换成px单位的,而px又喜欢四舍五入简单粗暴,那就变成不是+1就是-1。想了想,后面才用的rem
然后又想到能不能随时换个方向呢,比如我看烦了从下到上,想来个螺旋丸貌似也可以做到。所以就再加个bindtouchmove来监听用户动作创造不一样的Style~
开胃菜差不多消化完了吧?那我就直接上主菜啦(主要注释都在里面,可以直接复制到小程序demo项目运行)
WXML:
<view class="container"> <view class="wrapper" hidden="{{tagState}}" bindtouchmove="handletouchmove" bindtouchstart="handletouchstart"> <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:
/**index.wxss**/ page { width: 100%; height: auto; } .wrapper { width: 700rpx; margin: 0 auto; position: relative; top: 0; left: 0; display: inline-block; min-height: 500rpx; background-color: #000000; } .wrapper view { position: absolute; top: 60%; left: 40%; display: inline-block; padding: 11rpx 30rpx; color: #333; font-size: 20rpx; border: 1rpx solid #e6e7e8; border-radius: 18rpx; background-color: #f2f4f8; text-decoration: none; }
JS:
Page({ data: { tagEle: [], // 标签标题数据 tagState: true, // 是否显示标签云 countTime: null, // 计算定时器 lastX: 0, // 坐标X lastY: 0, // 坐标Y direction: 310, // 初始化标签词云角度,默认左上角 },
handletouchmove: function (event) { let currentX = event.touches[0].pageX // 获得X轴坐标 let currentY = event.touches[0].pageY // 获得Y轴坐标 let tx = currentX - this.data.lastX // 计算X轴偏差值 let ty = currentY - this.data.lastY // 计算Y轴偏差值 // 上下左右方向滑动 if (tx === 0) { // 上下方向 if (ty < 0) { // 上滑动 this.data.direction = 360 } else if (ty > 0) { // 下滑动 this.data.direction = 180 } } else if (ty === 0) { // 左右方向 if (tx < 0) { // 左滑动 this.data.direction = 270 } else if (tx > 0) { // 右滑动 this.data.direction = 90 } } else if (tx < 0 && ty < 0) { // 左上滑动 this.data.direction = 315 } else if (tx < 0 && ty > 0) { // 左下滑动 this.data.direction = 225 } else if (tx > 0 && ty < 0) { // 右上滑动 this.data.direction = 45 } else if (tx > 0 && ty > 0) { // 右下滑动 this.data.direction = 135 } // 将当前坐标进行保存以进行下一次计算 this.data.lastX = currentX this.data.lastY = currentY }, handletouchstart: function (event) { this.data.lastX = event.touches[0].pageX // 获得触摸点X轴坐标 this.data.lastY = event.touches[0].pageY // 获得触摸点Y轴坐标 }, // 初始化标签云特效 initialize(data) { const that = this let countList = [] // 计算列表数据集合 let radius = 150 // 初始化滚动半径作用区域 let tagEle = data // 标题元素数组 this.setData({ // 首次赋值给到页面用于后续获取高宽值 tagEle: tagEle }) // 首次循环获取所有元素高宽值并计算得出首次计算列表数据 for (let i = 0; i < tagEle.length; i++) { let query = wx.createSelectorQuery() // 小程序API获得组件对象 query.select(`#tag${i}`).boundingClientRect(rect => { // 使用选择器获得每个id元素的高宽值 let acos = Math.acos(-1 + (2 * i + 1) / tagEle.length) // 计算反余弦 let sqrt = Math.sqrt((tagEle.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 }) }, 300) }, // Style样式计算过程 calculation(tagData, countData, num) { let countList = countData // 计算结果数组 const radius = num // 滚动区域范围,默认单位为px,数值越大滚动范围越大 let fontsize = 14 // 字体大小,默认单位为px,后期转换rem。数值越大字体越大 let depth = 2 * radius // 滚动深度 let ispeed = 15 // 滚动速度,数值越大滚动速度越快,不能小于2 let direction = this.data.direction // 滚动方向, 取值角度(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.7 // 计算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 + (450 - countList[i].offsetHeight) / 2 + "rpx", // 440越大,则距离上边越远 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 (options) { let tagEle = [ // 标题元素数组 { title: '我是007' }, { title: 'Web前端' }, { title: 'JavaScript 教程' }, { title: '微信小程序' }, { title: '爱上大树的小猪' }, { title: '王者荣耀' }, { title: '伪程序猿' }, { title: '老人与代码' }, { title: '虚拟DOM' }, { title: 'CSS3不服' }, { title: 'Animation的锅' }, { title: '感谢先人踩的坑' }, { title: '后人接着挖点坑' }, { title: '博客园' }, { title: '复仇者联盟' } ] this.initialize(tagEle) // 调用标签云特效 }, onUnload() { clearInterval(this.countTime) // 清除计算定时器 this.countTime = null // 清除计算定时器 }, });
以上就是伪3D版的标签词云滚动特效全部代码。看官们满意的评个论点个推荐呗。有可以改进的地方也请提出来,互相学习再接再厉共抗秃头~
END