携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第13天,点击查看活动详情
效果图
使用
<NoticeBar text="testtesttst" :scrollable="scrollable" @onClose="handleClose"/>
布局
采用 flex 布局,中间是走马灯内容部分,左边可以是标题,后退按钮或者自定义,右边关闭走马灯。
因为我们的项目是移动端项目,所以这个我就写成后退按钮。
<div class="notice-content">
<div class="left-icon"><</div>
<div ref="wrapRef" class="wrap">
<!-- scrollable为false 可给 notice-ellipsis 类名 -->
<div ref="textRef" :class="['text ', animationClass]" :style="contentStyle" @animationend="onAnimationEnd">
{{ text || 'no-content' }}
</div>
</div>
<div class="right-icon" @click="handleCloseClick">×</div>
</div>
.notice-content {
/*...*/
white-space: nowrap;
overflow: hidden;
}
.notice-content .wrap {
flex: 1;
width: 100%;
overflow: hidden;
}
.notice-content .notice-ellipsis {
width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.notice-content .text {
width: fit-content;
}
/* 第一次滚动,只执行一次 */
.play {
animation: nut-notice-bar-play linear both running;
}
.play-infinite {
animation: nut-notice-bar-play-infinite linear infinite both running;
}
@keyframes nut-notice-bar-play {
to {
transform: translate3d(-100%, 0, 0);
}
}
@keyframes nut-notice-bar-play-infinite {
to {
transform: translateX(-100%);
}
}
notice-ellipsis 这个类可以当你不想滚动,但是内容宽度又比盒子大的时候,显示省略号。判断 scrollable
样式有个需要注意的地方
.notice-content .text {
width: fit-content;
}
文字根据内容自动设置宽度,否则每次滚动的时候,都默认是中间盒子的高度。
- 如果文字内容长度大于等于盒子宽度的时候,这个时候没有问题。
- 如果文字内容长度小于盒子宽度的时候,就会导致等待走完整个盒子宽度才继续下一次滚动,这样体验就不太好。
属性
- text:展示的文字
- spped:滚动速度,默认 50
- scrollable: 只做展示的时候可以设置为 false, 则不会滚动
- delay: 第一次滚动延迟时间
- showxxx: 是否展示icon
const props = withDefaults(
defineProps<{
speed?: number
delay?: number
scrollable?: boolean
text: string
showLeftIcon?: boolean
showRightIcon?: boolean
}>(),
{
speed: 50,
delay: 1000,
scrollable: true,
showLeftIcon: true,
showRightIcon: true
}
)
事件
- close:不传则默认关闭走马灯,传了触发对应操作后关闭。
- xxx: 点击左边 icon 的事件。
const emit = defineEmits<{
(e: 'close', data: any): void
}>()
整体思路
初始化
获取中间内容宽度、文字宽度
判断 scrollable,1. true则执行 scroll() 逻辑;2. false则不执行,但是我们需要 watch(() => props.scrollable),可以控制是否开始,但不可暂停;
第2点的功能使用场景很少,一般都是进入页面就开始滚动或者直接展示。
onMounted(() => {
widthParam.wrapWidth = wrapRef.value.getBoundingClientRect().width
widthParam.textWidth = textRef.value.getBoundingClientRect().width
console.log(widthParam)
props.scrollable && scroll()
})
// 考虑是否需要
watch(() => props.scrollable, (val) => {
if (widthParam.wrapWidth && widthParam.textWidth && val) scroll()
})
维护一个 firstLoop 变量,判断是否第一次滚动。
第一次滚动只滚动文字的宽度,第二次开始才滚动盒子的宽度。
scroll函数
const scroll = () => {
animationClass.value = 'play'
contentStyle.value = getContentStyle()
firstLoop.value = false
}
// 动画结束,也就是判断第一次滚动结束之后重新计算滚动距离
const onAnimationEnd = () => {
console.log('onAnimationEnd')
animationClass.value = 'play-infinite'
contentStyle.value = getContentStyle()
}
getContentStyle函数
const getContentStyle = (): string => {
const distance = widthParam.textWidth + (firstLoop.value ? 0 : widthParam.wrapWidth)
const duration = distance / props.speed
const style = `animation-delay: ${firstLoop.value ? props.delay : 0}ms; animation-duration: ${duration}s; transform: translateX(${firstLoop.value ? 0 : widthParam.wrapWidth}px)`;
return style
}
点击事件派发
const handleCloseClick = () => {
/**
* 你的逻辑....
*/
// 派发事件
emit('close', 123)
}