我正在使用以下代码在网页的顶层上滑动图像,但它有点紧张,在图像上留下条纹垂直线,特别是当有多个嵌套元素的内容时。即使边框设置为零,也是如此。有关使用JS / CSS滑动图像的更平滑方法的任何建议吗?
border=4;
pps=250; // speed of glide (pixels per second)
skip=2; // e.g. if set to 10 will skip 9 in 10 pixels
refresh=3; // how often looks to see if move needed in milliseconds
elem = document.createElement("img");
elem.id = 'img_id';
elem.style.zIndex="2000";
elem.style.position="fixed";
elem.style.top=0;
elem.style.left=0;
elem.src='http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg';
elem.style.border=border+'px solid black';
elem.style.cursor='pointer';
document.body.insertBefore(elem,null);
pos_start = -250;
pos_current = pos_start;
pos_finish = 20000;
var timer = new Date().getTime();
move();
function move ()
{
var elapsed = new Date().getTime() - timer;
var pos_new = Math.floor((pos_start+pps*elapsed/1000)/skip)*skip;
if (pos_new != pos_current)
{
if (pos_new>pos_finish)
pos_new=pos_finish;
$("#img_id").css('left', pos_new);
if (pos_new==pos_finish)
return;
pos_current = pos_new;
}
t = setTimeout("move()", refresh);
}
答案 0 :(得分:9)
我没有解决方案,我确信会阻止垂直线出现 但是我有一些提示来改进你的代码,以便提高性能,你可能会有线条消失。
将图像元素缓存到移动函数之外:
var image = $("#img_id")[0];
在您的代码中,没有理由每3毫秒对DOM查询一次图像ID。 jQuery的选择器引擎,Sizzle需要做很多工作¹。
不要使用jQuery CSS函数:
image.style.left = pos_new;
设置属性对象比函数调用更快。对于jQuery css
函数,至少有两个函数调用(一个到css
,一个在css
内。)
使用interval而不是timeout:
setInterval(move, refresh);
我会考虑一个我想成为的一次性动画的间隔 尽可能顺利
更平滑动画的另一个选项是使用CSS过渡或动画。 John Resig在CSS Animations and JavaScript中可以找到一个很好的介绍和比较
浏览器支持表:http://caniuse.com/#search=transition
我发现一个JavaScript库,通过JavaScript很容易实现CSS动画morpheus。
¹在引擎盖下,这是每3毫秒查找一次图像的代码:
在支持querySelectorAll的浏览器中:
Sizzle = function( query, context, extra, seed ) {
context = context || document;
// Only use querySelectorAll on non-XML documents
// (ID selectors don't work in non-HTML documents)
if ( !seed && !Sizzle.isXML(context) ) {
// See if we find a selector to speed up
var match = /^(\w+$)|^\.([\w\-]+$)|^#([\w\-]+$)/.exec( query );
if ( match && (context.nodeType === 1 || context.nodeType === 9) ) {
// Speed-up: Sizzle("TAG")
if ( match[1] ) {
return makeArray( context.getElementsByTagName( query ), extra );
// Speed-up: Sizzle(".CLASS")
} else if ( match[2] && Expr.find.CLASS && context.getElementsByClassName ) {
return makeArray( context.getElementsByClassName( match[2] ), extra );
}
}
if ( context.nodeType === 9 ) {
// Speed-up: Sizzle("body")
// The body element only exists once, optimize finding it
if ( query === "body" && context.body ) {
return makeArray( [ context.body ], extra );
// Speed-up: Sizzle("#ID")
} else if ( match && match[3] ) {
var elem = context.getElementById( match[3] );
// Check parentNode to catch when Blackberry 4.6 returns
// nodes that are no longer in the document #6963
if ( elem && elem.parentNode ) {
// Handle the case where IE and Opera return items
// by name instead of ID
if ( elem.id === match[3] ) {
return makeArray( [ elem ], extra );
}
} else {
return makeArray( [], extra );
}
}
try {
return makeArray( context.querySelectorAll(query), extra );
} catch(qsaError) {}
// qSA works strangely on Element-rooted queries
// We can work around this by specifying an extra ID on the root
// and working up from there (Thanks to Andrew Dupont for the technique)
// IE 8 doesn't work on object elements
} else if ( context.nodeType === 1 && context.nodeName.toLowerCase() !== "object" ) {
var oldContext = context,
old = context.getAttribute( "id" ),
nid = old || id,
hasParent = context.parentNode,
relativeHierarchySelector = /^\s*[+~]/.test( query );
if ( !old ) {
context.setAttribute( "id", nid );
} else {
nid = nid.replace( /'/g, "\\$&" );
}
if ( relativeHierarchySelector && hasParent ) {
context = context.parentNode;
}
try {
if ( !relativeHierarchySelector || hasParent ) {
return makeArray( context.querySelectorAll( "[id='" + nid + "'] " + query ), extra );
}
} catch(pseudoError) {
} finally {
if ( !old ) {
oldContext.removeAttribute( "id" );
}
}
}
}
return oldSizzle(query, context, extra, seed);
};
并且浏览器没有:
var Sizzle = function( selector, context, results, seed ) {
results = results || [];
context = context || document;
var origContext = context;
if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
return [];
}
if ( !selector || typeof selector !== "string" ) {
return results;
}
var m, set, checkSet, extra, ret, cur, pop, i,
prune = true,
contextXML = Sizzle.isXML( context ),
parts = [],
soFar = selector;
// Reset the position of the chunker regexp (start from head)
do {
chunker.exec( "" );
m = chunker.exec( soFar );
if ( m ) {
soFar = m[3];
parts.push( m[1] );
if ( m[2] ) {
extra = m[3];
break;
}
}
} while ( m );
if ( parts.length > 1 && origPOS.exec( selector ) ) {
if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
set = posProcess( parts[0] + parts[1], context, seed );
} else {
set = Expr.relative[ parts[0] ] ?
[ context ] :
Sizzle( parts.shift(), context );
while ( parts.length ) {
selector = parts.shift();
if ( Expr.relative[ selector ] ) {
selector += parts.shift();
}
set = posProcess( selector, set, seed );
}
}
} else {
// Take a shortcut and set the context if the root selector is an ID
// (but not if it'll be faster if the inner selector is an ID)
if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
ret = Sizzle.find( parts.shift(), context, contextXML );
context = ret.expr ?
Sizzle.filter( ret.expr, ret.set )[0] :
ret.set[0];
}
if ( context ) {
ret = seed ?
{ expr: parts.pop(), set: makeArray(seed) } :
Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
set = ret.expr ?
Sizzle.filter( ret.expr, ret.set ) :
ret.set;
if ( parts.length > 0 ) {
checkSet = makeArray( set );
} else {
prune = false;
}
while ( parts.length ) {
cur = parts.pop();
pop = cur;
if ( !Expr.relative[ cur ] ) {
cur = "";
} else {
pop = parts.pop();
}
if ( pop == null ) {
pop = context;
}
Expr.relative[ cur ]( checkSet, pop, contextXML );
}
} else {
checkSet = parts = [];
}
}
if ( !checkSet ) {
checkSet = set;
}
if ( !checkSet ) {
Sizzle.error( cur || selector );
}
if ( toString.call(checkSet) === "[object Array]" ) {
if ( !prune ) {
results.push.apply( results, checkSet );
} else if ( context && context.nodeType === 1 ) {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
results.push( set[i] );
}
}
} else {
for ( i = 0; checkSet[i] != null; i++ ) {
if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
results.push( set[i] );
}
}
}
} else {
makeArray( checkSet, results );
}
if ( extra ) {
Sizzle( extra, origContext, results, seed );
Sizzle.uniqueSort( results );
}
return results;
};
答案 1 :(得分:4)
有许多小方法可以调整代码以使运行稍微平滑...使用反馈循环来优化步长和延迟,查找不会向上或向下舍入的步骤,导致定期跳转小跳等等。
但是您可能正在寻找的秘密API(以及您正在避免的许多库使用的秘密API)是requestAnimationFrame。它目前是非标准化的,因此每个浏览器都有一个前缀实现(webkitRequestAnimationFrame,mozRequestAnimationFrom等)
我不会重新解释它如何帮助减少/防止撕裂和vsync问题,而是指向文章本身:
http://robert.ocallahan.org/2010/08/mozrequestanimationframe_14.html
答案 2 :(得分:3)
我在考虑一些想法的情况下对此进行了拍摄。我永远不会让动画变得非常不平滑,我也没有经历任何垂直线条,所以我不确定它是否甚至是一种改进。然而,下面的函数考虑了一些对我有意义的关键想法:
使用容器<div>
保持元素远离DOM,以用于动画。重绘的DOM参与使得它比基本的叠加动画要长得多。
尽可能多地保留move
功能。看到这个函数将被大量调用,运行的脚本越少越好。这包括用于更改元素位置的jQuery调用。
只刷新绝对必要的内容。我将刷新间隔设置为121 Hz,但这对于60Hz显示器来说是绝对的高端。我可能建议61或更少,具体取决于需要。
如果需要,只在元素样式对象中设置一个值。问题中的函数确实这样做了,但再次记住这是一件好事,因为在某些引擎中,只需访问样式对象中的setter就会强制重新绘制。
我想要尝试的是使用图像作为元素的背景,因此您可以编写脚本来更改CSS background-position
属性而不是更改元素位置。如果可能的话,这将意味着动画触发重绘中的DOM参与损失。
这个函数,对于你的测试,有一个相当不必要的闭包:
var border = 4;
var pps = 250;
var skip = 2;
var refresh = 1000 / 121; // 2 * refresh rate + 1
var image = new Image();
image.src = 'http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg';
// Move img (Image()) from x1,y1 to x2,y2
var moveImage = function (img, x1, y1, x2, y2) {
x_min = (x1 > x2) ? x2 : x1;
y_min = (y1 > y2) ? y2 : y1;
x_max = (x1 > x2) ? x1 : x2;
y_max = (y1 > y2) ? y1 : y2;
var div = document.createElement('div');
div.id = 'animationDiv';
div.style.zIndex = '2000';
div.style.position = 'fixed';
div.style.top = y_min;
div.style.left = x_min;
div.style.width = x_max + img.width + 'px';
div.style.height = y_max + img.height + 'px';
div.style.background = 'none';
document.body.insertBefore(div, null);
elem = document.createElement('img');
elem.id = 'img_id';
elem.style.position = 'relative';
elem.style.top = 0;
elem.style.left = 0;
elem.src = img.src;
elem.style.border = border + 'px solid black';
elem.style.cursor = 'pointer';
var theta = Math.atan2((y2 - y1), (x2 - x1));
(function () {
div.insertBefore(elem, null);
var stop = function () {
clearInterval(interval);
elem.style.left = x2 - x1;
elem.style.top = y2 - y1;
};
var startTime = +new Date().getTime();
var xpmsa = pps * Math.cos(theta) / (1000 * skip); // per milli adjusted
var ypmsa = pps * Math.sin(theta) / (1000 * skip);
var interval = setInterval(function () {
var t = +new Date().getTime() - startTime;
var x = (Math.floor(t * xpmsa) * skip);
var y = (Math.floor(t * ypmsa) * skip);
if (parseInt(elem.style.left) === x &&
parseInt(elem.style.top) === y) return;
elem.style.left = x + 'px';
elem.style.top = y + 'px';
if (x > x_max || x < x_min || y > y_max || y < y_min) stop();
}, refresh);
console.log(xpmsa, ypmsa, elem, div, interval);
})();
};
答案 3 :(得分:3)
根据您的情况,您应该考虑以下因素以使动画更流畅:
动画步骤之间的间隔(您的refresh
值)应该足够长,以便浏览器处理(JavaScript代码,渲染)。根据我的经验,它应该是10到20毫秒。
为什么您将图像位置设为skip
的倍数?将skip
值设置得尽可能小(1)可以使动画更流畅。
尽可能避免导致浏览器重排(reflow vs repaint)。
使用适当的缓动方法而不是线性(如在代码中)可以使动画看起来更好(人类视觉,而不是技术)
优化每个动画步骤的JavaScript代码。这在你的简单动画中不是问题,但你可以改进一些东西,例如:使用setInterval而不是setTimeout,缓存图像对象以便快速访问,使用原生JS代码来改变图像位置
希望这些帮助。
答案 4 :(得分:3)
您的问题似乎与浏览器呈现引擎及其功能有关。正如您所注意到的,浏览器渲染动画的速度有限。如果达到此限制,您将看到抖动或其他“不平滑”行为。有时候会出现故障,比如没有清理部分动画或乱码部分
回到过去,任何形式的体面动画几乎都是不可能的。随着时间的推移,事情变得更好,但我仍然记得使用最微小的图像来保持我漂亮的折叠/展开菜单顺利进行。当然,现在我们已经有了硬件加速的浏览器渲染,所以你可以一次做多个动画,而且不需要担心动画很慢。
但我一直在重做一些我曾经使用过的动画,因为我的iPad(1)看起来很慢,而且渲染了一些。就像滚动一个大的div
变得非常不稳定。所以基本上,我开始调整一下:
经过一些试验和错误,这确实有效。你需要记住的是,javascript只是改变了html-elements的css属性。浏览器重新绘制了JS告诉他的内容。所以它告诉他的越多,它越重,渲染就越落后 从性能上看,它分为三个部分:CPU,GPU和屏幕更新。每个浏览器引擎的工作方式都不同,因此性能也可能不同。一个有趣的看看这是如何工作的,来自IE 10团队的人,这比我更彻底:http://blogs.msdn.com/b/ie/archive/2011/04/26/understanding-differences-in-hardware-acceleration-through-paintball.aspx
答案 5 :(得分:3)
Javascript动画总是有点紧张,因为定时器不是很精确。你可以通过一些技巧获得更好的表现:
img { -webkit-transform: translateZ(0) };
setInterval
,虽然更改通常不明显,但也可以使动画更流畅1000/60
(60pfs) - 这是屏幕限制,计时器永远不会低于4毫秒IE9 +似乎可以通过将滴答与屏幕刷新率相结合来解决这个问题,这样可以使动画更加流畅,但我不会指望其他浏览器能够很快做到这一点。未来是CSS过渡。
在CSS中,你可以使用它:
img {
-webkit-transition:2s all linear;
-moz-transition:2s all linear;
-ms-transition:2s all linear;
transition:2s all linear;
}
但是由于你的动画持续时间取决于目标位置以达到恒定速度,你可以通过JS操作值:
var img = document.createElement('img')
document.body.appendChild(img)
var styles = {
zIndex : '2000'
, position : 'absolute'
, top : '0px'
, left : '0px'
, border : '4px solid black'
, cursor : 'pointer'
}
Object.keys(styles).forEach(function(key){
img.style[key] = styles[key]
})
var prefixes = ['webkit', 'Moz', 'O', 'ms', '']
, speed = 250
, endPosition = 2000
, transition = Math.floor(endPosition/speed)+'s all linear'
prefixes.forEach(function(prefix){
img.style[prefix+(prefix ? 'T' : 't')+'ransition'] = transition
})
img.onload = function(){
img.style.left = endPosition+'px' // starts the animation
}
img.src = 'http://farm7.static.flickr.com/6095/6301314495_69e6d9eb5c_m.jpg'
(为了简洁省略了一些跨浏览器的代码路径 - onload,forEach,Object.keys)
答案 6 :(得分:0)