title: Vue Js infinite loop marquee (horizontal bar)
body: <p>I need recommendations on how to build an infinite loop marquee from my vue js code, or at least how to use Vue Js and jQuery for this specific project.</p>
  <p>My project Fiddle is as follows: <a href="https://jsfiddle.net/jackbauer/xz5wv617/7" rel="nofollow noreferrer">https://jsfiddle.net/jackbauer/xz5wv617/7</a></p>
  <p>It is working and what it does is creating a crypto currency horizontal bar, but now I need to know how to make it slide from right to left, in an infinite loop, and perhaps even add navigation controls. I tried searching for something like that in the The Progressive JavaScript Framework (<a href="https://vuejs.org/" rel="nofollow noreferrer">https://vuejs.org/</a>) but I couldn't find anything related to horizontal animation.</p>
  <p>The result I'm trying to achieve is the same as in this jQuery plugin: <a href="https://www.jqueryscript.net/demo/jQuery-Plugin-For-Horizontal-Text-Scrolling-Simple-Marquee/" rel="nofollow noreferrer">https://www.jqueryscript.net/demo/jQuery-Plugin-For-Horizontal-Text-Scrolling-Simple-Marquee/</a></p>
  <p>I've even tried risking using Vue js and jQuery, with no success: <a href="https://jsfiddle.net/jackbauer/xz5wv617/14" rel="nofollow noreferrer">https://jsfiddle.net/jackbauer/xz5wv617/14</a></p>
  <p>Maybe I should write a code that makes the vue get fully executed, only then the jQuery code will execute?</p>
  <p>All my code is available in the two fiddles that I've just posted, but just in case, here's my javascript:</p>
  <pre><code>    // common number filters
  Vue.filter( 'toFixed', ( num, asset ) =&gt; {
    if ( typeof asset === 'number' ) return Number( num ).toFixed( asset );
    return Number( num ).toFixed( ( asset === 'USDT' ) ? 3 : 8 );
  });
  Vue.filter( 'toMoney', num =&gt; {
    return Number( num ).toFixed( 0 ).replace( /./g, ( c, i, a ) =&gt; {
      return i &amp;&amp; c !== &quot;.&quot; &amp;&amp; ( ( a.length - i ) % 3 === 0 ) ? ',' + c : c;
    });
  });
  
  // component for creating line chart
  Vue.component( 'linechart', {
    props: {
      width: { type: Number, default: 400, required: true },
      height: { type: Number, default: 40, required: true },
      values: { type: Array, default: [], required: true },
    },
    data() {
      return { cx: 0, cy: 0 };
    },
    computed: {
      viewBox() {
        return '0 0 '+ this.width +' '+ this.height;
      },
      chartPoints() {
        let data = this.getPoints();
        let last = data.length ? data[ data.length - 1 ] : { x: 0, y: 0 };
        let list = data.map( d =&gt; ( d.x - 10 ) +','+ d.y );
        this.cx  = last.x - 5;
        this.cy  = last.y;
        return list.join( ' ' );
      },
    },
    methods: {
      getPoints() {
        this.width  = parseFloat( this.width ) || 0;
        this.height = parseFloat( this.height ) || 0;
        let min     = this.values.reduce( ( min, val ) =&gt; val &lt; min ? val : min, this.values[ 0 ] );
        let max     = this.values.reduce( ( max, val ) =&gt; val &gt; max ? val : max, this.values[ 0 ] );
        let len     = this.values.length;
        let half    = this.height / 2;
        let range   = ( max &gt; min ) ? ( max - min ) : this.height;
        let gap     = ( len &gt; 1 ) ? ( this.width / ( len - 1 ) ) : 1;
        let points  = [];
  
        for ( let i = 0; i &lt; len; ++i ) {
          let d = this.values[ i ];
          let val = 2 * ( ( d - min ) / range - 0.5 );
          let x = i * gap;
          let y = -val * half * 0.8 + half;
          points.push( { x, y } );
        }
        return points;
      }
    },
    template: `
    &lt;svg :viewBox=&quot;viewBox&quot; xmlns=&quot;http://www.w3.org/2000/svg&quot;&gt;
      &lt;polyline class=&quot;cryptocolor&quot; fill=&quot;none&quot; stroke=&quot;#fff&quot; stroke-width=&quot;2&quot; stroke-linecap=&quot;round&quot; :points=&quot;chartPoints&quot; /&gt;
      &lt;circle class=&quot;cryptocolor&quot; :cx=&quot;cx&quot; :cy=&quot;cy&quot; r=&quot;4&quot; fill=&quot;#fff&quot; stroke=&quot;none&quot; /&gt;
    &lt;/svg&gt;`,
  });
  
  // vue instance
  new Vue({
    // mount point
    el: '#app',
  
    // app data
    data: {
      endpoint : 'wss://stream.binance.com:9443/ws/!ticker@arr',
      iconbase : 'https://raw.githubusercontent.com/rainner/binance-watch/master/public/images/icons/',
      cache    : {},             // coins data cache
      coins    : [],             // live coin list from api
      asset    : 'USDT',          // filter by base asset pair
      search   : '',             // filter by search string
      sort     : 'Price',  // sort by param
      order    : 'desc',         // sort order ( asc, desc )
      limit    : 50,             // limit list 
      status   : 0,              // socket status ( 0: closed, 1: open, 2: active, -1: error )
      sock     : null,           // socket inst
      cx       : 0,
      cy       : 0,
    },
  
    // computed methods
    computed: {
  
      // process coins list
      coinsList() {
        let list = this.coins.slice();
        let search = this.search.replace( /[^\s\w\-\.]+/g, '' ).replace( /[\r\s\t\n]+/g, ' ' ).trim();
  
        if ( this.asset ) {
          list = list.filter( i =&gt; i.asset === this.asset );
        }
        if ( search &amp;&amp; search.length &gt; 1 ) {
          let reg = new RegExp( '^('+ search +')', 'i' );
          list = list.filter( i =&gt; reg.test( i.token ) );
        }
        if ( this.sort ) {
          list = this.sortList( list, this.sort, this.order );
        }
        if ( this.limit ) {
          list = list.slice( 0, this.limit );
        }
        return list;
      },
  
      // show socket connection loader
      loaderVisible() {
        return ( this.status === 2 ) ? false : true;
      },
  
      // sort-by label for buttons, etc
      sortLabel() {
        switch ( this.sort ) {
  
          case 'token'       :  return 'Token';
          case 'percent'     :  return 'Percent';
          case 'close'       :  return 'Price';
          case 'change'      :  return 'Change';
          case 'assetVolume' :  return 'Volume';
          case 'tokenVolume' :  return 'Volume';
          case 'trades'      :  return 'Trades';
          default            :  return 'Default';
        }
      },
    },
  
    // custom methods
    methods: {
  
      // apply sorting and toggle order
      sortBy( key, order ) {
        if ( this.sort !== key ) { this.order = order || 'asc'; }
        else { this.order = ( this.order === 'asc' ) ? 'desc' : 'asc'; }
        this.sort = key;
      },
  
      // filter by asset
      filterAsset( asset ) {
        this.asset = String( asset || 'BTC' );
      },
  
      // set list limit
      setLimit( limit ) {
        this.limit = parseInt( limit ) || 0;
      },
  
      // on socket connected
      onSockOpen( e ) {
        this.status = 1; // open
        console.info( 'WebSocketInfo:', 'Connection open ('+ this.endpoint +').' );
      },
  
      // on socket closed
      onSockClose( e ) {
        this.status = 0; // closed
        console.info( 'WebSocketInfo:', 'Connection closed ('+ this.endpoint +').' );
        setTimeout( this.sockInit, 10000 ); // try again
      },
  
      // on socket error
      onSockError( err ) {
        this.status = -1; // error
        console.error( 'WebSocketError:', err.message || err );
        setTimeout( this.sockInit, 10000 ); // try again
      },
  
      // process data from socket
      onSockData( e ) {
        let list = JSON.parse( e.data ) || [];
  
        for ( let item of list ) {
          // cleanup data for each coin
          let c = this.getCoinData( item );
          // keep to up 100 previous close prices in hostiry for each coin
          c.history = this.cache.hasOwnProperty( c.symbol ) ? this.cache[ c.symbol ].history : this.fakeHistory( c.close );
          if ( c.history.length &gt; 100 ) c.history = c.history.slice( c.history.length - 100 );
          c.history.push( c.close );
          // add coin data to cache
          this.cache[ c.symbol ] = c;
        }
        // convert cache object to final prices list for each symbol
        this.coins = Object.keys( this.cache ).map( s =&gt; this.cache[ s ] );
        this.status = 2; // active
      },
  
      // start socket connection
      sockInit() {
        if ( this.status &gt; 0 ) return;
        try {
          this.status = 0; // closed
          this.sock = new WebSocket( this.endpoint );
          this.sock.addEventListener( 'open', this.onSockOpen );
          this.sock.addEventListener( 'close', this.onSockClose );
          this.sock.addEventListener( 'error', this.onSockError );
          this.sock.addEventListener( 'message', this.onSockData );
        }
        catch( err ) {
          console.error( 'WebSocketError:', err.message || err );
          this.status = -1; // error
          this.sock = null;
        }
      },
  
      // start socket connection
      sockClose() {
        if ( this.sock ) {
          this.sock.close();
        }
      },
  
      // come up with some fake history prices to fill in the initial line chart
      fakeHistory( close ) {
        let num = close * 0.0001; // faction of current price
        let min = -Math.abs( num );
        let max = Math.abs( num );
        let out = [];
  
        for ( let i = 0; i &lt; 50; ++i ) {
          let rand = Math.random() * ( max - min ) + min;
          out.push( close + rand );
        }
        return out;
      },
  
      // finalize data for each coin from socket
      getCoinData( item ) {
        let reg         = /^([A-Z]+)(BTC|ETH|BNB|USDT|TUSD)$/;
        let symbol      = String( item.s ).replace( /[^\w\-]+/g, '' ).toUpperCase();
        let token       = symbol.replace( reg, '$1' );
        let asset       = symbol.replace( reg, '$2' );
        let name        = token;
        let pair        = token +'/'+ asset;
        let icon        = this.iconbase + token.toLowerCase() + '_.png';
        let open        = parseFloat( item.o );
        let high        = parseFloat( item.h );
        let low         = parseFloat( item.l );
        let close       = parseFloat( item.c );
        let change      = parseFloat( item.p );
        let percent     = parseFloat( item.P );
        let trades      = parseInt( item.n );
        let tokenVolume = Math.round( item.v );
        let assetVolume = Math.round( item.q );
        let sign        = ( percent &gt;= 0 ) ? '+' : '';
        let arrow       = ( percent &gt;= 0 ) ? '▲' : '▼';
        let info        = [ pair, close.toFixed( 8 ), '(', arrow, sign + percent.toFixed( 2 ) +'%', '|', sign + change.toFixed( 8 ), ')' ].join( ' ' );
        let style       = '';
  
        if ( percent &gt; 0 ) style = 'cryptogain';
        if ( percent &lt; 0 ) style = 'cryptoloss';
  
        return { symbol, token, asset, name, pair, icon, open, high, low, close, change, percent, trades, tokenVolume, assetVolume, sign, arrow, style, info };
      },
      // sort an array by key and order
      sortList( list, key, order ) {
        return list.sort( ( a, b ) =&gt; {
          let _a = a[ key ];
          let _b = b[ key ];
  
          if ( _a &amp;&amp; _b ) {
            _a = ( typeof _a === 'string' ) ? _a.toUpperCase() : _a;
            _b = ( typeof _b === 'string' ) ? _b.toUpperCase() : _b;
  
            if ( order === 'asc' ) {
              if ( _a &lt; _b ) return -1;
              if ( _a &gt; _b ) return 1;
            }
            if ( order === 'desc' ) {
              if ( _a &gt; _b ) return -1;
              if ( _a &lt; _b ) return 1;
            }
          }
          return 0;
        });
      },
    },
      // app mounted
    mounted() {
      this.sockInit();
    },
  
    // app destroyed
    destroyed() {
      this.sockClose();
    }
  });
  
  
  
  // BEGIN SLIDER
  
  // ---------------------------------
  // ---------- SimpleMarquee ----------
  // ---------------------------------
  //Copyright (C) 2016  Fabian Valle 
  //An easy to implement marquee plugin. I know its easy because even I can use it.
  //Forked from: https://github.com/conradfeyt/Simple-Marquee
  //Re-Written by: Fabian Valle (www.fabian-valle.com) (www.obliviocompany.com)
  // 
  // ------------------------
  // Structure //
  //
  //  *********************************** - marque-container - *************************************
  //  *                                                                                            *
  //  *   ******************************* ******************************************************   *
  //  *   *                             * *                                                    *   *
  //  *   * - marquee-content-sibling - * *                 - marquee-content -                *   *
  //  *   *                             * *                                                    *   *
  //  *   ******************************* ******************************************************   *
  //  *                                                                                            *
  //  **********************************************************************************************
  //
  //// Usage //
  //  
  //    Only need to call the createMarquee() function,
  //    if desired, pass through the following paramaters:
  //
  //    $1 duration:                   controls the speed at which the marquee moves
  //
  //    $2 padding:                    right margin between consecutive marquees. 
  //
  //    $3 marquee_class:             the actual div or span that will be used to create the marquee - 
  //                                   multiple marquee items may be created using this item's content. 
  //                                   This item will be removed from the dom
  //
  //    $4 container_class:           the container div in which the marquee content will animate. 
  //
  //    $5 marquee-content-sibling :   (optional argument) a sibling item to the marqueed item  that 
  //                                   affects the end point position and available space inside the 
  //                                   container. 
  //
  //    $6 hover:                     Boolean to indicate whether pause on hover should is required. 
  ;(function ($, window, document, undefined){
      var pluginName = 'SimpleMarquee';
  
      function Plugin (element, options) {
          this.element = element;
          this._name = pluginName;
          this._defaults = $.fn.SimpleMarquee.defaults;
          this.settings = $.extend( {}, this._defaults, options );
          this.marqueeSpawned = [];
          this.marqueeHovered = false;
          this.documentHasFocus = false;        
          //
          this.counter = 0;
  
          this.timeLeft = 0;
          this.currentPos = 0;
          this.distanceLeft = 0;
          this.totalDistance = 0;
          this.contentWidth = 0;
          this.endPoint = 0;
          this.duration = 0;
          this.hovered = false;
          this.padding = 0;
          
          
          this.init();
      }
      function marqueeObj(newElement){
          this.el=newElement;
          this.counter=0;
          this.name=&quot;&quot;;
          this.timeTop=0;
          this.currentPos=0;
          this.distanceTop=0;
          this.totalDistance=0;
          this.contentWidth=0;
          this.endPoint=0;
          this.duration=0;
          this.hovered=false;
          this.padding=0;
      }
      //methods for plugin
      $.extend(Plugin.prototype, {
  
          // Initialization logic
          init: function () {
              this.buildCache();
              this.bindEvents();
              var config = this.settings;
              //init marquee
              if($(config.marquee_class).width() == 0){
                  console.error('FATAL: marquee css or children css not correct. Width is either set to 0 or the element is collapsing. Make sure overflow is set on the marquee, and the children are postitioned relatively');
                  return;
              }
      
              if(typeof $(config.marquee_class) === 'undefined'){
                  console.error('FATAL: marquee class not valid');
                  return;
              }
      
              if(typeof $(config.container_class) === 'undefined'){
                  console.error('FATAL: marquee container class not valid');
                  return;
              }
      
              if(config.sibling_class != 0 &amp;&amp; typeof $(config.sibling_class) === 'undefined'){
                  console.error('FATAL: sibling class container class not valid');
                  return;
              }
              
                  if (config.autostart)
                  {
                      this.documentHasFocus = true;
                  }
              //create the Marquee
              this.createMarquee();
          },
  
          // Remove plugin instance completely
          destroy: function() {
              this.unbindEvents();
              this.$element.removeData();
          },
  
          // Cache DOM nodes for performance
          buildCache: function () {
              this.$element = $(this.element);
          },
  
          // Bind events that trigger methods
          bindEvents: function() {
              var plugin = this;
              $(window).on('focus',function(){
                  plugin.documentHasFocus = true;
                  for (var key in plugin.marqueeSpawned){
                    plugin.marqueeManager(plugin.marqueeSpawned[key]);   
                  } 
              });
              $(window).on('blur',function(){
                  plugin.documentHasFocus = false;
                  for (var key in plugin.marqueeSpawned){
                      plugin.marqueeSpawned[key].el.clearQueue().stop(); 
                      plugin.marqueeSpawned[key].hovered = true;
                  }
              });
  
          },
  
          // Unbind events that trigger methods
          unbindEvents: function() {
              $(window).off('blur focus');
          },
          getPosition: function(elName){
              this.currentPos = parseInt($(elName).css('left'));
              return this.currentPos;
          },
          createMarquee: function(){
              var plugin = this;
              var config = plugin.settings;
              var marqueeContent =  $(config.marquee_class).html();
              var containerWidth = $(config.container_class).width();
              var contentWidth = $(config.marquee_class).width();
              
              var widthToIgnore = 0;
              if (config.sibling_class != 0){ 
                  widthToIgnore = $(config.sibling_class).width();
              } 
              
              var spawnAmount = Math.ceil(containerWidth / contentWidth);
              
              $(config.marquee_class).remove();
  
              if(spawnAmount&lt;=2){
                  spawnAmount = 3;
              } else {
                spawnAmount++;
              }
  
              var totalContentWidth = (contentWidth + config.padding)*spawnAmount;
  
              var endPoint = -(totalContentWidth - containerWidth);
  
              var totalDistance =  containerWidth - endPoint;
              
              
              
              
              for (var i = 0; i &lt; spawnAmount; i++) {
                  
                  var newElement = false;
                  
                  if(config.hover == true){
  
                    
                    newElement = $('&lt;div class=&quot;marquee-' + (i+1) + '&quot;&gt;' + marqueeContent + '&lt;/div&gt;')        
                    .mouseenter(function() {
  
  
                      if ((plugin.documentHasFocus == true) &amp;&amp; (plugin.marqueeHovered == false)){
                        plugin.marqueeHovered = true;
  
                        for (var key in plugin.marqueeSpawned){
                          plugin.marqueeSpawned[key].el.clearQueue().stop(); 
                          plugin.marqueeSpawned[key].hovered = true;
                        }
                        
  
                      }
  
                    })
                    .mouseleave(function() {
  
  
                        if ((plugin.documentHasFocus == true) &amp;&amp; (plugin.marqueeHovered == true)){
  
                          for (var key in plugin.marqueeSpawned){
                            plugin.marqueeManager(plugin.marqueeSpawned[key]);   
                          } 
  
                          plugin.marqueeHovered = false;
                        } 
                    });
  
                  } else {
  
                    newElement = $('&lt;div class=&quot;marquee-' + (i+1) + '&quot;&gt;' + marqueeContent + '&lt;/div&gt;') ;   
  
                  }
  
                  plugin.marqueeSpawned[i] = new marqueeObj(newElement);
  
                  $(config.container_class).append(newElement);
  
                  plugin.marqueeSpawned[i].currentPos = (widthToIgnore + (contentWidth*i))+(config.padding*i);  //initial positioning
                  plugin.marqueeSpawned[i].name = '.marquee-'+(i+1); 
  
                  plugin.marqueeSpawned[i].totalDistance = totalDistance;  
                  plugin.marqueeSpawned[i].containerWidth = containerWidth;  
                  plugin.marqueeSpawned[i].contentWidth = contentWidth;  
                  plugin.marqueeSpawned[i].endPoint = endPoint;  
                  plugin.marqueeSpawned[i].duration = config.duration;  
                  plugin.marqueeSpawned[i].padding = config.padding;  
  
                  plugin.marqueeSpawned[i].el.css('left', plugin.marqueeSpawned[i].currentPos+config.padding +'px'); //setting left according to postition
  
                   if (plugin.documentHasFocus == true){
                    plugin.marqueeManager(plugin.marqueeSpawned[i]);
                  }
  
              }
              //end for
              
              if(document.hasFocus()){
                   plugin.documentHasFocus = true;
              }else{
                  plugin.documentHasFocus = false;
              }
              
          },
          marqueeManager: function(marqueed_el){
              var plugin = this;
              var elName = marqueed_el.name;
              if (marqueed_el.hovered == false) { 
  
                  if (marqueed_el.counter &gt; 0) {  //this is not the first loop
                    
                        marqueed_el.timeLeft = marqueed_el.duration;
                        marqueed_el.el.css('left', marqueed_el.containerWidth +'px'); //setting margin 
                        marqueed_el.currentPos = marqueed_el.containerWidth; 
                        marqueed_el.distanceLeft = marqueed_el.totalDistance - (marqueed_el.containerWidth - plugin.getPosition(elName));
  
                  } else {    // this is the first loop
                    
                    marqueed_el.timeLeft = (((marqueed_el.totalDistance - (marqueed_el.containerWidth - plugin.getPosition(elName)))/ marqueed_el.totalDistance)) * marqueed_el.duration;
                  }
  
              } else {
                    marqueed_el.hovered = false;
                    marqueed_el.currentPos = parseInt(marqueed_el.el.css('left'));
                    marqueed_el.distanceLeft = marqueed_el.totalDistance - (marqueed_el.containerWidth - plugin.getPosition(elName));
                    marqueed_el.timeLeft = (((marqueed_el.totalDistance - (marqueed_el.containerWidth - marqueed_el.currentPos))/ marqueed_el.totalDistance)) * marqueed_el.duration;
              }
  
              plugin.marqueeAnim(marqueed_el);
          },
          marqueeAnim: function(marqueeObject){
              var plugin = this;
              marqueeObject.counter++;
              marqueeObject.el.clearQueue().animate(
                      {'left': marqueeObject.endPoint+'px'}, 
                      marqueeObject.timeLeft, 
                      'linear', 
                      function(){
                          plugin.marqueeManager(marqueeObject);
                      });
          },
          callback: function() {
              // Cache onComplete option
              var onComplete = this.settings.onComplete;
  
              if ( typeof onComplete === 'function' ) {
                  onComplete.call(this.element);
              }
          }
  
      });
      //end methods for plugin
      
      $.fn.SimpleMarquee = function (options) {
          this.each(function() {
              if ( !$.data( this, &quot;plugin_&quot; + pluginName ) ) {
                  $.data( this, &quot;plugin_&quot; + pluginName, new Plugin( this, options ) );
              }
          });
          return this;
      };
      $.fn.SimpleMarquee.defaults = {
          autostart: true,
              property: 'value',
              onComplete: null,
              duration: 20000,
              padding: 10,
              marquee_class: '.marquee',
              container_class: '.simple-marquee-container',
              sibling_class: 0,
              hover: true
      };
      
  })( jQuery, window, document );
  </code></pre>
  <p>Would appreciate if anyone could point me some directions here. Thanks in advance.</p>