Vue js выделение почти готово, но нужно знать, как дублировать часть содержимого - PullRequest
0 голосов
/ 06 августа 2020

Вот чего я пытаюсь добиться, так это эффекта выделения, я почти его делаю, за исключением одной детали: я заметил, что для того, чтобы выделение работало, первый элемент должен быть повторен до конца список.

На данный момент список выглядит так: | 1 | 2 | 3 | 4 | 5 | 6 | 7 |

И он должен быть таким: | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 1 |

Как видите, первый элемент должен быть продублирован, чтобы он появился в конце списка, который прокручивается справа налево.

Это это моя скрипка: https://jsfiddle.net/jackbauer/zh2sjpr6/3/

А это мой код:

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: '#appcrypto',

  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:18, // 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();
/* THIS IS HOW THE ANIMATION WAS BEING GENERATED BEFORE - I'LL KEEP THIS HERE JUST IN CASE I NEED IT LATER (PROBABLY WON'T): */
/*
    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(`
        // NEW CODE 
        .cryptomain-grid-list {  
        animation: scrollleftcrypto 10s linear infinite;
        -webkit-animation:  scrollleftcrypto 10s linear infinite;
        -moz-animation: scrollleftcrypto 10s linear infinite; 
        -ms-animation: scrollleftcrypto 10s linear infinite;
        -o-animation: scrollleftcrypto 10s linear infinite; 
        animation: scrollleftcrypto 10s linear infinite;}
        // END NEW CODE
        @keyframes scrollleftcrypto {
    0% { transform: translateX(0%); }
    100% { transform: translateX(${-Math.abs(gridListWidth)}px); }
}
`);
        cssAnimation.appendChild(rules);
        document.getElementsByTagName("head")[0].appendChild(cssAnimation);
        clearInterval(gridListInterval);
      }
    }, 1)*/
/* jQuery code should be inserted here, in the Mounted. */      
  },
  destroyed() {
    this.sockClose();
  }
});

Как я могу продублировать первый элемент и сделать так, чтобы он появился в конце?

...