好久没来了。
架构组本身已经挺多事了,手头上还有个picker的组件没做完善,但是隔壁运营活动组几十个前端忙不过来,产品和场景一口气出了十来个项目,基本都是拉新、节日、社交、小游戏、签到等互动类的活动, 应公司建设需要,领导亲自跟我说不当紧的事可以放一放,去帮同事一把,他跟上面说好了,于是乎我就到了运营活动的小程序组了(运营活动这边分了小程序端和HTML5端),说实话小程序上手虽然快,但是很久没用了,小程序端的那个妹子人也好,让我看几个页面,几个组件,都是比较小的那种,以上为背景
有一个连续滚动的的跑马灯组件,暂且称呼marquee组件吧,跟我维护的组件库中的marquee组件基本一样,但是那个是基于animation.transition实现的,调用的GPU渲染动画,肯定是比这个使用js直接修改位置调用CPU渲染的性能要好
不扯了,看效果,上干货,设计图长以下这样
marquee组件相关代码:
marquee.json
{
"component": true,
"usingComponents": {}
}
marquee.wxml
<view class='marquee_container'>
<view id="marquee_wrap" class='marquee_text' style='{{ orientation }}:{{ leftDistance }}px;font-size: {{size}}px;'>
<view class="item" wx:for="{{text}}" wx:for-index="key" wx:for-item="item" wx:key="{{index}}">
{{item.getAwardsUser}}抽中{{item.name}}
</view>
</view>
</view>
marquee.wxss
.marquee_container {
position: relative;
width: 100%;
margin-top: 80rpx;
}
.marquee_text {
position: absolute;
white-space: nowrap;
}
.item{
display: inline;
margin-left:22rpx;
margin-right:22rpx;
padding: 14rpx 40rpx;
line-height: 52rpx;
/* opacity: 0.43; */
/* background: #E1D3C2; 手动将背景色转为rgba色,因为ui上的色是16进制色和透明度,但是单独写opacity会影响字体颜色*/
background: rgba(241, 211, 194, 0.43);
color: #E83C00;
border-radius: 52rpx;
}
/* 解决文本首尾的外边距 */
.item:first-child{
margin-left:0;
}
.item:last-child{
margin-right:0;
}
marquee.js
Component({
/**
* 组件的属性列表
*/
properties: {
text: {
type: Array,
value: [],
observer(newVal, oldVal) {
if(newVal) {
this.textScrolling();
this.dynamicLength()
}
}
}
},
//防止定时器叠加,在组件生命周期清空定时器
pageLifetimes: {
hide: function () {
let { timer } = this.data
console.log("timer0", timer)
clearInterval(timer);
console.log("timer1", timer)
}
},
/**
* 组件的初始数据
*/
data: {
scrollPace: 0.8,
leftDistance: 375,
size: 12,
orientation: 'left',
interval: 25,
windowWidth:750,
length:0
},
created: function() {
// var length = _this.data.text.length * _this.data.size;
// var windowWidth = wx.getSystemInfoSync().windowWidth
// _this.setData({
// length: length,
// // windowWidth: windowWidth
// });
// _this.textScrolling();
},
//生命周期钩子函数
pageLifetimes: {
hide: function () {
let { timer } = this.data
console.log("timer0", timer)
clearInterval(timer);
console.log("timer1", timer)
}
},
methods: {
textScrolling: function() {
// return;
var _this = this;
const { interval } = this.data
var timer = setInterval(()=> {
if(-_this.data.leftDistance < _this.data.length) {
_this.setData({
leftDistance: _this.data.leftDistance - _this.data.scrollPace
})
} else {
clearInterval(timer);
_this.setData({
// leftDistance: _this.data.windowWidth
leftDistance: 375,
});
_this.textScrolling();
}
},interval);
},
dynamicLength(){
var _this = this
//选择器
const query = wx.createSelectorQuery().in(this)
query.select('#marquee_wrap').boundingClientRect(function (rect) {
console.log("rect", rect)
_this.setData({
length:rect.width
})
}).exec();
}
}
})
开始我只是把宽度处理写在created里面的,但是最后页面端说会在页面渲染后多次更改其内容,所以单独写了个dynamicLength方法,在observe里面执行
页面使用直接在目标页面 json文件引入,然后以标签的方式使用即可,效果如下:
<marquee text="{{testText}}"></marquee>
testText:[
{name:'一台冰箱', getAwardsUser:'wilson'},
... ...
]
实现思路:
设定整个文本的长度为length(先可以随便给个,再获取真实宽度),按照定位的方式把整一串的文本从left:windowWith(这里的windowWith为375也就是一屏的宽度)逐渐调整为left:-length,当left为-length时,将left:设为windowWith,循环递归即可
ps:有几点需要注意的
-
组件的结构中最里面一层渲染文本要使用view标签,使用text会出现莫名其妙的外边距,而且display:inline对text标签是无效的(会同时把所有的弹幕内容一起滚出来)
-
防止在跳转其他页面再次进来时定时器叠加,即滚动变快,所以需要在组件的生命周期里清掉定时器
-
scrollPace和interval的设定,即速度和频率的设定,scrollPace不宜过大,可以将interval设置了再调整scrollPace,interval设定范围为16-30之间即可,恰好执行的效果为30-60帧,大于30时低于30帧会有视觉卡顿感觉,小于16时已经高于人眼分辨的60帧,浪费客户端性能。
-
开始我只是把宽度处理写在created里面的,但是最后页面端说会在页面渲染后多次更改其内容,所以单独写了个dynamicLength方法,在observe里面执行
-
关于文本的动态宽度,开始我简单粗暴直接使用了文本长度文本大小再加边距的宽度以为就可以了,最后发现当传过来一些特殊的字符时不能精确的获得宽度,这就导致了length比实际的文本宽度要宽,因为leftDistance为-length时,才设置left为windowWith,然后才从右边滚过来,也就是要等
(length-trueLen)/(scrollPace/interval)
, 即所需时间 = 多余长度/速度 ,这就有点奇怪了, 就想能不能直接获取到文本的真是宽度, 最后查到wx.createSelectorQuery
这个api,注意这里有个很重要的问题,因为marquee是独立组件,使用时一定要在wx.createSelectorQuery()
后面加上.in(this)
,this传入的是自定义组件的实例,否则获取到的rect值为null,不能成功获取到节点,同时需要在调用boundingClientRect
外部声明this的替代,否则在boundingClientRect
的回调中是拿不到组件中的this的,应该会报this.setData为undefined,毕竟是谁调用指向谁,正确实现如下:
var _this = this
//选择器
const query = wx.createSelectorQuery().in(this)
query.select('#marquee_wrap').boundingClientRect(function (rect) {
console.log("rect", rect)
_this.setData({
length:rect.width
})
}).exec();
仓仓促促,肯定有需要优化的地方,比如那些函数中的调用数据,解构使用会更加简洁,我把项目提了后再过来优化一下
我又回来了,体验还行,如果想体验流畅那么频率就很小,步长也不能太长,但是会很慢,由于频率高,一秒钟执行三四十次,真的太耗费资源,我不得不是用其他方法再尝试一下,目前暂定是用小程序的animate,如果可以,下一篇会用animate实现
免费使用,欢迎交流,转载注明即可