2011-11-05 62 views
10

我使用下面的代碼在網頁的頂層上滑動圖像,但它的一點點緊張感,給圖像帶來了條紋垂直線條,特別是當有許多嵌套元素。即使邊框設置爲零,情況也是如此。用JS/CSS滑動圖像的更平滑方法的任何建議?使用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); 
} 
+3

嘗試使用jQuery。它通常更加流暢,而且比現在做的要容易得多。 – RobinJ

+8

@RobinJ號這不是一個jQuery的問題,答案是不使用jQuery。這是一個很好的問題,因爲理解動畫的怪癖並不是很好理解。 – Incognito

+1

我開始得出結論,沒有辦法像這樣在250pps下滑動這樣的圖像而沒有一些裸奔。順便說一句,如果任何人在將來做任何類似的動畫,我會徹底推薦這個JS庫:http://fx.inetcat.com/它不能解決上述問題,但它仍然非常有用。 –

回答

9

我沒有解決方案,我肯定會防止垂直線出現。
但是,我確實有一些技巧來改善您的代碼,以便提高性能,並且您可能有線條消失的機會。

  1. 緩存您的移動功能以外的圖像元素:

    var image = $("#img_id")[0];

    在你的代碼,沒有任何理由來查詢圖像ID對每3毫秒的DOM。 jQuery的選擇器引擎,Sizzle需要很多工作¹。

  2. 不要使用jQuery的CSS功能:

    image.style.left = pos_new;

    設置屬性對象比函數調用更快。在jQuery css函數的情況下,至少有兩個函數調用(一個到css和一個在css之內)。

  3. 使用間隔,而不是超時:

    setInterval(move, refresh);

    我會考慮一次性動畫的間隔,我想是因爲 順利地進行

    setTimeout or setInterval?


平滑動畫的另一個選項是使用CSS轉換或動畫。一個偉大的介紹和對比可以CSS Animations and JavaScript由John Resig的

瀏覽器支持表中找到:http://caniuse.com/#search=transition

,我覺得讓通過JavaScript CSS動畫非常簡單JavaScript庫是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); 
}; 

而且那並不是一個瀏覽器't:

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

你得到了賞金,因爲你的答案解釋了人們有一些重大問題,比如優化不佳的代碼,以及鏈接到幾個資源。其他人有像卡西米爾和ExpExc真棒答案。每個人都應該閱讀計算機語言學家的答案,因爲他提出了一個很少使用(並且有點新)的功能,可以用來真正幫助動畫流動。 – Incognito

+0

非常感謝!我同意還有其他很棒的答案(我已經提出)。 – DADU

+0

這並不是不正確,但這裏指出的事實上並不涉及動畫性能。在這種情況下,在0-1ms範圍內,通過ID查詢元素或使用jQuery設置樣式屬性的開銷可以忽略不計。大多數瀏覽器將落入'querySelectorAll'代碼路徑。 –

3

我在腦海中想到了一些想法。我永遠無法讓動畫變得難以置信地不順滑,也沒有經歷任何垂直線條,所以我不確定這是否是一種改進。然而,下面的函數將一些關鍵的思路考慮,使感覺對我說:

  • 與動畫的容器<div>保持元件與DOM的路程。 DOM參與重繪使得它比基本的重疊動畫更長。

  • 保持儘可能多的脂肪儘可能走出move功能。看到這個函數將被稱爲大量,那麼運行的腳本越少越好。這包括jQuery調用來改變元素的位置。

  • 只有刷新不亞於絕對必要的。我將刷新間隔設置爲121赫茲,但這對於60赫茲的顯示器來說絕對是最高端的。我可能會建議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); 
     })(); 

    }; 
4

有很多小的方式來調整你的代碼運行更流暢略...使用反饋迴路來優化步長和延遲,尋找甚至步驟不圓向上或向下定期造成小跳等

但你可能尋找(並祕密API被許多的你正在避免的庫)是requestAnimationFrame。這是目前非標化,讓每個瀏覽器都有一個前綴實現(webkitRequestAnimationFrame,mozRequestAnimationFrom等。)

而不是重新解釋它如何幫助減少/防止撕裂和VSYNC的問題,我會指出你的文章本身:

http://robert.ocallahan.org/2010/08/mozrequestanimationframe_14.html

+0

對於requestAnimationFrame爲+1,但它只是整個問題的一部分。 – Incognito

+0

是的,但確實解決了調度和時間問題。其餘主要項目是(a)儘可能預先計算(b)使用背景位置,而不是在可能的情況下移動dom元素。 setInterval vs setTimeout優化是假的。而線性緩動使舍入誤差和抖動最明顯 - 最好有一些(減)加速進行。 –

+0

requestAnimationFrame在實踐中不會產生更平滑的動畫,它只會避免在不關注頁面時浪費週期。 http://www.phoboslab.org/log/2011/08/are-we-fast-yet –

3

對於你的情況,你應該考慮到以下讓動畫更流暢:

  1. 動畫步驟(您refresh值)之間的時間間隔應足夠長,對於瀏覽器處理(JavaScript代碼,渲染)。根據我的經驗,它應該是10到20毫秒。

  2. 爲什麼你做的skip圖像位置多?設置skip儘可能小的值(1)可以使動畫更平滑。

  3. 避免引起瀏覽器迴流(如果可能reflow vs repaint)。

  4. ,利用適當的寬鬆方法而不是線性的(如在你的代碼)可以使動畫更好看(人的視線,而不是技術)

  5. 優化JavaScript代碼的每個動畫一步。這不是簡單的動畫爲你的問題,但可以提高的東西,如:使用的setInterval,而不是setTimeout的,用於快速訪問緩存圖像對象,使用本地的JS代碼來改變圖像位置

希望這些幫助。

3

你的問題似乎真的是關於瀏覽器渲染引擎和自己的能力。正如您已經注意到的那樣,瀏覽器渲染動畫的速度有限。如果你達到這個限制,你會看到抖動或其他「不平滑」的行爲。有時會渲染錯誤,如不清除動畫部分或混亂部分。
早在過去,任何形式的體面動畫幾乎都是不可能的。隨着時間的推移,事情變得更好了,但我還記得使用最小的圖像來保持我的摺疊/展開菜單順利進行。當然,現在我們已經有硬件加速的瀏覽器渲染,所以你可以一次做多個動畫,並且不需要擔心動畫變慢。
但我一直重做一些動畫我用,因爲我的iPad(1)似乎呈現相當慢一些。像滾動一個大div變得相當波濤洶涌。所以基本上,我開始調下來:

  • 使用簡單的動畫,而不是複雜的,和:沒有合併動畫(如滾動褪色)
  • 減少動畫對象
  • 內的HTML元素的數
  • 製作動畫對象較小
  • 預壓儘可能
  • 創建動畫對象空間(如果可能的話,在殼體滑動或移動裝置移動一大堆其他元素)

該做的工作,一些試驗和錯誤之後。你必須記住的是,JavaScript只是改變html元素的CSS屬性。瀏覽器重新繪製了JS告訴他的內容。所以它告訴他的越多,得到的越重,渲染就落後了。看看性能,它分爲三個組件:CPU,GPU和屏幕更新。每個瀏覽器引擎的工作方式都不相同,因此性能也可能不同一個有趣的看看,這是如何工作的,來自IE 10團隊的人,這比我可以更徹底:http://blogs.msdn.com/b/ie/archive/2011/04/26/understanding-differences-in-hardware-acceleration-through-paintball.aspx

3

Javascript動畫總是有點緊張,因爲定時器不是很精確。

  • Enable hardware acceleration:您可以通過使用一些技巧得到更好一點peformance img { -webkit-transform: translateZ(0) };
  • 使用setInterval,它會導致平滑的動畫過,雖然變化通常是無法察覺
  • 設置你的刷新率1000/60(60pfs) - 這是屏幕的限制,和定時器從未低於去4ms的

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)