Javascript – Tạo chuyển động với WindowAnimationTiming API

runnerThay vì đặt timeout để gọi các phương thức vẽ lại hình ảnh, cách tốt nhất mà bạn nên sử dụng để tạo các hiệu ứng chuyển động trong canvas là dùng API WindowAnimationTiming, thông qua phương thức chính là requestAnimationFrame().

Xem Demo.

setTimeout và setInterval

Cách truyền thống mà bạn dùng để tạo các hiệu ứng đồ họa chuyển động với javascript là gọi liên tục công việc update và draw sau những khoảng thời gian xác định thông qua phương thức setInterval() hoặc setTimeout(). Mỗi lần gọi, một frame (khung hình) mới sẽ được tạo ra và vẽ đè lên màn hình cũ.

Khó khăn của phương pháp này là khó xác định được giá trị thời gian thích hợp dựa trên mỗi thiết bị được sử dụng (thông thường khoảng 60 fps – frames per second). Ngoài ra với những hiệu ứng phức tạp thì việc update/draw có thể diễn ra lâu hơn so với thời gian giữa hai lần gọi.

clock-time-working

Cách tổng quát để giải quyết vấn đề trên là thực hiện tính toán dựa vào khoảng cách thời gian giữa lần gọi trước đó và hiện tại, sau đó xác định bỏ qua một vài bước draw hoặc thay đổi giá trị timeout cho phù hợp.

WindowAnimationTiming

Một tính năng mới ra đời cho phép bạn đơn giản hóa công việc này là API WindowAnimationTiming.

Đây là một interface bao gồm hai phương thức là requestAnimationFrame() và cancelAnimationFrame(). Việc xác định thời điểm cập nhật frame sẽ được tự động chọn giá trị thích hợp nhất.

interface WindowAnimationTiming {
long requestAnimationFrame(FrameRequestCallback callback);
void cancelAnimationFrame(long handle);
};
Window implements WindowAnimationTiming;

callback FrameRequestCallback = void (DOMTimeStamp time);

requestAnimationFrame: gửi request đến trình duyệt thực hiện một hành động update/draw frame mới thông qua tham số callback. Các request này sẽ được lưu trong đối tượng document với các cặp <handle, callback> và được thực hiện lần lượt. Giá trị handle là một số định danh được tạo ra và trả về sau khi gọi phương thức này.

cancelAnimationFrame: hủy một request được tạo ra requestAnimationFrame với tham số là handle của request.

Ngoài ra còn có thuộc tính window.mozAnimationStartTime (chỉ mới được hỗ trợ trên Firefox) chứa giá trị milisecond là khoảng cách từ mốc thời gian (1/1/1970) đến thời điểm bắt đầu của request cuối cùng được thực hiện. Giá trị này tương đương với giá trị trả về của Date.now() hoặc Date.getTime(), mốc thời gian là bao nhiêu không quan trọng nhưng nó giúp bạn biết được khoảng thời gian giữa hai lần thực hiện request.

Lợi ích và hiệu quả

Microsoft cho ta một ví dụ trực quan về hiệu quả của requestAnimationFrame() so với setTimeout() tại trang requestAnimationFrame API. Qua ví dụ này, ta thấy được số lần gọi callback (update) thực tế của setTimeout() lớn hơn so với dự tính. Ngoài ra các kết quả cho thấy hiệu suất của việc thực thi callback, CPU, mức tiêu tốn năng lượng và ảnh hướng đến các tác vụ nền của requestAnimationFrame() hơn hẳn so với setTimeout():

requestAnimationFrame API - TestDrive - Clock

setTimeout requestAnimationFrame 
Expected callbacks 40543 40544
Actual callbacks 41584 40544
Callback Efficiency 59.70% 100.00%
Callback Efficiency Medium High
Power Consumption Medium Low
Background Interference High Low

Một hiệu quả khác các animation sẽ tự động dừng lại nếu tab chứa nó không được hiển thị (khi bạn chuyển sang một tab khác của trình duyệt). Điều này giúp hạn chế được tài nguyên sử dụng một cách hợp lý.

Sử dụng

Kiểm tra trình duyệt hỗ trợ:

Tùy theo trình duyệt mà tên của phương thức này sẽ có những prefix khác nhau (vendor):

_reqAnimation = window.requestAnimationFrame 	||
			window.mozRequestAnimationFrame	||
			window.webkitRequestAnimationFrame	||
			window.msRequestAnimationFrame		||
			window.oRequestAnimationFrame		;

if(_reqAnimation)
	update();
else
	alert("Your browser doesn't support requestAnimationFrame.");

Lấy thời gian giữa hai lần thực thi

Đối tượng FrameRequestCallback nhận vào tham số là giá trị milisecond tại thời điểm nó được gọi (xem phần WindowAnimationTiming), vậy để tính được khoảng cách giữa lần gọi và hiện tại, bạn cần lưu giá trị này lại và thực hiện phép trừ để lấy kết quả. Bạn không cần dùng thuộc tínhwindow.mozAnimationStartTime:

function update(time) {
	console.log(time-_startTime);
	// do something

	_startTime = time;
	_reqAnimation(update);
}

Ví dụ hoàn chỉnh:

<html>
<head>
  <script>
	const RADIUS = 20;
	var cx =  100;
	var cy = 100;
	var speedX = 2;
	var speedY = 2;
	var _canvas;
	var _context;
	var _reqAnimation;
	var _angle = 0;

	function update(time) {

		cx += speedX;
		cy += speedY;
		if(cx<0 || cx > _canvas.width)
			speedX = -speedX;

		if(cy<0 || cy > _canvas.height)
			speedY = -speedY;

		// draw
		_context.clearRect(0, 0, _canvas.width, _canvas.height);
		_context.beginPath();
		_context.arc(cx, cy, RADIUS, 0, Math.PI*2, false);
		_context.closePath();
		_context.fill();

		_reqAnimation(update);
	}
	window.onload = function(){
		_canvas  = document.getElementById("canvas");
		_context = _canvas.getContext("2d");
		_context.fillStyle = "red";
		cx = _canvas.width/2;
		cy = _canvas.height/2;

	_reqAnimation = window.requestAnimationFrame 	||
			window.mozRequestAnimationFrame		||
			window.webkitRequestAnimationFrame	||
			window.msRequestAnimationFrame		||
			window.oRequestAnimationFrame		;

	if(_reqAnimation)
		update();
	else
		alert("Your browser doesn't support requestAnimationFrame.");
		};
  </script>
</head>
<body>
<canvas id="canvas" width="400px" height="300px" style="border: 1px solid"></canvas>
</body>
</html>

Tham khảo

window.requestAnimationFrame (https://developer.mozilla.org/en/DOM/window.requestAnimationFrame).

Timing control for script-based animations (http://www.w3.org/TR/animation-timing/)

YinYang’s Blog

1 bình luận về “Javascript – Tạo chuyển động với WindowAnimationTiming API

  1. Pingback: Html5 – Giới thiệu thư viện Modernizr – Blog Collect Knowledge

Đã đóng bình luận.