【问题标题】:Vue Js infinite loop marquee (horizontal bar)Vue Js无限循环选框(横条)
【发布时间】:2020-11-06 21:22:39
【问题描述】:

我需要关于如何从我的 vue js 代码构建无限循环选取框的建议,或者至少如何为这个特定项目使用 Vue Js 和 jQuery。

我的项目Fiddle如下:https://jsfiddle.net/jackbauer/xz5wv617/7

它正在工作,它的作用是创建一个加密货币水平条,但现在我需要知道如何让它从右向左滑动,无限循环,甚至可能添加导航控件。我尝试在 The Progressive JavaScript Framework (https://vuejs.org/) 中搜索类似的内容,但找不到与水平动画相关的任何内容。

我想要达到的结果和这个 jQuery 插件中的一样:https://www.jqueryscript.net/demo/jQuery-Plugin-For-Horizontal-Text-Scrolling-Simple-Marquee/

我什至尝试过使用 Vue js 和 jQuery,但没有成功:https://jsfiddle.net/jackbauer/xz5wv617/14

也许我应该写一段代码让 vue 完全执行,然后 jQuery 代码才会执行​​?

我所有的代码都在我刚刚发布的两个小提琴中可用,但以防万一,这是我的 javascript:

    // common number filters
Vue.filter( 'toFixed', ( num, asset ) => {
  if ( typeof asset === 'number' ) return Number( num ).toFixed( asset );
  return Number( num ).toFixed( ( asset === 'USDT' ) ? 3 : 8 );
});
Vue.filter( 'toMoney', num => {
  return Number( num ).toFixed( 0 ).replace( /./g, ( c, i, a ) => {
    return i && c !== "." && ( ( 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 => ( 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 ) => val < min ? val : min, this.values[ 0 ] );
      let max     = this.values.reduce( ( max, val ) => val > max ? val : max, this.values[ 0 ] );
      let len     = this.values.length;
      let half    = this.height / 2;
      let range   = ( max > min ) ? ( max - min ) : this.height;
      let gap     = ( len > 1 ) ? ( this.width / ( len - 1 ) ) : 1;
      let points  = [];

      for ( let i = 0; i < 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: `
  <svg :viewBox="viewBox" xmlns="http://www.w3.org/2000/svg">
    <polyline class="cryptocolor" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" :points="chartPoints" />
    <circle class="cryptocolor" :cx="cx" :cy="cy" r="4" fill="#fff" stroke="none" />
  </svg>`,
});

// 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 => i.asset === this.asset );
      }
      if ( search && search.length > 1 ) {
        let reg = new RegExp( '^('+ search +')', 'i' );
        list = list.filter( i => 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 > 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 => this.cache[ s ] );
      this.status = 2; // active
    },

    // start socket connection
    sockInit() {
      if ( this.status > 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 < 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 >= 0 ) ? '+' : '';
      let arrow       = ( percent >= 0 ) ? '▲' : '▼';
      let info        = [ pair, close.toFixed( 8 ), '(', arrow, sign + percent.toFixed( 2 ) +'%', '|', sign + change.toFixed( 8 ), ')' ].join( ' ' );
      let style       = '';

      if ( percent > 0 ) style = 'cryptogain';
      if ( percent < 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 ) => {
        let _a = a[ key ];
        let _b = b[ key ];

        if ( _a && _b ) {
          _a = ( typeof _a === 'string' ) ? _a.toUpperCase() : _a;
          _b = ( typeof _b === 'string' ) ? _b.toUpperCase() : _b;

          if ( order === 'asc' ) {
            if ( _a < _b ) return -1;
            if ( _a > _b ) return 1;
          }
          if ( order === 'desc' ) {
            if ( _a > _b ) return -1;
            if ( _a < _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="";
        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 && 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<=2){
                spawnAmount = 3;
            } else {
              spawnAmount++;
            }

            var totalContentWidth = (contentWidth + config.padding)*spawnAmount;

            var endPoint = -(totalContentWidth - containerWidth);

            var totalDistance =  containerWidth - endPoint;
            
            
            
            
            for (var i = 0; i < spawnAmount; i++) {
                
                var newElement = false;
                
                if(config.hover == true){

                  
                  newElement = $('<div class="marquee-' + (i+1) + '">' + marqueeContent + '</div>')        
                  .mouseenter(function() {


                    if ((plugin.documentHasFocus == true) && (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) && (plugin.marqueeHovered == true)){

                        for (var key in plugin.marqueeSpawned){
                          plugin.marqueeManager(plugin.marqueeSpawned[key]);   
                        } 

                        plugin.marqueeHovered = false;
                      } 
                  });

                } else {

                  newElement = $('<div class="marquee-' + (i+1) + '">' + marqueeContent + '</div>') ;   

                }

                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 > 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, "plugin_" + pluginName ) ) {
                $.data( this, "plugin_" + 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 );

如果有人能在这里给我指点方向,我将不胜感激。提前致谢。

【问题讨论】:

    标签: jquery vue.js vuejs2 vue-component


    【解决方案1】:

    这是我能想到的最好的:

    Vue.filter('toFixed', (num, asset) => {
      if (typeof asset === 'number') return Number(num).toFixed(asset);
      return Number(num).toFixed((asset === 'USDT') ? 3 : 8);
    });
    Vue.filter('toMoney', num => {
      return Number(num).toFixed(0).replace(/./g, (c, i, a) => {
        return i && c !== "." && ((a.length - i) % 3 === 0) ? ',' + c : c;
      });
    });
    
    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 => (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) => val < min ? val : min, this.values[0]);
          let max = this.values.reduce((max, val) => val > max ? val : max, this.values[0]);
          let len = this.values.length;
          let half = this.height / 2;
          let range = (max > min) ? (max - min) : this.height;
          let gap = (len > 1) ? (this.width / (len - 1)) : 1;
          let points = [];
    
          for (let i = 0; i < 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: `
      <svg :viewBox="viewBox" xmlns="http://www.w3.org/2000/svg">
        <polyline class="cryptocolor" fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" :points="chartPoints" />
        <circle class="cryptocolor" :cx="cx" :cy="cy" r="4" fill="#fff" stroke="none" />
      </svg>`,
    });
    
    new Vue({
      el: '#app',
    
      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: {
    
        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 => i.asset === this.asset);
          }
          if (search && search.length > 1) {
            let reg = new RegExp('^(' + search + ')', 'i');
            list = list.filter(i => 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;
        },
    
        loaderVisible() {
          return (this.status === 2) ? false : true;
        },
    
        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';
          }
        },
      },
    
      methods: {
    
        sortBy(key, order) {
          if (this.sort !== key) {
            this.order = order || 'asc';
          } else {
            this.order = (this.order === 'asc') ? 'desc' : 'asc';
          }
          this.sort = key;
        },
        filterAsset(asset) {
          this.asset = String(asset || 'BTC');
        },
    
        setLimit(limit) {
          this.limit = parseInt(limit) || 0;
        },
        onSockOpen(e) {
          this.status = 1; // open
          console.info('WebSocketInfo:', 'Connection open (' + this.endpoint + ').');
        },
        onSockClose(e) {
          this.status = 0; // closed
          console.info('WebSocketInfo:', 'Connection closed (' + this.endpoint + ').');
          setTimeout(this.sockInit, 10000); // try again
        },
    
        onSockError(err) {
          this.status = -1; // error
          console.error('WebSocketError:', err.message || err);
          setTimeout(this.sockInit, 10000); // try again
        },
    
        onSockData(e) {
          let list = JSON.parse(e.data) || [];
    
          for (let item of list) {
            let c = this.getCoinData(item);
            c.history = this.cache.hasOwnProperty(c.symbol) ? this.cache[c.symbol].history : this.fakeHistory(c.close);
            if (c.history.length > 100) c.history = c.history.slice(c.history.length - 100);
            c.history.push(c.close);
            this.cache[c.symbol] = c;
          }
          this.coins = Object.keys(this.cache).map(s => this.cache[s]);
          this.status = 2; // active
        },
    
        sockInit() {
          if (this.status > 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;
          }
        },
        sockClose() {
          if (this.sock) {
            this.sock.close();
          }
        },
    
        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 < 50; ++i) {
            let rand = Math.random() * (max - min) + min;
            out.push(close + rand);
          }
          return out;
        },
    
        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 >= 0) ? '+' : '';
          let arrow = (percent >= 0) ? '▲' : '▼';
          let info = [pair, close.toFixed(8), '(', arrow, sign + percent.toFixed(2) + '%', '|', sign + change.toFixed(8), ')'].join(' ');
          let style = '';
    
          if (percent > 0) style = 'cryptogain';
          if (percent < 0) style = 'cryptoloss';
    
          return {
            symbol,
            token,
            asset,
            name,
            pair,
            icon,
            open,
            high,
            low,
            close,
            change,
            percent,
            trades,
            tokenVolume,
            assetVolume,
            sign,
            arrow,
            style,
            info
          };
        },
    
        sortList(list, key, order) {
          return list.sort((a, b) => {
            let _a = a[key];
            let _b = b[key];
    
            if (_a && _b) {
              _a = (typeof _a === 'string') ? _a.toUpperCase() : _a;
              _b = (typeof _b === 'string') ? _b.toUpperCase() : _b;
    
              if (order === 'asc') {
                if (_a < _b) return -1;
                if (_a > _b) return 1;
              }
              if (order === 'desc') {
                if (_a > _b) return -1;
                if (_a < _b) return 1;
              }
            }
            return 0;
          });
        },
      },
    
      mounted() {
        this.sockInit();
    
        const gridList = document.querySelector('.cryptomain-grid-list');
        let gridListWidth = 0;
        const gridListInterval = setInterval(() => {
    
          if (gridList.children.length > 0) {
    
            for (let i of gridList.children) {
              gridListWidth += 311.2;
            }
    
            const cssAnimation = document.createElement('style');
            cssAnimation.type = 'text/css';
            const rules = document.createTextNode(`@keyframes marquee {
        0% { transform: translateX(100%); }
        100% { transform: translateX(${-Math.abs(gridListWidth)}px); }
    }`);
            cssAnimation.appendChild(rules);
            document.getElementsByTagName("head")[0].appendChild(cssAnimation);
            clearInterval(gridListInterval);
          }
        }, 1)
      },
    
      destroyed() {
        this.sockClose();
      }
    });
    .vw-sirat-search-icon i {
      color: #fff;
    }
    
    .placeholdercrypto::placeholder {
      /* Chrome, Firefox, Opera, Safari 10.1+ */
      color: #1567d8;
      opacity: 1;
      /* Firefox */
    }
    
    .placeholdercrypto:-ms-input-placeholder {
      /* Internet Explorer 10-11 */
      color: #1567d8;
    }
    
    .placeholdercrypto::-ms-input-placeholder {
      /* Microsoft Edge */
      color: #1567d8;
    }
    
    
    /* CSS Document */
    
    .cryptoif-small {
      display: none;
    }
    
    @media only screen and (min-width: 420px) {
      .cryptoif-small {
        display: initial;
      }
    }
    
    .cryptoif-medium {
      display: none;
    }
    
    @media only screen and (min-width: 720px) {
      .cryptoif-medium {
        display: initial;
      }
    }
    
    .cryptoif-large {
      display: none;
    }
    
    @media only screen and (min-width: 1200px) {
      .cryptoif-large {
        display: initial;
      }
    }
    
    .cryptohidden,
    [hidden],
    [v-cloak] {
      display: none;
    }
    
    .cryptodisabled,
    [disabled] {
      pointer-events: none;
      opacity: 0.5;
    }
    
    .cryptocard {
      padding: 1em;
      background-color: #1e2126;
      border-radius: 4px;
      -moz-border-radius: 4px;
      -webkit-border-radius: 4px;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    }
    
    .cryptopush-top {
      margin-top: 1em;
    }
    
    .cryptopush-right {
      margin-right: 1em;
    }
    
    .cryptopush-bottom {
      margin-bottom: 1em;
    }
    
    .cryptopush-left {
      margin-left: 1em;
    }
    
    .cryptopush-all {
      margin: 1em;
    }
    
    .cryptopad-top {
      padding-top: 1em;
    }
    
    .cryptopad-right {
      padding-right: 1em;
    }
    
    .cryptopad-bottom {
      padding-bottom: 1em;
    }
    
    .cryptopad-left {
      padding-left: 1em;
    }
    
    .cryptopad-all {
      padding: 1em;
    }
    
    .cryptoborder-top {
      border-top: 2px solid rgba(255, 255, 255, 0.04);
    }
    
    .cryptoborder-right {
      border-right: 2px solid rgba(255, 255, 255, 0.04);
    }
    
    .cryptoborder-bottom {
      border-bottom: 2px solid rgba(255, 255, 255, 0.04);
    }
    
    .cryptoborder-left {
      border-left: 2px solid rgba(255, 255, 255, 0.04);
    }
    
    .cryptoflex-row {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
    }
    
    .cryptoflex-wrap {
      flex-wrap: wrap;
    }
    
    .cryptoflex-left {
      justify-content: flex-start;
    }
    
    .cryptoflex-center {
      justify-content: center;
    }
    
    .cryptoflex-right {
      justify-content: flex-end;
    }
    
    .cryptoflex-space {
      justify-content: space-between;
    }
    
    .cryptoflex-around {
      justify-content: space-around;
    }
    
    .cryptoflex-top {
      align-items: flex-start;
    }
    
    .cryptoflex-middle {
      align-items: center;
    }
    
    .cryptoflex-bottom {
      align-items: flex-end;
    }
    
    .cryptoflex-1 {
      flex: 1;
    }
    
    .cryptoflex-2 {
      flex: 2;
    }
    
    .cryptoflex-3 {
      flex: 3;
    }
    
    .cryptoflex-4 {
      flex: 4;
    }
    
    .cryptoflex-5 {
      flex: 5;
    }
    
    .cryptotext-left {
      text-align: left;
    }
    
    .cryptotext-right {
      text-align: right;
    }
    
    .cryptotext-center {
      text-align: center;
    }
    
    .cryptotext-justify {
      text-align: justify;
    }
    
    .cryptotext-uppercase {
      text-transform: uppercase;
    }
    
    .cryptotext-lowercase {
      text-transform: lowercase;
    }
    
    .cryptotext-capitalize {
      text-transform: capitalize;
    }
    
    .cryptotext-underline {
      text-decoration: underline;
    }
    
    .cryptotext-striked {
      text-decoration: line-through;
    }
    
    .cryptotext-italic {
      font-style: italic;
    }
    
    .cryptotext-bold {
      font-weight: bold;
    }
    
    @media only screen and (max-width: 719px) {
      .cryptoif-mediumtopo {
        display: none;
      }
      .cryptoif-mediumtopo {
        display: block;
      }
      .cryptodropdown>ul {
        left: 0;
      }
      .cryptodropdown>ul {
        right: inherit;
      }
      .cryptotext-nowrap.cryptotext-condense.cryptoshadow-text {
        font-size: 5vw;
      }
    }
    
    @media only screen and (min-width: 720px) {
      .cryptoif-mediumtopo {
        display: block;
      }
      .cryptoif-mediumtopo {
        display: none;
      }
      .cryptodropdown>ul {
        right: 0;
      }
      .cryptodropdown>ul {
        left: inherit;
      }
      .cryptotext-nowrap.cryptotext-condense.cryptoshadow-text {
        font-size: 3vw;
      }
    }
    
    .cryptotext-nowrap.cryptotext-condense.cryptoshadow-text {
      font-family: fantasy;
      text-transform: uppercase;
      white-space: normal;
      text-align: center;
      color: #f3f3f3;
      text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.2), 1px 0px 2px rgba(0, 0, 0, 0.2), -1px 0px 2px rgba(0, 0, 0, 0.2), -1px -1px 2px rgba(0, 0, 0, 0.2), 1px 1px 2px rgba(0, 0, 0, 0.2), 0px 1px 2px rgba(0, 0, 0, 0.2);
      -webkit-font-smoothing: antialiased;
      -moz-osx-font-smoothing: grayscale;
      text-rendering: optimizeLegibility;
    }
    
    
    /* Manter o nowrap, por causa dos botões */
    
    .cryptotext-nowrap {
      white-space: nowrap;
    }
    
    .cryptotext-clip {
      overflow: hidden;
      white-space: nowrap;
      text-overflow: ellipsis;
      color: #34b5eb;
      text-shadow: none;
      font-weight: bolder;
    }
    
    .cryptotext-primary {
      color: orange;
      font-size: 18px;
      margin-block-start: 0.1em;
      margin-block-end: 0.1em;
      text-shadow: none;
    }
    
    .cryptotext-secondary {
      color: #20acea;
    }
    
    .cryptotext-grey {
      color: #5c6776;
    }
    
    .cryptotext-bright {
      color: #34b5eb;
      /*color: #f0f0f0;
         color: #1567d8; */
      margin-block-start: 0.1em;
      margin-block-end: 0.1em;
      text-shadow: none;
    }
    
    .cryptotext-faded {
      color: #004eb9;
      opacity: 0.5;
    }
    
    .cryptotext-big {
      font-size: 120%;
      line-height: 1.212em;
    }
    
    .cryptotext-small {
      font-size: 70%;
      line-height: 1.14em;
    }
    
    .cryptotext-condense {
      letter-spacing: -1px;
    }
    
    .cryptoshadow-box {
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    }
    
    .cryptoshadow-text {
      text-shadow: 0px 1px 0px rgba(0, 0, 0, 0.3), 1px 0px 0px rgba(0, 0, 0, 0.3);
    }
    
    .cryptoform-input {
      display: flex;
      flex-direction: row;
      flex-wrap: nowrap;
      align-items: center;
      padding: 0.7em 1em;
      color: #f0f0f0;
      background-color: #1567d8;
      border-radius: 100px;
      -moz-border-radius: 100px;
      -webkit-border-radius: 100px;
    }
    
    .cryptoform-input.dark {
      background-color: #000;
    }
    
    .cryptoform-input>input {
      width: auto;
      border-radius: 15px;
      -moz-border-radius: 15px;
      -webkit-border-radius: 15px;
      border: 0;
      padding: 5px;
      color: #1567d8;
    }
    
    @keyframes dropdownShow {
      0% {
        transform: translateY(30px);
        opacity: 0;
      }
      100% {
        transform: translateY(0);
        opacity: 1;
      }
    }
    
    .cryptodropdown {
      display: block;
      position: relative;
      cursor: pointer;
    }
    
    .cryptodropdown>ul {
      z-index: 999;
      display: none;
      list-style: none;
      position: absolute;
      transition: none;
      animation: dropdownShow 300ms cubic-bezier(0.215, 0.61, 0.355, 1) forwards;
      top: 50%;
      min-width: 200px;
      max-width: 400px;
      padding: 0.5em 0;
      background-color: rgba(21, 103, 216, 0.19);
      border-radius: 4px;
      -moz-border-radius: 4px;
      -webkit-border-radius: 4px;
      -webkit-backdrop-filter: blur(0.18em);
      backdrop-filter: blur(0.18em);
    }
    
    .cryptodropdown>ul>li {
      display: block;
      padding: 0.5em 1em;
      background-color: rgba(0, 0, 0, 0);
      font-weight: bolder;
      color: #fff;
      text-shadow: #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px, #004eb9 0px 0px 1px;
      -webkit-font-smoothing: antialiased.;
      cursor: pointer;
    }
    
    .cryptodropdown .cryptotext-faded {
      opacity: 1;
      color: #fff;
      font-weight: bolder
    }
    
    .cryptodropdown>ul>li+li {
      border-top: 2px solid rgba(255, 255, 255, 0.50);
    }
    
    .cryptodropdown>ul>li:hover {
      background-color: rgba(0, 0, 0, 0.1);
    }
    
    .cryptodropdown:hover>ul,
    .cryptodropdown:active>ul {
      display: block;
    }
    
    .cryptoheader-wrap {
      position: fixed;
      left: 0;
      top: 0;
      width: 100%;
      background-color: #1e2126;
      background-image: radial-gradient(ellipse at top, rgba(255, 255, 255, 0.1) 0%, transparent 60%);
      box-shadow: 0 1px 8px rgba(0, 0, 0, 0.6);
      z-index: 999;
    }
    
    .cryptoheader-wrap .cryptoheader-row {
      height: 4em;
      padding: 1em;
    }
    
    .cryptoheader-wrap .cryptoheader-row .cryptodropdown {
      margin-left: 0.4em;
    }
    
    .cryptomain-wrap {
      position: relative;
      padding: 1px;
      overflow-x: hidden;
    }
    
    .cryptomain-wrap .cryptomain-grid-list {
      display: grid;
      grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
      grid-gap: 0.7em;
      grid-auto-flow: column;
      padding-bottom: 4px;
      transform: translateX(100%);
      animation: marquee 210s infinite linear;
    }
    
    .cryptomain-grid-list:hover {
      animation-play-state: paused;
    }
    
    .cryptomain-wrap .cryptomain-grid-list::-webkit-scrollbar {
      width: 12px;
    }
    
    .cryptomain-wrap .cryptomain-grid-list::-webkit-scrollbar-track {
      -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
      border-radius: 10px;
    }
    
    .cryptomain-wrap .cryptomain-grid-list::-webkit-scrollbar-thumb {
      border-radius: 10px;
      -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.5);
    }
    
    @media only screen and (min-width: 420px) {
      .cryptomain-wrap .cryptomain-grid-list {
        grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
      }
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item .cryptomain-grid-chart {
      background-color: #f3f3f3;
      position: absolute;
      width: 100%;
      height: 100%;
      z-index: -2;
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item {
      background-color: transparent;
      position: relative;
      border-radius: 4px;
      -moz-border-radius: 4px;
      -webkit-border-radius: 4px;
      border-left: solid 7px #1567d8;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptogain {
      background-color: transparent;
    }
    
    
    /* Cor do linha de ganho */
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptogain polyline.cryptocolor {
      stroke: rgba(50, 205, 50, 0.07);
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptogain circle.cryptocolor {
      fill: #32cd32;
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptogain .cryptocolor {
      color: #32cd32;
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptoloss {
      background-color: transparent;
    }
    
    
    /* Cor do linha de perda */
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptoloss polyline.cryptocolor {
      stroke: rgb(220, 20, 60, 0.04);
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptoloss circle.cryptocolor {
      fill: #dc143c;
    }
    
    
    /* Cor do texto de perda */
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item.cryptoloss .cryptocolor {
      color: #dc143c;
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item .cryptomain-grid-info {
      padding: 0.7em 1em;
    }
    
    .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item .cryptomain-grid-info img {
      width: auto;
      height: 16px;
    }
    
    @media only screen and (min-width: 420px) {
      .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item .cryptomain-grid-info img {
        height: 20px;
      }
    }
    
    @media only screen and (min-width: 720px) {
      .cryptomain-wrap .cryptomain-grid-list .cryptomain-grid-item .cryptomain-grid-info img {
        height: 32px;
      }
    }
    
    .cryptoloader-wrap {
      display: none;
      flex-direction: row;
      justify-content: center;
      align-items: center;
      position: relative;
      left: 0;
      top: 0;
      width: 100%;
      height: 100%;
      background-color: rgba(0, 0, 0, 0.8);
      text-align: center;
      z-index: 9999;
    }
    
    .cryptoloader-wrap.cryptovisible {
      display: flex;
    }
    
    .cryptoloader-wrap .cryptoloader-content {
      padding: 1em 2em;
      background-color: #1e2126;
      border-radius: 4px;
      -moz-border-radius: 4px;
      -webkit-border-radius: 4px;
      box-shadow: 0 1px 2px rgba(0, 0, 0, 0.3);
    }
    
    .cryptoloader-wrap .cryptoloader-content i {
      font-style: normal;
      font-size: 600%;
      line-height: normal;
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
    <!-- vue root -->
    <div id="app" v-cloak>
      <!-- price list grid -->
      <main class="cryptomain-wrap">
        <div class="cryptomain-grid-list">
          <div class="cryptomain-grid-item marquee-text-text" v-for="c in coinsList" :key="c.symbol" :class="c.style">
            <div class="cryptomain-grid-chart">
              <linechart :width="190" :height="50" :values="c.history"></linechart>
            </div>
            <div class="cryptomain-grid-info cryptoflex-row cryptoflex-top cryptoflex-stretch">
              <div class="cryptopush-right">
                <img :src="c.icon" :alt="c.pair" onerror="this.onerror=null;this.src='/123/wp-content/imagenspersonalizadas/lb-crypto-nopic.png';" />
              </div>
              <div class="cryptoflex-1 cryptoshadow-text">
                <div class="cryptoflex-row cryptoflex-top cryptoflex-space">
                  <div class="cryptotext-left cryptotext-clip cryptopush-right">
                    <h1 class="cryptotext-primary cryptotext-clip">{{ c.token }}<small class="cryptotext-faded cryptotext-small text-condense">/{{ c.asset }}</small></h1>
                    <h2 class="cryptotext-bright cryptotext-clip">{{ c.close | toFixed( asset ) }}</h2>
                  </div>
                  <div class="cryptotext-right">
                    <div class="cryptocolor cryptotext-big cryptotext-clip">{{ c.arrow }} {{ c.sign }}{{ c.percent | toFixed( 2 ) }}%</div>
                    <div class="cryptotext-clip">{{ c.sign }}{{ c.change | toFixed( asset ) }} <small class="cryptotext-faded">24h</small></div>
                    <div class="cryptotext-clip">{{ c.assetVolume | toMoney }} <small class="cryptotext-faded">Vol</small></div>
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </main>
      <!-- socket loader -->
      <div class="cryptoloader-wrap" :class="{ 'visible': loaderVisible }">
        <div class="cryptoloader-content">
          <div v-if="status === 0"><i>?</i> <br /> Connecting to Socket API ...</div>
          <div v-else-if="status === 1"><i>?</i> <br /> Waiting for data from Socket API ...</div>
          <div v-else-if="status === 2"><i>?</i> <br /> Connected to the Socket API</div>
          <div v-else-if="status === -1"><i>?</i> <br /> Error connecting to the Socket API</div>
        </div>
      </div>
    </div>

    有点紧张,但我认为这可能是套接字更新或其他问题,不确定。

    分解其工作原理:

    将动画添加到.cryptomain-grid-listanimation: marquee 210s infinite linear,无限循环,因此它是线性的,所以从右到左运动是恒定的。

    然后在挂载的钩子里:

        const gridList = document.querySelector('.cryptomain-grid-list');
        let gridListWidth = 0;
        const gridListInterval = setInterval(() => {
    
          if (gridList.children.length > 0) {
    
            for (let i of gridList.children) {
              console.log(i.offsetWidth)
              gridListWidth += 311.2;
            }
    
            const cssAnimation = document.createElement('style');
            cssAnimation.type = 'text/css';
            const rules = document.createTextNode(
              `@keyframes marquee {
                0% { transform: translateX(100%); }
                100% { transform: translateX(${-Math.abs(gridListWidth)}px); }
              }`);
            cssAnimation.appendChild(rules);
            document.getElementsByTagName("head")[0].appendChild(cssAnimation);
            clearInterval(gridListInterval);
          }
        }, 1)
    

    设置一个变量来计算每个网格项的宽度,添加setInterval 以持续运行直到套接字加载数据,然后添加每个项的宽度(300px)加上间隙的宽度( 0.7em 是 11.2px) 到计数变量gridListWidth

    获得网格宽度后,创建一个定义动画的style 元素并将其附加到文档头部。

    希望这是有道理的,至少可以为您指明正确的方向!

    【讨论】:

    • 这都是vue创建的,没有jQuery?
    • 我没有添加或删除任何 jQuery,所以除了已经存在的,它只是 Vue 和 CSS
    • 我对 Vue Js 还是很陌生,直到最近我才意识到它对 DOM 有多么渴望。您的代码按我的需要工作。你能告诉我如何创建play/pausepreviousnext 按钮吗?还有,有什么办法可以把bar的两端连接起来,这样动画结束的时候就不会有很大的差距了?
    • 悬停时也暂停。
    • 悬停暂停很简单:.cryptomain-grid-list:hover { animation-play-state: paused; },已添加到我的帖子中
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-10-15
    • 1970-01-01
    • 1970-01-01
    • 2019-12-05
    • 1970-01-01
    • 1970-01-01
    • 2019-03-15
    相关资源
    最近更新 更多