有几次遇到文字超长不能在布局内显示完整信息,只能通过ellipsis加上省略号显示内容,再补充title去提示完整信息,其实知道这个办法能解决问题,但是不够对于动效来说属于不优雅,查询看了部分人的想法是将文字使用translateX来移动,当然能实现,感觉动画效果处理起来有点费劲,会很呆板。又想起vant-ui的通知栏有这个效果,就花了点时间的修改组件的源码,进行修改成常用组件代码的格式。整体效果功能同官网一致。将里面的rander模板换成了template模板,将里面使用的common方法直接简化放在正常文件导入的位置了。现在完完整整就是一个单页面组件。只是移除了close和right-icon的slot和方法。
链接: 这是vant-ui的官网效果,属性也是根据官方去进行传递使用
调用代码块
<template>
<div style="width: 360px">
<!-- <marquee left-icon="volume-o" scrollable text="无论我们能活多久,我们能够享受的只有无法分割的此刻,此外别无其他。"></marquee> -->
<marquee scrollable wrapable>
<p>无论我们能活多久,我们能够享受的只有无法分割的此刻,此外别无其他。</p>
</marquee>
</div>
</template>
<script>
import marquee from "@/components/marquee.vue";
export default {
name: "Index",
components: { marquee },
data() {
return { };
},
mounted() { },
methods: {},
// End
};
</script>
组件代码块
<template>
<div role="alert" :class="bem({ wrapable })" :style="wrapStyl" v-show="show" @click="onClickIcon">
<i v-if="leftIcon" :class="[bem('left-icon'), leftIcon]" />
<div ref="wrap" role="marquee" :class="bem('wrap')">
<div ref="content" :class="[bem('content'), (!!scrollable && !wrapable) && 'ellipsis']" :style="contentStyl" @transitionend="onTransitionEnd">
<slot>{{ text }}</slot>
</div>
</div>
</div>
</template>
<script>
const isDef = (val) => val !== undefined && val !== null;
const raf = fn => window.requestAnimationFrame.call(window, fn);// double raf for animation
function doubleRaf(fn) {
raf(raf.bind(null, fn));
}
export default {
name: 'marquee',
props: {
text: String,
mode: String,
color: String,
leftIcon: String,
wrapable: Boolean,
background: String,
scrollable: { type: Boolean, default: null },
delay: { type: [Number, String], default: 1 },
speed: { type: [Number, String], default: 50 }
},
data() {
return {
show: true,
offset: 0,
duration: 0,
wrapWidth: 0,
contentWidth: 0
}
},
activated() {
this.start();
this.bind((bind) => { bind(window, 'pageshow', this.start); });
},
mounted() {
this.bind((bind) => { bind(window, 'pageshow', this.start); });
},
computed: {
wrapStyl() {
const { color, background } = this;
return { color, background }
},
contentStyl() {
return { transform: this.offset ? "translateX(" + this.offset + "px)" : '', transitionDuration: this.duration + 's' }
},
bem() {
return (...cls) => this.createBem('grid-item')(...cls);
}
},
methods: {
onClickIcon(event) {
return;
if (this.mode === 'closeable') {
this.show = false;
this.$emit('close', event);
}
},
onTransitionEnd() {
this.offset = this.wrapWidth;
this.duration = 0; // 等待Vue渲染偏移
raf(() => {// 使用双raf确保动画可以开始
doubleRaf(() => {
this.offset = -this.contentWidth;
this.duration = (this.contentWidth + this.wrapWidth) / this.speed;
this.$emit('replay');
});
});
},
reset() {
this.offset = 0;
this.duration = 0;
this.wrapWidth = 0;
this.contentWidth = 0;
},
start() {
const delay = isDef(this.delay) ? this.delay * 1000 : 0;
this.reset();
clearTimeout(this.startTimer);
this.startTimer = setTimeout(() => {
const refsNode = this.$refs;
const wrap = refsNode.wrap;
const content = refsNode.content;
if (!wrap || !content || this.scrollable === false) return;
const wrapWidth = wrap.getBoundingClientRect().width;
const contentWidth = content.getBoundingClientRect().width;
if (this.scrollable || contentWidth > wrapWidth) {
doubleRaf(() => {
this.offset = -contentWidth;
this.duration = contentWidth / this.speed;
this.wrapWidth = wrapWidth;
this.contentWidth = contentWidth;
});
}
}, delay);
},
bind(handler) {
handler.call(this, (target, event, handler, passive) => {
if (passive === void 0) passive = false;
target.addEventListener(event, handler, false);
}, true);
},
unbind() {
handler.call(this, (target, event, handler) => {
target.removeEventListener(event, handler);
}, true);
},
createBem(name) {
/**
* bem helper
* b() // 'button'
* b('text') // 'button__text'
* b({ disabled }) // 'button button--disabled'
* b('text', { disabled }) // 'button__text button__text--disabled'
* b(['disabled', 'primary']) // 'button button--disabled button--primary'
*/
return function (el, mods) {
function gen(name, mods) {
if (!mods) return '';
if (typeof mods === 'string') return " " + name + "--" + mods;
if (Array.isArray(mods)) return mods.reduce((ret, item) => ret + gen(name, item), '');
return Object.keys(mods).reduce((ret, key) => ret + (mods[key] ? gen(name, key) : ''), '');
}
if (el && typeof el !== 'string') {
mods = el; el = '';
}
el = el ? name + "__" + el : name;
return "" + el + gen(el, mods);
};
}
},
deactivated() {
this.unbind((bind) => { bind(window, 'pageshow', this.start);});
},
beforeDestroy() {
this.unbind((bind) => { bind(window, 'pageshow', this.start);});
}
}
</script>
<style scoped>
.notice-bar {
position: relative;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
height: 40px;
padding: 0 16px;
color: #ed6a0c;
font-size: 14px;
line-height: 24px;
background-color: #fffbe8;
}
.notice-bar__left-icon,
.notice-bar__right-icon {
min-width: 24px;
font-size: 16px;
}
.notice-bar__right-icon {
text-align: right;
cursor: pointer;
}
.notice-bar__wrap {
position: relative;
display: -webkit-box;
display: -webkit-flex;
display: flex;
-webkit-box-flex: 1;
-webkit-flex: 1;
flex: 1;
-webkit-box-align: center;
-webkit-align-items: center;
align-items: center;
height: 100%;
overflow: hidden;
}
.notice-bar__content {
position: absolute;
white-space: nowrap;
-webkit-transition-timing-function: linear;
transition-timing-function: linear;
}
.notice-bar__content.ellipsis {
max-width: 100%;
}
.notice-bar--wrapable {
height: auto;
padding: 8px 16px;
}
.notice-bar--wrapable .notice-bar__wrap {
height: auto;
}
.notice-bar--wrapable .notice-bar__content {
position: relative;
white-space: normal;
word-wrap: break-word;
}
</style>