Vue

by rogeriolino

HTML

<div id="app">
  <h1>
    Vue Marquee Component
  </h1>

  <div class="demo">
    <v-marquee :speed="speed" :text="text"></v-marquee>
  </div>
  
  
  <div class="settings">
    <div>
      <label>Speed</label>
      <input type="range" min="10" max="200" v-model="speed">
    </div>
    <div>
      <label>Text</label>
      <textarea v-model="text"></textarea>
    </div>
  </div>
</div>

CSS

#app {
  padding: 2rem 1rem;
}

.demo {
  padding: 2rem;
  margin: 1rem 0;
  background: #f1f1f1;
}

.settings label {
  display: block;
}

.settings textarea {
  width: 100%;
  height: 150px;
}

/* component css */
div.marquee {
  overflow: hidden;
}

div.marquee p {
  width: auto;
  margin: 0;
  padding: 0;
  white-space: nowrap;
  display: inline-block;
}

Vue

const Marquee = Vue.component('v-marquee', {
  template: '<div class="marquee"><p>{{text}}</p></div>',
  props: {
  	text: {
    	type: String,
      required: true
    },
    speed: {
      default: 30,
    }
  },
  data() {
  	return {
    	started: false,
    	timestamp: 0,
      width: 0,
      p: null,
      resetId: 0
    }
  },
  methods: {
  	reset() {
    	Vue.nextTick(() => {
      	this.started = false;
        this.p = this.$el.querySelector('p');
        this.p.style.marginLeft = 0;
        this.width = this.p.scrollWidth;
        clearTimeout(this.resetId);
        this.resetId = setTimeout(() => {
        	this.started = true;
        	this.timestamp = (new Date()).getTime();
        }, 500);
      })
    }
  },
  watch: {
  	text() {
    	this.reset();
    }
  },
  mounted() {
  	let pad = 5;
    this.reset();
    
    setInterval(() => {
    	if (this.started) {
        let now = (new Date()).getTime();
        let elapsedTime = now - this.timestamp;
        this.timestamp = now;

        let currLeft = this.p.style.marginLeft.replace('px', '');
        let left = (currLeft - (this.speed/elapsedTime));

        if (left < 0 && Math.abs(left) + pad >= this.width) {
          left = this.$el.scrollWidth + pad;
        }
        this.p.style.marginLeft = left + 'px';
      }
    }, 30);
  }
})

new Vue({
  el: "#app",
  components: {
  	Marquee
  },
  data() {
  	return {
    	speed: 30,
      text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin placerat ante bibendum, consectetur sem a, convallis sapien. Praesent dui sapien, pretium sed magna eget, venenatis gravida justo. Nulla vel magna nec libero elementum eleifend. Nullam sit amet viverra arcu. Mauris sit amet neque quis leo iaculis scelerisque. Vivamus nisi velit, mollis eget risus quis, lobortis dignissim metus. Phasellus quis justo nunc. Vivamus at ipsum ipsum. Suspendisse et rhoncus mauris. Praesent a odio aliquam metus commodo dictum. Suspendisse lobortis mauris ac dui...