iT邦幫忙

0

jQuery 動畫的問題

builder 5 年前10058 瀏覽
<meta charset="utf-8" />
<!-- jQuery -->
<script type="text/javascript" src="js/jquery-1.6.4.min.js"></script>

<!-- CSS -->
<style type="text/css">
div
{
	background:#aaa;
	width:18px;
	height:18px;
	position:absolute;
	top:10px;
}
</style>

<!-- JavaScript -->
<script>
var funs = [
	function() {
		$("#block1").animate({left:"+=100"}, 3000, goNext);
	},
	function() {
		$("#block1").css("background","#ff0000");
		//暫停 3 秒 (模擬要做 3 秒的事情)
		sleep(3000);
		goNext();
	},
	function() {
		$("#block1").animate({left:"+=100"},3000);
	}
];

$(document).ready(function(){
	goNext();
});

function goNext()
{
	var fun = funs.shift();
	fun();
}

function sleep(milliseconds)
{
	var start = new Date().getTime();
	while(1)
	{
		if ((new Date().getTime() - start) > milliseconds)
		{
			break;
		}
	}
}
</script>



	<div id="block1"></div>

若把 sleep 註解掉就正常了~~ Why??

看更多先前的討論...收起先前的討論...
ccutmis iT邦新手 1 級 ‧ 5 年前 檢舉
    function() {   
        $("#block1").css("background","#ff0000");   
        //暫停 3 秒 (模擬要做 3 秒的事情)   
        $("#block1").delay(3000); //用jQuery本身有的函式就行了~==?
        //sleep(3000);   
        goNext();   
    }...略...
builder iT邦新手 2 級 ‧ 5 年前 檢舉
hi~ ccutmis 你好!~
我想要的效果是
(1)執行動畫1
(2)作一些非動畫的事情 (不知道要幾秒)
(3)執行動畫2
按照 (1) -> (2) -> (3) 的次序來執行~

所以 delay() 不適合...
ccutmis iT邦新手 1 級 ‧ 5 年前 檢舉
那把function sleep改寫一下試看看?...
function sleep( milliseconds ) {
	var timer = new Date();
	var time = timer.getTime();
	do{
		timer = new Date();
	}while( (timer.getTime() - time) < (milliseconds) );
}

我這邊用IE8測試是OK的...-_-"
builder iT邦新手 2 級 ‧ 5 年前 檢舉
還是不行耶 IE 9 FireFox 8 chrome 15 都不正確....
wordsmith iT邦高手 1 級 ‧ 5 年前 檢舉
我在Chrome 15.x測,你的程式碼先是往右跑,然後停下來執行sleep,之後變紅又繼續往又跑,所以不知道你說不正確到底是哪裡不正確,有點不太清楚。
builder iT邦新手 2 級 ‧ 5 年前 檢舉
我希望的動作是
先往右跑100px(動畫1) => 變紅色 => 暫停三秒鐘 => 再往右跑100px(動畫2)
我測試的結果
先往右跑100px(動畫1) => 變紅色 => 暫停三秒鐘 => 直接出現在最後的位置(也就是200px的位置) (重點是它是直接出現的 並非動畫)
ccutmis iT邦新手 1 級 ‧ 5 年前 檢舉
會否是瀏覽器版本問題或是電腦跑不動...?
目前的sleep作法是很耗資源=="
ccutmis iT邦新手 1 級 ‧ 5 年前 檢舉
認同您說的...=_="
wordsmith iT邦高手 1 級 ‧ 5 年前 檢舉
目前發現和jquery的版本有關,如果用http://ajax.googleapis.com/ajax/libs/jquery/1.4.2/jquery.min.js這個版本,效果就會如builder所預期,但不管是用1.6.4用最新的1.7.x,都會出現瞬間移動的現象。

所以各位兄弟們,請往這個方向找問題吧。
ccutmis iT邦新手 1 級 ‧ 5 年前 檢舉
大大真的是太強大了~~我確實是用1.4.2的版本測而已...

1 個回答

10
fillano
iT邦超人 1 級 ‧ 5 年前
最佳解答

瀏覽器reflow/render的速度跟Javascript執行的速度差很多,你用同步的方式實作sleep,會影響到render的執行,所以方格來不及變成紅色,位置也沒有用動畫的方式改變。

像這樣的動作穿插,最好用非同步的方式來做。例如把while改成setTimeout,像這樣:

&lt;pre class="c" name="code">




&lt;meta charset="utf-8" />
&lt;!-- jQuery -->
&lt;script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.6.4/jquery.min.js">&lt;/script>

&lt;!-- CSS -->
&lt;style type="text/css">
div
{
	background:#aaa;
	width:18px;
	height:18px;
	position:absolute;
	top:10px;
}
&lt;/style>

&lt;!-- JavaScript -->
&lt;script>
var funs = [
	function() {
		$("#block1").animate({left:"+=100"}, 3000, goNext);
	},
	function() {
		$("#block1").css("background","#ff0000");
		setTimeout(goNext, 3000);
	},
	function() {
		$("#block1").animate({left:"+=100"},3000);
	}
];

$(document).ready(function(){
	goNext();
});

function goNext()
{
    funs.shift()();
}
&lt;/script>



	&lt;div id="block1">&lt;/div>
看更多先前的回應...收起先前的回應...
wordsmith iT邦高手 1 級 ‧ 5 年前 檢舉

經過測試,費大公的改作可以正確執行了,不受jQuery版本的影響。讚

瀏覽器reflow/render 的速度,這個課題以前都沒有想過耶,費大公可以多說一點,或是哪裡有相關的資料嗎?

builder iT邦新手 2 級 ‧ 5 年前 檢舉

請問 fillano 大
若我想要的效果是
(1)執行動畫1
(2)作一些非動畫的事情 (不知道要幾秒)
(3)執行動畫2
按照 (1) -> (2) -> (3) 的時序來執行的話~
要怎做比較好呢?

builder iT邦新手 2 級 ‧ 5 年前 檢舉

畢竟不管是 delay 還是 setTimeout
都是已經指明要延遲幾秒..無法代表做一些計算的事情(因為畢竟計算的時間無法估計...因User的電腦等級而定)

builder iT邦新手 2 級 ‧ 5 年前 檢舉

有一點我一直很不能理解的是
不知道大家有沒有注意到下列兩行

&lt;pre class="c" name="code">sleep(3000);   
goNext();

sleep 是放在 goNext 之前,而 sleep 函數本身是用迴圈實現的 (故會造成 javascript Blocking)
也就是說必須在 sleep 完全執行結束後 才有可能執行 goNext 來加入動畫!

換句話說動畫一定是在 sleep 完全結束後才有可能被加入並執行的~
所以結果理論上次序一定是
(1)加入並執行 "動畫1" (向左移動)
(2)變紅色 並 sleep 3 秒
(3)最後才是加入並執行 "動畫2" (向左移動)

但是真正的結果卻像是第 3 步在第 2 步還沒執行完畢就已經偷跑了

fillano iT邦超人 1 級 ‧ 5 年前 檢舉

這個部份其實跟Javascript的基本執行機制有關係。

Javascript是以函數為執行的基本單位(函數裡面執行其他函數時,也算在同一個函數內),一個函數沒有執行完畢前,不會做其他的動作(函數)。你程式裡面真正的動作是執行兩次goNext()(第三個goNext是在第二個goNext之中執行的,所以只能算一個),第一次執行完畢後,瀏覽器有機會做reflow,但是第二次的動作其實都包在一個函數中,動作在Javascript中執行完成時,瀏覽器還來不及reflow,所以你只看到funs[1]跟funs[2]中兩個動作的結果(中間過程來不及跑了)。

所以要用Javascript做需要在精確時間完成的動畫,你會需要使用許多的setTimeout,並且在動作中做計時,如果時機過了就要放棄動作,進入下一格。當然,像這樣的動作你可以用alert就把他破壞掉。

fillano iT邦超人 1 級 ‧ 5 年前 檢舉

我做的事情是:

builder iT邦新手 2 級 ‧ 5 年前 檢舉

fillano 大:

fillano提到:
Javascript是以函數為執行的基本單位(函數裡面執行其他函數時,也算在同一個函數內),一個函數沒有執行完畢前,不會做其他的動作(函數)。

這是真的嗎....!?

我寫了下面的程式來測試

&lt;pre class="c" name="code">

&lt;meta charset="utf-8" />
&lt;script type="text/javascript" src="js/jquery-1.6.4.min.js">&lt;/script>   

&lt;script>
$(document).ready(function(){
	fun1();
})

function fun1()
{
	fun2();

	var tmp=0;
	for(var i=0; i&lt;100000; i++)
	{
		tmp += i*i;
	}
	
	var date = new Date();
	$("#block1").html(date.getTime());
}

function fun2()
{
	var date = new Date();
	$("#block2").html(date.getTime());
}
&lt;/script>


	&lt;div id="block1">&lt;/div>
	&lt;div id="block2">&lt;/div>

結果是 block1 的時間大於 block2~
換句話說 javascript 在 fun1 內 還是會先執行完 fun2 才會往下執行耶~
跟你講的好像不太一樣

builder iT邦新手 2 級 ‧ 5 年前 檢舉

我修改了原始的程式~ 加上時間的顯示..來釐清執行順序問題...
可惜 code 字數的關係貼不上來~
但結果顯示 真的是跑完 sleep() 後才往下繼續執行的
我猜想有沒有可能是 jQuery Animate 的關係
if( 第二次動畫的開始時間 - 第一次動畫的結束時間 > 3秒) //也就是jQuery 認為動畫逾時了
{
就直接跳到動畫最後的結果 而不再表演動畫
}

builder iT邦新手 2 級 ‧ 5 年前 檢舉

新版的 jQuery 太過聰明..會去計算 動畫逾時 了幾秒...然後決定應該從哪表演起...
所以 聰明反被聰明誤... 反而造成 達不到我要的效果...
反而是舊版ㄉ jQuery 可以達到....orz...

fillano iT邦超人 1 級 ‧ 5 年前 檢舉

前面這樣講得也不太對。我trace了一下jQuery.fn.animate的執行過程,看起來他的運作方式如下:

  1. 初始化一些資料,包括開始時間
  2. 用setInterval每間隔13 milisecond執行動作
  3. 動作是用與開始時間的差距以及duration的比例算出來的
  4. 如果時間差超過duration,就會停止執行

所以,需要注意到適時用非同步的方式執行程式,這樣才能讓setInterval有機會執行,不然就有可能一開始執行setInterval時,時間已經過了,所以只跑出結果。

builder iT邦新手 2 級 ‧ 5 年前 檢舉

我覺得有爭議的地方在於動畫開始時間的定義~
我猜舊版 jQuery 定義動畫2的開始時間是動畫2剛被加入的時間~
但新版 jQuery 卻把動畫1的結束時間當作是動畫2的開始時間~ orz

builder iT邦新手 2 級 ‧ 5 年前 檢舉

這或許是因為我把動畫2 寫在動畫1的 finish callback function 的關係...

fillano iT邦超人 1 級 ‧ 5 年前 檢舉

光是用時間這樣看不準啦,建議用開發工具的console.log來標示(在函數開始與最後顯示開始與結束)

我前面的圖不太對,你執行的過程應該是:

另外,把第一個funs改成這樣:

&lt;pre class="c" name="code">$("#block1").animate({left:"+=100"}, 3000, function(){console.log('animation 1 end');setTimeout(goNext, 0);});

效果就都會出來。

builder iT邦新手 2 級 ‧ 5 年前 檢舉

還是 fillano 大聰明~
中間多加一個 setTimeout 就把問題搞定了~ 我怎沒想到..Orz 我還是太嫩了..

wordsmith iT邦高手 1 級 ‧ 5 年前 檢舉

builder提到:
還是 fillano 大聰明~

拍手拍手拍手

總裁 iT邦好手 1 級 ‧ 5 年前 檢舉

看來費大6萬拿定了...簽名
咦??這裡不是鐵人賽頒獎會場嗎??暈

我要發表回答

立即登入回答