一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第6天,点击查看活动详情。
前段时间,产品经理看了别人的小程序提了个新需求,要求小程序里实现一个轮盘效果。要轮盘滚动
参考
我一看好家伙这不是swiper么,但又不全是swiper,毕竟swiper不能弧线滑动。
最终效果
一番思索时间得出一套可实现方案 原理如图
原理
初始版本
初始版本实线步骤
- 创建一个大的元素(图中的黑色实线)
- 按照大的元素的中心 创建数个小的元素 (途中灰色虚线,为了好解释没有画完,以此按照角度画到头)
- 在页面上加入swiper组件,绝对定位在最上层。
有了正确思路就能够开工了。 但是按照上面的图可以发现,虽然达到了轮转的效果,但是不够美观,页面上只会有一个元素,没法展示的更多。按照产品的需求还需要展示文字,一个页面上需要展示5个标题。于是有了下面的版本
1.0版本
可以看到文字代表的蓝色(实际上会依次按角度旋转到头,自行脑补)和图片代表的灰色不是同一个元素表示。
于是我把360度分成了4份,每份90度。在这90度中,放入一组完整的标题数组。然后4份就刚好凑成完整的圆形。
1.0版本实线步骤
- 创建一个大的元素(图中的黑色实线)
- 按照大的元素的中心 创建数个小的元素 (途中灰色虚线,为了好解释没有画完,以此按照角度画到头) 2.1 按照一定的角度
- 在页面上加入swiper组件,绝对定位在最上层。
按照这个思路,就可以写出具体实现方法
具体实现
1.创建一个容器元素(黑色实线部分)
我们都知道小程序的宽是 750rpx,所以需要将这个容器计算放到屏幕的中心
建议先定义为750 可以看到整体效果,后续再放大
2.创建灰色虚线和块的元素
这部分是图片的元素容器,页面上只会显示一个图片,所以可以将图片隔的比较开,节省渲染性能
我是用360度除图片数组的长度。 然后根据 index 按照角度显示到页面上。
2.1 创建蓝色虚线块
这部分是文字的元素容器,页面上会显示5个文字,所以要将360度分为4份,每份90度(自己定义份数、度数 相乘360度),然后根据 index * (90/num)按照角度显示到页面上。
上面两部的核心代码
_setBanner() {
// num代表服务器返回数组的长度
const num = this.data.num;
// 先创建 bannerNullArr 表示 将360度分为多少份 比如4份
const bannerNullArr = Array(BANNER_CONFIG.ROUND_GROUP_NUM * num).fill("");
// 组装数据,将每份的角度计算出来
// 角度为一份(比如90度) 90/数组长度
const banner = bannerNullArr.map((el, i) => {
return {
rotate: i * (BANNER_CONFIG.ONE_GROUP_DEG / num),
...this.data.originBanner[i % num],
};
});
// 图片长度的空数组
const imgs = Array(num).fill("");
// 图片计算角度
const bannerImg = imgs.map((el, i) => {
return {
rotate: i * (360 / num),
...this.data.originBanner[i % num],
};
});
this.setData({
banner,
bannerImg,
});
},
3 实现swiper与显示元素滚动的联动
这里实现的是swiper与轮盘的联动
利用了swiper的transition方法,来联动swiper和滚动的角度
transition(e) {
// 先计算滑动相对于总路程的进度
const progress = this._getProcess(e);
// 计算文字滚动的角度
const textdeg = progress * (360 / BANNER_CONFIG.ROUND_GROUP_NUM); //每60度为份 共6份
// 计算图片滚动的角度
const imgDeg = progress * 1 * 360; //每360度为份 共1份
const rotate = `transform: rotate(${-textdeg}deg)`;
const rotateImg = `transform: rotate(${-imgDeg}deg)`;
this.setData({
rotate,
rotateImg,
animationfinish: false,
});
},
_getProcess(e) {
const num = this.data.num;
const current = this.data.oldCurrent || this.data.current;
let dx = e.detail.dx;
// dx 表示走到页面宽度的距离
// 宽度需要通过 wx.getSystemInfoSync().windowWidth 获取
dx = Math.ceil(dx);
return ((dx / this.data.windowWidth + current) / num).toFixed(6);
},
核心代码
wxml
<view class="box">
<!-- 用户滑动的区域 -->
<swiper
current="{{current}}"
class="swiper"
indicator-dots="{{false}}"
autoplay="{{false}}"
circular="{{true}}"
interval="2000"
duration="500"
bindtransition="transition"
bindanimationfinish="animationfinish"
>
<block wx:for="{{banner}}" wx:key="index">
<swiper-item>
<view class="swiper-item" data-item="{{item}}" bind:tap="chooseBanner" />
<view class="swiper-item-tabs">
<view
wx:if="{{!oldCurrent}}"
wx:for="{{showIndex}}"
id="{{item}}"
bind:tap="jumpSwiper"
class="swiper-item-tabs"
wx:key="index"
/>
</view>
</swiper-item>
</block>
</swiper>
<view class="rotateBoxPort">
<view style="{{rotate}}" class="rotateBox">
<view
class="innerBox"
wx:for="{{banner}}"
wx:key="index"
style="transform: rotate({{item.rotate}}deg)"
>
<view class="rotateBoxItem">
<view class="rotateBoxItemText">
<!-- <view class="rotateBoxItemLine" style="left:{{BANNER_CONFIG.left}};transform: rotate({{BANNER_CONFIG.deg}}deg)" /> -->
<view class="{{current === index?'rotateBoxItemTextActive':''}}">
{{item.title}}
</view>
</view>
<view wx:if="{{current === index}}" style="font-size:20rpx">
{{item.englishTitle}}
</view>
</view>
</view>
</view>
</view>
<view class="rotateImgsBoxPort">
<view style="{{rotateImg}}" class="rotateImgsBox">
<view
class="innerBox"
wx:for="{{bannerImg}}"
wx:key="index"
style="transform: rotate({{item.rotate}}deg)"
>
<image class="rotateBoxItemImg" src="{{item.imgUrl}}" />
</view>
</view>
</view>
</view>
当我实线到这个步骤后,产品告诉我还要点击文字滚动到对应的图片,也就是要跳过某些元素。还有可能后台返回的数据不是一定长度的。要针对多种情况都在页面完美的显示5个标题。
下次再讲下实现
总结
- 利用
swiper的transition方法来联动轮盘的滚动 - 按照区域划分360度,计算出每个文字的角度
- 按照360度划分图片的角度