Vue Js无限循环选框(横条)
Posted
技术标签:
【中文标题】Vue Js无限循环选框(横条)【英文标题】:Vue Js infinite loop marquee (horizontal bar) 【发布时间】: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- 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 F*** 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: F*** Valle (www.f***-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 );
如果有人能在这里给我指点方向,我将不胜感激。提前致谢。
【问题讨论】:
【参考方案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- 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 : : :values="c.history"></linechart>
</div>
<div class="cryptomain-grid-info cryptoflex-row cryptoflex-top cryptoflex-stretch">
<div class="cryptopush-right">
<img :src="c.icon" : 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-list
:animation: 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/pause
、previous
和next
按钮吗?还有,有什么办法可以把bar的两端连接起来,这样动画结束的时候就不会有很大的差距了?
悬停时也暂停。
悬停暂停很简单:.cryptomain-grid-list:hover animation-play-state: paused;
,已添加到我的帖子中以上是关于Vue Js无限循环选框(横条)的主要内容,如果未能解决你的问题,请参考以下文章
控制台上的 Vue 警告:组件渲染函数中可能存在无限更新循环