Hướng dẫn Vanilla JavaScript thân thiện beginner và thực tế. Point lớn: cách viết Window Events Hiệu Năng Cao không "giết" phần cứng user.
Dành cho: Complete Beginners đến Intermediates
Hôm nay xây dựng app nhỏ tuyệt vời tên "Viewport Dimensions". Tính năng này làm tốt một việc: hiển thị width và height viewport trình duyệt khi resize cửa sổ.
Đây công cụ quý giá khi làm responsive design. CodePen có feature tương tự. Nhưng mình cần cái to hơn và feature thêm, quyết định tự xây. Học được nhiều, nhất là xử lý performance issues. Giờ chia sẻ!
Viewport Dimensions

Không muốn giả định kiến thức hiện có. Kết quả: tutorial chi tiết cao.
Chủ đề kỹ thuật:
- Tạo element với phương thức
createElement(). - Style element với
Style object. - Thiết lập và dùng event
resizecủa Window Object. - Lấy width/height từ browser viewport với
innerWidthvàinnerHeight. - Gọi function bên ngoài function (callbacks).
- Cách conditional
ifstatements (true/false) hoạt động. - Trì hoãn thực thi code với timer, dùng
setTimeout(). - Reset timer với
clearTimeout(). - Dùng event throttler function cải thiện hiệu năng window events đáng kể.
- Mối quan hệ MS & FPS (Mili-giây & Frames Per Second).
- Khác biệt quan trọng giữa Perceived Performance và Hardware Performance.
Cách tiếp cận
Xây dựng hai phiên bản Viewport Dimensions app.
- Version 1 làm app chạy. Draft, chưa phải sản phẩm cuối.
- Version 2 cải thiện version 1. Học làm code hiệu năng hơn, tiết kiệm năng lượng, dễ đọc hơn.
Để tập trung JavaScript, làm tất cả trong file JS. Giữ mọi thứ trong một file JS, app dễ thêm vào dự án hiện có.
Bạn complete beginner? Theo dõi dùng CodePen. Không cần đăng ký. Mình có video tutorial CodePen phổ biến xem tại đây.
Cách học
Nếu băn khoăn, quay lại vài đoạn trước đảm bảo không bỏ sót quan trọng. Học nhiều hơn bằng cách tự giải quyết vấn đề trước rồi tham khảo code sau.
Nếu lạc đường tham khảo code hoàn chỉnh:
Bắt đầu!
Viewport Dimensions [Version 1.0]
Trước khi code, xem yêu cầu dự án.
Yêu cầu
- Container hiển thị window width & height.
- Vị trí góc trên phải cửa sổ.
- Ẩn mặc định.
- Hiện ngay khi window resize.
- Ẩn sau 3 giây kể từ lần resize cuối.
Đó là bản chất yêu cầu version 1.
Bắt đầu code!
Tạo Display container và style
Tạo div element cho Viewport Dimensions app, dùng createElement gán cho biến:
var viewportDimensions = document.createElement("div");
Giờ có empty <div></div> lưu trong biến.
Trong trình duyệt, mọi HTML element được biểu diễn bởi Element objects. Các Element objects có properties và methods truy cập bằng JavaScript.
Muốn viewport width/height hiển thị góc trên phải. Loại đó và style div element truy cập properties của HTML DOM Style object:
viewportDimensions.style.position = "fixed"; viewportDimensions.style.right = "0"; viewportDimensions.style.top = "0"; viewportDimensions.style.padding = "16px"; viewportDimensions.style.zIndex = "3"; viewportDimensions.style.fontSize = "22px";
Cú pháp trên là dot notation. Đơn giản: dùng . truy cập property/method của object. Ví dụ trên nói:
- Hey
viewportDimensions - Cho truy cập
Style Object - Để truy cập styling properties
Style với Style object như CSS. Nhưng Style object dùng inline style. Properties style thêm trực tiếp vào HTML element, không phải external CSS stylesheet.
Xác nhận code chạy, append Viewport Dimensions <div> vào <body> HTML document.
Khai báo biến lưu tham chiếu body element. Thêm code đầu file JS.
var body = document.querySelector("body");
Thêm Viewport Dimension div element vào body element. Code cuối file JS (dưới style cuối):
body.appendChild(viewportDimensions);
appendChild() là method append (thêm) node vào node khác. Mọi thứ trong DOM là node, có loại node khác nhau. Trường hợp này thêm div element node vào body element node.
Vì div element chưa có content, không thấy code có chạy không. Test nhanh bằng thêm border tạm:
viewportDimensions.style.border = "10px solid";
Nếu chạy, thấy hộp nhỏ border đen dày góc trên phải cửa sổ:

Cũng check bằng right-click browser window. Click Inspect mở DevTools.
Tab elements, tìm body element, thấy empty div element với inline style properties.

Chú ý invisible div được highlight góc trên phải khi hover chuột trong DevTools.
Tổng quan code đến nay:
// Select body element var body = document.querySelector("body"); // Create div element var viewportDimensions = document.createElement("div"); // Style element viewportDimensions.style.position = "fixed"; viewportDimensions.style.right = "0"; viewportDimensions.style.top = "0"; viewportDimensions.style.padding = "16px"; viewportDimensions.style.zIndex = "3"; viewportDimensions.style.fontSize = "22px"; // Add div element inside body element body.appendChild(viewportDimensions);
Tuyệt, div element tạo, style, append vào body. Tiếp: Window Resize Event.
Thiết lập Window Resize Event
Window Object đại diện cửa sổ trình duyệt. Có vài method, bao gồm addEventListener. Method này lắng nghe nhiều loại window events: scroll, click, typing, resize.
Dùng event 'resize'. Dưới body.appendChild thêm:
window.addEventListener("resize", function() { // Code thực thi khi window resize });
Lưu ý: comments // không được browser interpret, chỉ cho human.
Trước dùng dot notation lấy property div element. Giờ dùng truy cập method trên window object: addEventListener.
Truyền hai arguments cho event listener: 'resize' và function(). Resize event đã nói, nhưng function làm gì?
function() thiết lập code block muốn thực thi mỗi lần window resize event active (ngay khi resize window). Code block là thứ trong ngoặc nhọn { } (còn gọi function body) hiện chỉ có // comment.
Mỗi lần gọi window event, nó bảo function() chạy code trong ngoặc nhọn { }.
Xác nhận chạy, test bằng console.log():
window.addEventListener("resize", function() { // Code thực thi khi window resize console.log("Working!"); });
Resize window. Nếu console in 'Working!' nhiều lần, window resize event chạy.
Truy cập Console
Mở console CodePen, click nút Console góc dưới trái cửa sổ. Chrome shortcuts:
- Cmd + Opt + J (Mac).
- Ctrl + Shift + J (Windows / Linux)
Được, cho Window Event làm việc!
Trả về browser width & height tính bằng pixel
Bước sau: lấy giá trị số width/height viewport. Muốn giá trị cập nhật tức thì khi resize. Window Object đại diện cửa sổ trình duyệt. Có hai property tuyệt vời: innerWidth và innerHeight.
Đoán làm gì?
Ừ, trả về width/height cửa sổ browser tính bằng pixel (kể scrollbar). Xác nhận, dùng console.log() in property values.
Vì muốn lấy giá trị hiện tại động để console.log() bên trong function block resize event:
window.addEventListener("resize", function() { // Code thực thi khi window resize console.log("Width:", window.innerWidth); console.log("Height:", window.innerHeight); });
Text 'Width:' và 'Height:' trong console.log() chỉ làm output console rõ ràng.
Thực tế không cần viết window. lấy width/height, chỉ innerWidth và innerHeight cũng chạy. Mình viết để tránh nhầm lẫn.
Nếu chạy, console in liên tục viewport dimensions hiện tại miễn resize window.
Trông như:

Đang tiến bộ!
Bỏ console.log(), gán width/height properties cho biến riêng lưu tham chiếu:
window.addEventListener("resize", function() { // Code thực thi khi window resize var width = window.innerWidth; var height = window.innerHeight; });
Giờ width và height luôn chứa giá trị hiện tại window width/height property. Không luôn luôn phải gán returned value cho biến, nhưng practice tốt vì cho phép dùng/thay đổi thông tin sau.
Xác nhận biến mới chạy đúng, in biến ra console:
window.addEventListener("resize", function() { // Code thực thi khi window resize var width = window.innerWidth; var height = window.innerHeight; console.log(width); console.log(height); });
Nếu mọi thứ chạy, xóa console.log, tiếp tục.
Thêm giá trị width/height vào div element
Muốn thêm width/height trả về vào div element. Cụ thể nhúng giá trị bên trong thẻ mở/đóng div:
<div> Muốn width & height values ở đây </div>
Làm vậy, cần lấy biến lưu div element: viewportDimensions. Truy cập property trên element object, tên textContent. Dùng dot notation như trước:
viewportDimensions.textContent;
Gán width/height variables:
viewportDimensions.textContent = width + "px" + " x " + height + "px";
Resize browser window. Nếu đúng, hiển thị width/height hiện tại góc trên phải cửa sổ.

Biết syntax code trên hơi rối.
Phân tích:
textContent là property của element object. Mọi element trong document có, bao gồm Viewport Dimensions div element.
textContent trả về node, cụ thể text node. Ví dụ: text bên trong paragraph element: <p>Some text...</p>, là text node.
Biến viewportDimensions chứa empty <div>. Giờ trả về text node với giá trị pixel số giữa thẻ mở/đóng div:
width + "px" + " x " + height + "px";
width và height chứa giá trị pixel số. Chỉ thêm 'px' và ' x ' (strings) tách width/height variables, rõ ràng giá trị trả về tính bằng pixel.
Thêm countdown timer với setTimeout()
Không muốn thấy width/height hiển thị liên tục khi không resize window. Làm div element Viewport Dimensions biến mất đúng 3 giây sau khi dừng resize.
Cần method khác của Window Object, tên setTimeout. Method này thiết kế thực thi function() một lần sau số mili-giây chỉ định.
Hoạt động như sau:
setTimeout(functionToExecute, 3000);
1000 ms = 1 giây. 3000 = 3 giây.
Function gì muốn thực thi? Function xóa Viewport Dimensions div element.
Chưa có function đó, tạo tên removeViewportDimensions() bên trong code block { } bảo nó xóa div element.
Thêm code sau trên window event function:
function removeViewportDimensions() { viewportDimensions.style.display = "none"; }
Property display chỉ định element hiển thị như thế nào hoặc có hiển thị. Set 'none', ẩn nó.
Gọi removeViewportDimensions() mới từ bên trong window event code block. Dùng format setTimeout() trước, thêm:
setTimeout(removeViewportDimensions, 3000);
Mỗi lần window resize event active, setTimeout set countdown 3 giây. Nghĩa là: trì hoãn gọi removeViewportDimensions() 3 giây.
Hết 3 giây, removeViewportDimensions() được gọi, thực thi code:

Giờ div element display property set 'none', ẩn.
Nhưng có vấn đề
Nếu resize window lần 2 ngay sau div element bị xóa, div element không hiện lại. Vì điều cuối cùng chương trình làm là set display property div element thành 'none'.
Không có chỗ nào chương trình nói muốn div element hiện lại.
Tại sao lần đầu lại hiện? Vì mặc định <div> là block-level element. Nghĩa là default display property value là 'block' — visible display type.
Điều cuối cùng làm với div element là set display value 'none'. Giá trị đó vẫn trên element khi chạy resize event lần 2. Trừ khi refresh trang.
Dễ sửa. Thêm sau trong code block window resize event (trên setTimeout!):
viewportDimensions.style.display = "block";
Giờ div element display property set 'block' mỗi lần resize event chạy. Resize window, chờ ẩn div, resize lại.

Tuyệt, app chạy!
Vâng... kiểu gì. Còn vấn đề khác. Nếu resize browser window kéo dài (hơn 3 giây) div element bắt đầu nhấp nháy. Như đèn bật tắt liên tục.

Xứng tầm app! Nguyên nhân: không reset countdown timer. Mỗi lần resize viewport, window event thực thi function block bao nhiêu lần có thể. Vậy removeViewportDimensions() cũng được gọi nhiều lần.
Gây overlapping code execution. Div element bị đập liên tục display = 'none' và display = 'block' cùng lúc.
Làm div element show/hide liên tục, gây nhấp nháy. Tệ. Giải quyết, học clearing timers trong JavaScript.
Reset countdown timer
Xóa timer tồn tại bằng method clearTimeout(). Method này thiết kế xóa timer set bằng setTimeout. Chúng như partner methods.
Xóa timer tồn tại chính là điều muốn. Chỉ muốn chạy setTimeout() timer một lần mỗi resize event.
Bạn nghĩ chạy clearTimeout() trên setTimeout() như sau:
clearTimeout(setTimeout);
Nhưng không chạy. Dùng clearTimeout(), phải dùng biến global khi tạo timeout method.
JavaScript có hai scope: global & local:
- Global variable khai báo bên ngoài function (global scope).
- Local variable khai báo bên trong function (local scope).
Nghĩa là muốn clear setTimeout() cần gán nó cho global variable trước, sau đó gọi clearTimeout() trên biến đó reset.
Tạo global variable, tên resizeTimeout:
var resizeTimeout;
Đặt variable đầu file JS.
Giờ chỉ empty variable. Chỉ empty cho đến lần đầu resize event start.
Thử console.log(resizeTimeout) nhận undefined. undefined là false value type, quan trọng. Sớm hiểu tại sao.
Lấy setTimeout() trước gán cho resizeTimeout:
resizeTimeout = setTimeout(removeViewportDimensions, 3000);
Giờ global resizeTimeout nhận info về setTimeout function ngay khi resize event active.
Dùng setTimeout() mỗi lần window resize event chạy. Set clearTimeout() trên global resizeTimeout:
clearTimeout(resizeTimeout);
Đặt code trên resizeTimeout = setTimeout():
clearTimeout(resizeTimeout); resizeTimeout = setTimeout(removeViewportDimensions, 3000);
Chạy resize event 5-6 giây, nhấp nháy biến mất!
Băn khoăn? Giải thích code.
JavaScript interpreter (engine chạy trình duyệt) đọc code và:
- thực thi dòngต่อ dòng
- một dòng tại một thời điểm
- từ trên xuống dưới
Code tiếp tục thực thi, trên xuống, miễn resize window. Mỗi chút resize fire window event mới. Interpreter đọc qua và thực thi code nhiều lần/giây.
Mỗi lần window event function thực thi code block, clearTimeout() reset bất kỳ countdown tồn tại trong queue, trên resizeTimeout.
Dòng cuối code, gán setTimeout().
Vậy điều cuối cùng luôn là một countdown 3 giây gán cho resizeTimeout. Countdown khởi tạo trước bị clearTimeout() xóa.
Tuyệt! Version 1 xong. Xem code hoàn chỉnh. Sang Version 2, tập trung performance.
Viewport Dimensions [Version 2]
App chạy, nhưng đủ tốt? Trả lời cần xét hai điều quan trọng:
- Perceived performance: trải nghiệm user (UX).
- Hardware performance (usage & efficiency).
Góc UX, app perform tốt. Response time nhanh, render tốt nawet màn hình cũ (tested trên vài veteran thập kỷ). Luôn tranh luận optimize visuals (UI Design). Dựa trên pure functionality, app chạy tốt.
Nhưng góc hardware performance, app thực sự tệ. App dùng ton CPU/GPU resources, so với yêu cầu kỹ thuật khiêm tốn.
Đoán được tại sao? Không phải điều instinctive nghĩ khi không quen bản chất tham lam của Window Event.
Vấn đề:
Mỗi lần resize browser window, window event app thực thi function hàng trăm lần. Chỉ trong vài giây!
Mình up function CodePen visualize vấn đề. Chú ý bao nhiêu function execute/giây. Cũng xem total count tăng vọt:
Kinh khủng và hoàn toàn không cần! Code dùng resource nhiều hơn đáng lẽ.
Xem CPU/GPU làm việc lúc browser events, mở Chrome Task Manager:

Resize browser window. Task Manager trả về CPU & GPU % usage bên tab browser active.
Absolute vs Relative Hardware Usage
Phân biệt absolute và relative hardware usage. Góc absolute (tổng) hardware usage, app nhỏ không hại nhiều. Máy tính hiện đại quá mạnh bị rung.
Nhưng góc relative usage, app hiệu suất tồi. Vấn đề là đó. Không bao giờ muốn thói quen bỏ qua performance, nó sẽ quay lại cắn.
Sớm làm việc với app thực thi heavy functions, hardware usage thành vấn đề nghiêm trọng. Không đẹp nếu thực thi code như hiện tại.
Window Event thực thi như thế nào
Mặc định, standard window event thực thi function bao nhiêu lần có thể, miễn event active. Mỗi thay đổi nhỏ trigger window event mới. Đây nguyên nhân execution rate cao ngớ ngẩn app hiện tại.
Function execute bao nhiêu lần/giây?
Từ 4 đến 40 milliseconds. Phụ thuộc yếu tố:
- CPU/GPU mạnh đến đâu (hardware)?
- Browser nào?
- Phần mềm (code) phức tạp đến đâu? Có animation?
- Hardware làm gì lúc đó? YouTube tab khác?
Hardware dùng resource free có sẵn tại thời điểm yêu cầu.
Đôi khi không đủ free resource respond ngay vì nhiều app chạy cùng lúc.
Gây slow response, screen glitch, freezing, issues khác.
Hardware usage cộng dồn. Cần fix trạng thái app hiện tại. Vì quan tâm performance!
Lưu ý: modern browsers, minimum execution rate 4ms ~ 250fps. Theo Mozilla Web API docs.
Nhưng thông tin conflicting trên topic này, thêm info tại đây.
Function "nên" execute bao nhiêu lần?
Ideal execution rate phụ thuộc context. Animation chi tiết? Hay đơn giản trả vài giá trị số (như app chúng ta)?
Bất kể context, thường không muốn window events execute function bao nhiêu lần có thể. Vô lý. Cũng không miễn phí. Phải dừng!
Kiểm soát execution frequency, dùng process gọi throttling. Đang đến.
Event Throttling cứu cánh!
Dùng throttling function giới hạn bao nhiêu lần function khác thực thi theo thời gian. Ví dụ: "chỉ gọi function này một lần per 100 ms".
Xem basic event throttler function, triggered bởi window resize event. Throttler gọi outside function mỗi 100 ms.
Basic Throttler Function:
var throttled; var delay = 100; function functionToExecute() { console.log("Executed!"); } function throttler() { if (!throttled) { functionToExecute(); throttled = true; setTimeout(function() { throttled = false; }, delay); } } window.addEventListener("resize", throttler);
Thêm extra white space format code dễ đọc. Đọc code vài lần, xem hiểu không.
Dùng CodePen test. Sẽ thấy gọi functionToExecute() lên đến 10 lần/giây (10 x 100 = 1000 ms = 1 giây).
Nói up to vì function không luôn fire đúng rate set. Phụ thuộc hardware bận lúc function chạy. Chạy app năng lượng nặng khác?
Điều gì xảy ra trong throttler code:
Không lo nếu không hiểu lần đầu. Mình mất nhiều thời gian mess với stuff này đến khi click.
Đầu file JS, khai báo hai global variables:
var throttled; // Empty variable. Type: undefined (falsy value) var delay = 100; // Number
Lưu ý: global variables, truy cập biến từ đâu cũng được.
Dùng throttled reference point cho throttler() function, check có function trong queue không.
Trước throttler() triggered lần đầu (bởi resize event), không có function execute trong queue. throttled bắt đầu empty variable.
Mặc định, empty variable value type undefined — falsy value type. Sớm thấy tại sao quan trọng.
Gán numeric value (100) cho variable delay. Giá trị điều chỉnh ms (mili-giây) execution rate throttler().
Gán 100ms cho delay. Truyền variable cho setTimeout() của throttler. Throttler chạy mỗi 100ms hoặc 10 lần/giây — tối đa.
Nói tối đa vì 100ms cap execution rate. Thực tế có thể ít hơn 10 calls/giây, tùy hardware/software user. Nhưng hầu hết máy fire 10 lần/giây.
Bên trong throttler function, setup conditional if statement:
function throttler() { if (!throttled) { // code to execute } }
Conditional statements check điều kiện có truthy value không.
Ký hiệu ! nghĩa not. Vậy if (!throttled) dịch:
"Nếu throttled không có truthy value, chạy code if."
"Nhưng nếu throttled có truthy value, dừng ngay!"
True & False values trong JavaScript?
Trong JS, có truthy và falsy values. Đúng, gọi như vậy. Truthy = giá trị true. Falsy = giá trị coi false (ngạc nhiên!).
JS có sáu falsy values:
undefinednull0''(empty string)NaNfalse
Trong JS, bất kỳ thứ không phải above values coi là truthy value. Đơn giản, phải không?
Quay lại throttler
Với bài học nhanh true/false, kiểm tra throttled thực sự undefined, falsy value type.
Nhanh check với console.log() dùng !! check variable:
var throttled; console.log(throttled); // undefined console.log(!!throttled); // false value
!! là double not operation. Cách nhanh check truthy/falsy values.
Tuyệt, xác nhận throttled undefined, falsy value type. Nghĩa là lần đầu throttler() chạy, điều kiện if thỏa mãn.
// Bắt đầu là falsy value var throttled; // Triggered bởi window resize event function throttler() { // Chỉ chạy { code } nếu throttled falsy (đúng!) if (!throttled) { // code to execute } }
Tiến vào bên trong code block if.
Bên trong code block if
Một khi trong if statement code block, ngay callback functionToExecute.
Set global throttled variable thành true, không còn undefined (không còn false value). Dừng future calls functionToExecute().
if (!throttled) { // Run callback functionToExecute(); // Set global throttled variable to true throttled = true; // ... }
Nhưng ngay dưới, dùng setTimeout reset throttled về falsy value, sau delay 100ms:
function throttler() { if (!throttled) { // Run callback functionToExecute(); // Set global throttled variable to true throttled = true; setTimeout(function() { // Set throttled back to false after delay throttled = false; }, delay); } }
Vậy mỗi lần window resize event chạy, trigger throttler(), gọi functionToExecute mỗi 100ms!
Throttling là quyền lực
Dễ hiểu power của throttling nhìn hai cực đối lập. Thử trong throttler function CodePen:
- Swap 100 thành 500 resize window. Chú ý
functionToExecute()gọi hiếm (2 lần/giây). - Swap 500 thành 16 resize window. Giờ
functionToExecute()bị ném calls (lên đến 60 calls/giây).
Đó là throttling. Dùng giới hạn số lần function gọi theo thời gian. Cách dễ tránh lãng phí hardware resources. Phần khó tìm "optimal" execution rate cho function, vì project không giống nhau. Sâu hơn sau.
Thêm throttler vào project
Áp dụng throttler function project, cần refactoring code cũ. Refactoring cải thiện cấu trúc chương trình nhưng không thay đổi functionality tổng thể.
Nghĩa là: lấy thứ đã chạy, làm dễ dùng/hiểu hơn.
Không muốn window resize event trực tiếp execute function trả width/height. Muốn throttler function điều tiết khi nào và bao nhiêu lần code chạy.
May mắn thay, thay đổi đơn giản. Chỉ cần khai báo named function để code từ anonymous function vào named function đó.
Đặt tên function mới getViewportDimensions():
function getViewportDimensions() { var width = window.innerWidth; var height = window.innerHeight; viewportDimensions.textContent = width + "px" + " x " + height + "px"; viewportDimensions.style.display = "block"; clearTimeout(resizeTimeout); resizeTimeout = setTimeout(removeViewportDimensions, 3000); }
Lấy throttler function trước, thay functionToExecute() bằng getViewportDimensions():
function throttler() { if (!throttled) { // Callback: function muốn throttle getViewportDimensions(); // Đang throttled! throttled = true; // Reset throttled variable về false sau delay setTimeout(function() { throttled = false; }, delay); } }
Cuối, thêm window resize event listener:
window.addEventListener("resize", throttler);
Xong, hard work done! Trước khi kết thúc, check performance app, xem cần tweak gì.
Hardware Performance vs Perceived Performance
Không biết bạn, nhưng khi resize browser window, số width/height render hơi laggy màn hình. Không mượt như trước thêm throttler.
Không ngạc nhiên. Trước thêm throttler, execution rate code cao vô lý. Giờ fire ~10 lần/giây. Đó lý do laggy.
Không nghi ngờ app rất performant góc hardware usage. Hardware hầu như không làm việc, nó yêu thích chúng ta. Tốt.
Nhưng perceived performance, góc User Experience? User có yêu chúng ta không?
Rendering nên mượt hơn. Execution rate hiện tại cap 10 lần/giây. Tăng bao nhiêu?
Dễ quyết định nếu trước xem mối quan hệ time vs frame rate.
Time vs Frame Rate
Frame rate hay FPS (Frames Per Second). Chỉ số frame riêng lẻ hiển thị/giây.
Chung, FPS cao hơn = render mượt hơn. Video game thường cần 60fps+ render mượt. Phim/series thường mượt 24-30fps. Action nhanh hơn, cần FPS cao render mượt.
Loại graphic nhất có thể xuống 15fps vẫn mượt. Nhưng hiếm khi dưới 15fps vì perceived performance bắt đầu trông chậm.
Tìm desired FPS trong ms, chia target fps cho 1000, ví dụ 1000/15(fps) = 66ms.
Bảng FPS to millisecond:
var 2fps = 1000/2; // 500ms var 10fps = 1000/10; // 100ms var 15fps = 1000/15; // 66ms var 24fps = 1000/24; // 42ms var 30fps = 1000/30; // 33ms var 60fps = 1000/60; // 16ms
Nhận ra gì? App hiện tại execute mỗi 100ms, hoặc frame rate: 10 frames/giây.
Nhớ, vừa nói dưới 15fps thường trông chậm? Giải thích tại sao render app hiện tại hơi off. Không đủ frames/giây render mượt. Không cảm giác instant.
Làm test
Đảm bảo nếu crank Viewport Dimensions feature lên 15fps (66ms), trông mượt đáng kể hơn 10fps (100ms).
Và không tốn nhiều CPU power thêm.
Chỉ cần mượt đủ.
Như nói, test cực đối lập cách tốt tìm điều không nên làm. Câu hỏi: đâu giữa hai cực project fit?
Test frame rates: 5fps (500ms), 15fps (66ms), 60fps (16ms) trên Viewport Dimensions app xem.
Suy nghĩ gì?
5fps trông tệ. Trông broken. Không bao giờ chạy. Hardware yêu, user ghét!
15fps trông tốt. Không nói 100% mượt, nhưng rõ ràng tốt hơn 10fps.
Quan trọng nhớ: nếu mắt bạn train tìm chi tiết nhỏ, không comparable với average user experience. Hầu hết người coi 15fps mượt, cho loại graphic này.
60fps siêu mượt — không ngạc nhiên. Nhưng honestly, không worth hardware usage lớn thêm.
Mình chọn 15fps cho app.
Tại sao 15 FPS?
Lý do chọn 15fps / 66ms, vì frame rate đủ cao được cảm nhận mượt (đa số người).
Hầu hết software, máy tính, display (màn hình) render 15fps không vấn đề. Thiết bị cầm tay nhỏ cũng handle 15fps không issue.
Irony của high frame rates: logic bảo more fps = smoother, nhưng cũng có hiệu ứng ngược.
Nếu ép frame rate/refresh rate hardware/software user không handle, kết quả thường: lagging.
Refresh rate: số lần display vẽ lại screen. Refresh rate 45Hz = display vẽ lại toàn màn hình 45 lần/giây.
Nếu CPU/GPU generate 60fps, nhưng display refresh rate 45Hz, mismatch. Thường gây kết quả unpredictable.
Devs/designers dễ out of touch với thực tế. Dev machines thường mạnh hơn máy bình thường.
Cần solid research và test assumptions trước khi push product market.
Vậy 15fps happy medium cũng chạy cho người không máy mạnh.
Dùng 15fps cũng tiết kiệm ít nhất 75% năng lượng hardware dùng trước thêm throttler.
Dưới 15fps thường trông chậm/laggy. Rõ ràng ở ~10fps, lý do frame rate ban đầu không đủ tốt.
Video insightful về response times ủng hộ argument không dưới 15fps (trên hầu hết thứ).
Context & Minimum Effective Dose
Perceived difference từ 15 đến 30fps bao nhiêu? Test trên app vừa xây, bạn ngạc nhiên.
Có thể không nhận ra different đáng kể. Không deal-breaking difference. Nhưng giá hardware usage gấp đôi!
High fps tốn hardware power. Tại sao dùng hơn cần thiết? Luôn có điểm diminishing returns. Đó chỗ Minimum Effective Dose (MED). Chỉ cần đủ, đạt effect mong muốn, không hơn, không kém.
Tùy context. Explosive first-person shooter game hay simple pixel counter như app chúng ta? Context is key!
Finishing Touch
Hard work done. Good measure, wrap tất cả code trong IIFE ((function () {...})();) khởi tạo use strict mode bên trong:
(function() { "use strict"; // Đặt code tất cả dưới đây })();
IIFE (Immediately-Invoked Function Expression), giữ code trong local scope.
Nếu không wrap như này, risk "polluting the global namespace". Cách fancy nói: risk gây conflict với code khác, bên ngoài app.
use strict; cũng prevent conflicts với 3rd party software không follow strict coding rules.
Xong!
Toàn bộ code viết. Thêm comments làm rõ và extra white space readability:
Finished project code:
(function() { "use strict"; // throttled value trước window event start var throttled; // Delay function calls 66ms (frame rate: 15fps) var delay = 66; // Declare empty variable dùng sau var resizeTimeout; // Grab body element var body = document.querySelector("body"); // Create element var viewportDimensions = document.createElement("div"); // Style element viewportDimensions.style.position = "fixed"; viewportDimensions.style.right = 0; viewportDimensions.style.top = 0; viewportDimensions.style.padding = "16px"; viewportDimensions.style.zIndex = 3; viewportDimensions.style.fontSize = "22px"; // Add div element inside body element body.appendChild(viewportDimensions); // window.resize callback function function getViewportDimensions() { var width = window.innerWidth; var height = window.innerHeight; viewportDimensions.textContent = width + "px" + " x " + height + "px"; viewportDimensions.style.display = "block"; clearTimeout(resizeTimeout); resizeTimeout = setTimeout(removeViewportDimensions, 3000); } // Called from getViewportDimensions() function removeViewportDimensions() { viewportDimensions.style.display = "none"; } function throttler() { // Chỉ chạy code if's block nếu không throttled if (!throttled) { // Callback: function muốn throttle getViewportDimensions(); // Đang throttled! throttled = true; // Reset throttled variable về false sau delay setTimeout(function() { throttled = false; }, delay); } } // Lắng nghe resize event, chạy throttler() window.addEventListener("resize", throttler); // End of IFFE wrapper })();
Cách dùng app trên bất kỳ project
Include script tag cuối HTML document, ngay trên closing body tag (</body>) thêm path js file:
<script src="/js/get-viewport-dimensions.js"></script>
Browser Compatibility
App tương thích tất cả modern browsers, từ Internet Explorer 9 trở lên.
Advanced throttling
Học tutorial này precursor tốt cho performance tools mạnh hơn market.
Cần boost performance project phức tạp hơn app xây dựng, check Lodash JavaScript library. Lodash nhiều performance features, popular devs. Cũng học được nhiều digging code thư viện.