Vue文字走马灯(文字轮播)组件

有几次遇到文字超长不能在布局内显示完整信息,只能通过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>
  • 0
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值