Hướng dẫn JavaScript Vanilla thực tế và thân thiện với người mới bắt đầu. Bài học kinh nghiệm lớn:cách viết Sự kiện cửa sổ hiệu suất cao điều đó sẽ không giết phần cứng của người dùng của chúng tôi.
Dành cho:Người mới bắt đầu hoàn thành đến Trung gian
Hôm nay, chúng tôi đang xây dựng một ứng dụng nhỏ tuyệt vời có tên là “Thứ nguyên khung nhìn”. Tính năng này thực sự làm tốt một điều:nó hiển thị các giá trị chiều rộng và chiều cao của khung nhìn trình duyệt khi bạn thay đổi kích thước cửa sổ trình duyệt của mình.
Đây là một công cụ vô giá khi bạn đang thiết kế đáp ứng. CodePen có một tính năng tương tự. Nhưng tôi cần một cái gì đó lớn hơn và một số tính năng bổ sung, vì vậy tôi quyết định xây dựng của riêng mình. Tôi đã học được rất nhiều điều trong quá trình này, đặc biệt là về cách xử lý các vấn đề về hiệu suất. Bây giờ tôi đang chia sẻ điều đó với bạn!
Kích thước khung nhìn
Tôi không muốn đưa ra giả định về kiến thức hiện có của bạn. Do đó, đây là một rất chi tiết hướng dẫn.
Các chủ đề kỹ thuật:
- Cách tạo các phần tử với
createElement()
phương pháp. - Cách tạo kiểu cho các phần tử với
Style object
- Cách thiết lập và sử dụng
resize
của Window Object sự kiện. - Cách lấy chiều rộng và chiều cao từ khung nhìn trình duyệt của bạn, với
innerWidth
vàinnerHeight
thuộc tính. - Cách gọi các hàm ngoài hàm (lệnh gọi lại).
- Cách có điều kiện (true / false)
if
câu lệnh hoạt động. - Cách trì hoãn việc thực thi mã bằng bộ hẹn giờ, sử dụng
setTimeout()
- Cách đặt lại bộ hẹn giờ với
clearTimeout()
. - Cách sử dụng chức năng điều chỉnh sự kiện để cải thiện đáng kể hiệu suất của các sự kiện cửa sổ.
- Mối quan hệ giữa MS và FPS (Mili giây &số khung hình trên giây).
- Sự khác biệt quan trọng giữa Hiệu suất được cảm nhận và Hiệu suất phần cứng.
Cách tiếp cận
Chúng tôi đang tạo hai phiên bản của ứng dụng Thứ nguyên khung nhìn của mình.
- Phiên bản 1 là về cách làm cho ứng dụng hoạt động. Đó là bản nháp, nhưng không phải là sản phẩm cuối cùng.
- Phiên bản 2 là về việc cải tiến phiên bản 1. Chúng tôi sẽ tìm hiểu cách làm cho mã của chúng tôi hoạt động hiệu quả hơn, tiết kiệm năng lượng hơn và dễ đọc hơn.
Để giữ mọi thứ tập trung vào JavaScript, chúng tôi sẽ làm mọi thứ bằng mã JavaScript. Bằng cách giữ mọi thứ trong một tệp JavaScript, ứng dụng của chúng tôi cũng sẽ dễ dàng thêm vào bất kỳ dự án hiện có nào của bạn.
Bạn có phải là người mới bắt đầu hoàn chỉnh không? Sau đó, tôi khuyên bạn nên làm theo bằng cách sử dụng CodePen. Bạn thậm chí không cần phải đăng ký để sử dụng nó. Tôi đã xuất bản một video hướng dẫn CodePen khá phổ biến mà bạn có thể xem tại đây.
Cách học
Nếu bạn bối rối, hãy quay lại một vài đoạn văn và đảm bảo rằng bạn không bỏ qua bất kỳ điều gì quan trọng. Bạn sẽ tìm hiểu thêm bằng cách cố gắng giải quyết vấn đề của riêng bạn trước tiên và sau đó tìm mã tham chiếu thứ hai .
Nếu bạn bị lạc bạn luôn có thể tham chiếu mã đã hoàn thành:
- Phiên bản 1
- Phiên bản 2
Hãy bắt đầu!
Kích thước khung nhìn [Phiên bản 1.0]
Trước khi bắt đầu viết mã, chúng ta hãy xem xét các yêu cầu của dự án.
Yêu cầu
- Nó phải có một vùng chứa để hiển thị chiều rộng và chiều cao của cửa sổ.
- Nó phải được đặt ở góc trên cùng bên phải của cửa sổ của chúng tôi.
- Nó phải được ẩn theo mặc định.
- Nó sẽ hiển thị ngay sau khi cửa sổ được thay đổi kích thước.
- Nó sẽ ẩn 3 giây sau khi thay đổi kích thước cửa sổ mới nhất.
Đó là bản chất của các yêu cầu tính năng cho phiên bản 1.
Hãy bắt đầu viết mã!
Tạo vùng chứa Hiển thị và tạo kiểu cho nó
Đầu tiên, hãy tạo phần tử div cho ứng dụng Thứ nguyên khung nhìn của chúng tôi bằng cách sử dụng createElement
và gán nó cho một biến:
var viewportDimensions = document.createElement("div");
Bây giờ chúng ta có một <div></div>
trống phần tử được lưu trữ bên trong một biến.
Trong trình duyệt, tất cả các phần tử HTML được biểu thị bằng Đối tượng phần tử. Các đối tượng Phần tử này có thuộc tính và phương pháp mà chúng tôi có thể truy cập bằng JavaScript.
Chúng tôi muốn chiều rộng và chiều cao của khung nhìn được hiển thị ở góc trên cùng bên phải của cửa sổ trình duyệt của chúng tôi. Hãy giải quyết vấn đề đó và tạo kiểu cho phần tử div của chúng ta bằng cách truy cập vào HTML DOM đối tượng Kiểu thuộc tính:
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à ký hiệu dấu chấm. Nói một cách dễ hiểu, điều đó có nghĩa là chúng tôi sử dụng .
để truy cập các thuộc tính hoặc phương thức của một đối tượng. Vì vậy, trong ví dụ trên, chúng ta đang nói:
- Xin chào
viewportDimensions
- Cấp cho tôi quyền truy cập vào
Style Object
- Vì vậy, tôi có thể truy cập thuộc tính tạo kiểu của nó
Tạo kiểu với đối tượng Kiểu giống như tạo kiểu với CSS. Nhưng đối tượng Kiểu sử dụng kiểu nội tuyến. Thuộc tính tạo kiểu được thêm trực tiếp vào phần tử HTML, không phải trong biểu định kiểu CSS bên ngoài.
Hãy xác nhận rằng mã của chúng tôi hoạt động, bằng cách thêm Thứ nguyên cửa sổ xem của chúng tôi <div>
phần tử của <body>
phần tử của tài liệu HTML của chúng tôi.
Đầu tiên, hãy khai báo một biến để lưu trữ tham chiếu đến phần tử nội dung tài liệu của chúng ta. Thêm mã sau vào đầu tệp JavaScript của bạn.
var body = document.querySelector("body");
Bây giờ, hãy thêm phần tử div của Thứ nguyên khung nhìn vào bên trong phần tử body. Đặt mã sau ở cuối tệp JavaScript của bạn (bên dưới kiểu cuối cùng:
body.appendChild(viewportDimensions);
appendChild()
là một phương pháp nối (thêm) một nút bên trong một nút khác. Mọi thứ trong DOM là một nút và có nhiều loại nút khác nhau. Trong trường hợp này, chúng tôi đang thêm một nút phần tử div, bên trong nút phần tử nội dung.
Vì phần tử div của chúng tôi chưa có bất kỳ nội dung nào nên chúng tôi không thể biết liệu mã hiện tại của chúng tôi có hoạt động hay không. Chúng tôi có thể nhanh chóng tìm ra bằng cách thêm đường viền tạm thời vào phần tử:
viewportDimensions.style.border = "10px solid";
Nếu nó hoạt động, bây giờ bạn sẽ thấy một hộp nhỏ có viền dày màu đen ở góc trên cùng bên phải của cửa sổ:
Bạn cũng có thể kiểm tra bằng cách nhấp chuột phải vào cửa sổ trình duyệt của mình. Sau đó nhấp vào Kiểm tra để truy cập DevTools của trình duyệt của bạn.
Bây giờ trong tab phần tử, hãy tìm body
của bạn và bạn sẽ thấy một phần tử div trống, với một số thuộc tính kiểu nội tuyến.
Dưới đây là tổng quan về tất cả mã của chúng tôi cho đế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 vời, phần tử div của chúng tôi đã được tạo, tạo kiểu và thêm vào bên trong phần tử body của chúng tôi. Tiếp theo là Sự kiện thay đổi kích thước cửa sổ.
Thiết lập sự kiện thay đổi kích thước cửa sổ
Đối tượng Cửa sổ đại diện cho cửa sổ trình duyệt của bạn. Nó có một số phương pháp, bao gồm addEventListener
. Phương pháp này có thể nghe nhiều loại của các sự kiện cửa sổ, chẳng hạn như cuộn, nhấp, nhập và thay đổi kích thước.
Chúng tôi sẽ sử dụng 'resize'
biến cố. Bên dưới body.appendChild
thêm phần sau:
window.addEventListener("resize", function() {
// Code to execute when window resizes
});
Lưu ý:nhận xét //
không được trình duyệt giải thích, chúng chỉ ở đó đối với con người chúng ta
Trước đó, chúng ta đã sử dụng ký hiệu dấu chấm để lấy các thuộc tính của phần tử div. Bây giờ chúng tôi sử dụng nó để truy cập một phương pháp trên đối tượng cửa sổ:addEventListener
.
Chúng tôi đang chuyển hai đối số tới trình xử lý sự kiện:'resize'
và function()
. Chúng ta đã thảo luận về sự kiện thay đổi kích thước, nhưng chức năng này dùng để làm gì?
Hàm function()
thiết lập khối mã mà chúng tôi muốn thực thi mỗi khi sự kiện thay đổi kích thước cửa sổ hoạt động (ngay sau khi bạn thay đổi kích thước cửa sổ của mình). Khối mã là những gì bên trong dấu ngoặc nhọn { }
(còn được gọi là cơ quan chức năng) mà cho đến nay chỉ có một // comment
.
Mỗi khi chúng ta gọi sự kiện cửa sổ, nó sẽ báo cho function()
để chạy mã bên trong dấu ngoặc nhọn { }
.
Để xác nhận rằng nó đang hoạt động, hãy kiểm tra nó bằng cách sử dụng console.log()
:
window.addEventListener("resize", function() {
// Code to execute when window resizes
console.log("Working!");
});
Bây giờ thay đổi kích thước cửa sổ của bạn. Nếu bảng điều khiển của bạn in 'Đang hoạt động!' nhiều lần, điều đó có nghĩa là sự kiện thay đổi kích thước cửa sổ của bạn đang hoạt động.
Truy cập Bảng điều khiển
Để truy cập bảng điều khiển trong CodePen, hãy nhấp vào nút Bảng điều khiển ở góc dưới cùng bên trái của cửa sổ của bạn. Để truy cập bảng điều khiển trong Chrome, đây là các phím tắt:
- Cmd + Opt + J (Mac).
- Ctrl + Shift + J (Windows / Linux)
Được rồi, hãy bắt đầu Sự kiện Window của chúng tôi hoạt động!
Trả lại chiều rộng và chiều cao của trình duyệt tính bằng pixel
Bước tiếp theo là lấy các giá trị số của chiều rộng và chiều cao khung nhìn. Chúng tôi muốn các giá trị cập nhật ngay lập tức khi chúng tôi thay đổi kích thước cửa sổ của mình. Đối tượng cửa sổ đại diện cho cửa sổ trình duyệt của bạn. Nó có một vài thuộc tính tuyệt vời được gọi là innerWidth
và innerHeight
.
Đoán xem họ làm gì?
Đúng vậy, chúng trả về chiều rộng và chiều cao của cửa sổ trình duyệt của bạn tính bằng pixel (bao gồm cả thanh cuộn). Hãy xác nhận điều này và sử dụng console.log()
để trả về các giá trị thuộc tính chiều rộng và chiều cao đó.
Vì chúng tôi muốn nó lấy giá trị hiện tại động chúng ta cần đặt console.log()
Bên trong khối chức năng của sự kiện thay đổi kích thước:
window.addEventListener("resize", function() {
// Code to execute when window resizes
console.log("Width:", window.innerWidth);
console.log("Height:", window.innerHeight);
});
'Width:'
và 'Height:'
văn bản trong console.log()
Chỉ ở đó để làm cho đầu ra bảng điều khiển rõ ràng.
Chúng tôi thực sự không phải viết window.
để lấy các giá trị chiều rộng và chiều cao, chỉ cần innerWidth
và innerHeight
sẽ làm việc. Tôi làm điều đó để tránh nhầm lẫn.
Nếu hoạt động, bảng điều khiển của bạn giờ đây sẽ liên tục in kích thước của cửa sổ xem hiện tại của bạn miễn là bạn thay đổi kích thước cửa sổ trình duyệt của mình.
Nó sẽ trông như thế này:
Bây giờ chúng ta đang đến một nơi nào đó!
Hãy loại bỏ console.log()
và thay vào đó, chỉ định các thuộc tính chiều rộng và chiều cao cho biến riêng của chúng để chúng tôi có thể lưu trữ một tham chiếu đến chúng:
window.addEventListener("resize", function() {
// Code to execute when window resizes
var width = window.innerWidth;
var height = window.innerHeight;
});
Bây giờ width
và height
sẽ luôn chứa các giá trị hiện tại của thuộc tính chiều rộng và chiều cao cửa sổ. Bạn không luôn luôn phải gán các giá trị trả về cho một biến, nhưng đó là một phương pháp hay vì nó cho phép bạn sử dụng hoặc thay đổi thông tin đó sau này.
Để xác nhận rằng các biến mới của bạn hoạt động như bình thường, hãy thử in các biến mới đó vào bảng điều khiển:
window.addEventListener("resize", function() {
// Code to execute when window resizes
var width = window.innerWidth;
var height = window.innerHeight;
console.log(width);
console.log(height);
});
Nếu mọi thứ hoạt động, bạn có thể xóa console.log của mình và tiếp tục.
Thêm giá trị chiều rộng và chiều cao vào phần tử div
Chúng tôi muốn thêm các giá trị chiều rộng và chiều cao trả về vào phần tử div của chúng tôi. Cụ thể hơn, chúng tôi muốn nhúng các giá trị bên trong phần tử div của chúng tôi mở và đóng thẻ:
<div> We want width & height values here </div>
Để làm điều đó, chúng ta cần lấy biến lưu trữ phần tử div của chúng ta:viewportDimensions
. Sau đó, chúng tôi cần truy cập một thuộc tính trên đối tượng phần tử của chúng tôi, được gọi là textContent
. Chúng tôi sử dụng cùng một ký hiệu dấu chấm từ trước đó:
viewportDimensions.textContent;
Bây giờ chúng ta chỉ cần gán các biến chiều rộng và chiều cao của mình:
viewportDimensions.textContent = width + "px" + " x " + height + "px";
Hãy thử thay đổi kích thước cửa sổ trình duyệt của bạn ngay bây giờ. Nếu bạn đã làm đúng mọi thứ, giờ đây nó sẽ hiển thị các giá trị chiều rộng và chiều cao hiện tại, ở góc trên cùng bên phải của cửa sổ của bạn.
Tôi biết cú pháp mã mà chúng tôi đã sử dụng ở trên có thể hơi khó hiểu.
Hãy chia nhỏ nó ra:
textContent
là một thuộc tính của đối tượng phần tử. Bất kỳ phần tử nào trong tài liệu đều có phần tử đó, bao gồm phần tử div Kích thước khung nhìn của chúng tôi.
textContent
trả về một nút, cụ thể là một nút văn bản . Ví dụ:văn bản bên trong phần tử đoạn văn:<p>Some text...</p>
, là một nút văn bản.
viewportDimensions
của chúng tôi biến chứa một <div>
trống yếu tố. Bây giờ, nó trả về một nút văn bản với các giá trị pixel số giữa thẻ phần tử div mở và đóng:
width + "px" + " x " + height + "px";
width
và height
cả hai đều chứa một giá trị số pixel. Chúng tôi chỉ thêm 'px'
và ' x '
(chuỗi) để phân tách các biến chiều rộng và chiều cao và để mọi người hiểu rõ rằng các giá trị chúng tôi trả về là pixel.
Thêm đồng hồ đếm ngược với setTimeout ()
Chúng tôi không muốn thấy chiều rộng và chiều cao hiển thị liên tục khi chúng tôi không thay đổi kích thước cửa sổ của mình. Hãy làm cho phần tử div của Thứ nguyên khung nhìn biến mất chính xác ba giây sau khi chúng tôi ngừng thay đổi kích thước cửa sổ trình duyệt của mình.
Để làm điều đó, chúng tôi cần sử dụng một phương pháp khác của Đối tượng cửa sổ , được gọi là setTimeout
. Phương thức này được thiết kế để thực thi một function()
một lần sau một số mili giây được chỉ định.
Nó hoạt động như thế này:
setTimeout(functionToExecute, 3000);
1000 ms =1 giây. Vì vậy, 3000 ở trên có nghĩa là 3 giây.
Chúng tôi muốn thực thi chức năng nào? Một cái sẽ xóa phần tử div của Kích thước khung nhìn của chúng tôi.
Chúng tôi chưa có một chức năng như vậy, hãy tạo một chức năng và gọi nó là removeViewportDimensions()
và bên trong khối mã của nó { }
hãy yêu cầu nó xóa phần tử div của chúng ta.
Thêm mã sau ở trên chức năng sự kiện cửa sổ của bạn:
function removeViewportDimensions() {
viewportDimensions.style.display = "none";
}
display
thuộc tính chỉ định cách thức hoặc nếu một phần tử được hiển thị. Bằng cách đặt nó thành 'none'
, chúng tôi đang giấu nó.
Bây giờ chúng ta cần gọi removeViewportDimensions()
mới của chúng tôi chức năng, từ bên trong khối mã sự kiện cửa sổ của chúng tôi. Sử dụng setTimeout()
định dạng trước đó, thêm phần sau:
setTimeout(removeViewportDimensions, 3000);
Giờ đây, mỗi khi sự kiện thay đổi kích thước cửa sổ hoạt động, setTimeout
đặt đếm ngược 3 giây. Nói cách khác:chúng tôi trì hoãn lệnh gọi đến removeViewportDimensions()
3 giây.
Khi hết 3 giây, removeViewportDimensions()
hàm được gọi và thực thi mã của nó:
Bây giờ thuộc tính hiển thị của phần tử div của chúng tôi được đặt thành 'none'
và bị ẩn.
Nhưng chúng tôi gặp sự cố
Nếu bạn cố gắng thay đổi kích thước cửa sổ trình duyệt của mình lần thứ hai, ngay sau khi phần tử div đã bị xóa, thì phần tử div sẽ không hiển thị lại. Đó là bởi vì cuối cùng điều xảy ra trong chương trình của chúng tôi là phần tử div của chúng tôi nhận được display
thuộc tính được đặt thành 'none'
.
Không có nơi nào trong chương trình của chúng tôi mà chúng tôi tuyên bố rằng chúng tôi muốn phần tử div của chúng tôi hiển thị lại.
Nhưng tại sao nó lại hiển thị lần đầu tiên? Bởi vì theo mặc định, một <div>
phần tử được gọi là cấp khối yếu tố. Điều này có nghĩa là display
mặc định của nó giá trị thuộc tính là 'block'
- là một hiển thị kiểu hiển thị.
Điều cuối cùng chúng tôi làm với phần tử div của mình là đặt giá trị hiển thị của nó thành 'none'
. Vì vậy, giá trị đó sẽ vẫn còn trên phần tử khi chúng tôi chạy sự kiện thay đổi kích thước lần thứ hai. Trừ khi bạn làm mới trang của mình.
Điều này rất dễ sửa chữa. Thêm phần sau vào trong khối mã của sự kiện thay đổi kích thước cửa sổ của bạn (ở trên setTimeout
!):
viewportDimensions.style.display = "block";
Bây giờ display
của phần tử div của chúng ta thuộc tính được đặt thành 'block'
mỗi khi sự kiện thay đổi kích thước chạy. Cố gắng thay đổi kích thước cửa sổ của bạn, đợi nó ẩn phần tử div và sau đó thay đổi kích thước một lần nữa.
Tuyệt vời, ứng dụng của chúng tôi hoạt động!
Chà .. đại loại. Chúng tôi có một vấn đề khác. Nếu bạn cố gắng thay đổi kích thước cửa sổ trình duyệt của mình trong một khoảng thời gian dài (hơn 3 giây) thì phần tử div sẽ bắt đầu nhấp nháy. Nó giống như một ngọn đèn được bật và tắt liên tục.
Ứng dụng của chúng tôi không xứng đáng làm sao!
Điều đó xảy ra, bởi vì chúng tôi không đặt lại đồng hồ đếm ngược của mình. Mỗi khi chúng ta thay đổi kích thước khung nhìn của mình, sự kiện cửa sổ sẽ thực thi khối mã chức năng của nó nhiều lần nhất có thể. Vì vậy, removeViewportDimensions()
của chúng tôi chức năng là cũng được gọi nhiều lần.
Điều này khiến ứng dụng của chúng tôi có một loạt các mã thực thi chồng chéo. Phần tử div của chúng ta đang bị mài mòn với display = 'none'
tài sản và display = 'block'
tài sản đồng thời.
Điều này làm cho phần tử div của chúng tôi hiển thị và ẩn liên tục, và gây ra hiện tượng nhấp nháy. Điều này thật tệ. Để giải quyết vấn đề này, chúng ta cần tìm hiểu cách xóa bộ hẹn giờ làm việc bằng JavaScript.
Đặt lại đồng hồ đếm ngược
Chúng tôi có thể xóa các bộ hẹn giờ hiện có bằng cách sử dụng clearTimeout()
phương pháp. Phương pháp này được thiết kế để xóa bộ hẹn giờ đã được đặt bằng setTimeout
phương pháp. Chúng giống như các phương pháp đối tác.
Xóa bộ hẹn giờ hiện có là chính xác những gì chúng tôi muốn. Chúng tôi chỉ muốn chạy một setTimeout()
hẹn giờ một lần mỗi sự kiện thay đổi kích thước.
Bạn có thể nghĩ rằng chúng tôi có thể chạy clearTimeout()
trên setTimeout()
như thế này:
clearTimeout(setTimeout);
Nhưng điều đó sẽ không hiệu quả. Để sử dụng phương thức clearTimeout (), bạn phải sử dụng global khi tạo phương thức thời gian chờ.
JavaScript có hai phạm vi:toàn cầu &cục bộ:
- Một biến tổng thể là một biến được khai báo bên ngoài một hàm (trong phạm vi toàn cục).
- Một biến cục bộ là một biến được khai báo bên trong một hàm (phạm vi cục bộ).
Điều này có nghĩa là nếu chúng ta muốn xóa setTimeout()
trước tiên chúng ta cần gán nó cho một biến toàn cục và sau đó chúng ta có thể gọi clearTimeout()
trên biến đó để đặt lại.
Hãy tạo biến toàn cục của chúng ta và gọi nó là resizeTimeout
:
var resizeTimeout;
Đặt biến này ở đầu tệp JavaScript của bạn.
Hiện tại, nó chỉ là một biến trống. Nó sẽ chỉ trống cho đến khi lần đầu tiên sự kiện thay đổi kích thước của chúng tôi bắt đầu.
Nếu bạn thử sử dụng console.log(resizeTimeout)
bạn sẽ nhận được không xác định trả lại. undefined
là một sai loại giá trị quan trọng. Bạn sẽ sớm hiểu tại sao.
Bây giờ, lấy setTimeout()
của bạn chức năng từ trước đó và gán nó cho resizeTimeout
biến:
resizeTimeout = setTimeout(removeViewportDimensions, 3000);
Bây giờ là resizeTimeout
toàn cầu biến sẽ nhận được thông tin về setTimeout
của chúng tôi , ngay sau khi sự kiện thay đổi kích thước hoạt động.
Bây giờ chúng ta có thể sử dụng setTimeout()
mỗi khi sự kiện thay đổi kích thước cửa sổ của chúng tôi đang chạy. Đặt clearTimeout()
trên resizeTimeout
chung Biến đổi. Như thế này:
clearTimeout(resizeTimeout);
Đặt mã ngay trên resizeTimeout = setTimeout()
của bạn vì vậy nó trông như thế này:
clearTimeout(resizeTimeout);
resizeTimeout = setTimeout(removeViewportDimensions, 3000);
Được rồi, bây giờ hãy chạy sự kiện thay đổi kích thước của bạn trong 5-6 giây và bạn sẽ thấy rằng hiện tượng nhấp nháy đã biến mất!
Bạn bối rối? Hãy nói về những gì xảy ra trong mã.
Trình thông dịch JavaScript (một công cụ chạy trong trình duyệt của bạn) sẽ đọc mã của bạn và:
- thực thi dòng nó cho dòng
- một dòng tại một thời điểm
- từ trên xuống dưới
Mã của chúng tôi sẽ tiếp tục được thực thi, từ trên xuống dưới, miễn là chúng tôi tiếp tục thay đổi kích thước cửa sổ của mình. Mỗi một lượng nhỏ thay đổi kích thước sẽ kích hoạt một sự kiện cửa sổ mới. Vì vậy, trình thông dịch JavaScript đọc qua và thực thi mã của chúng tôi nhiều lần mỗi giây.
Mỗi khi hàm của sự kiện cửa sổ của chúng tôi thực thi khối mã của nó, clearTimeout()
sẽ đặt lại bất kỳ hiện có nào đếm ngược trong hàng đợi, trên resizeTimeout
biến.
Vào cuối cùng dòng mã, chúng tôi gán setTimeout()
.
Vì vậy, điều cuối cùng xảy ra sẽ luôn là một đếm ngược 3 giây được gán cho resizeTimeout
. Mọi lần đếm ngược đã bắt đầu trước đó sẽ bị xóa sạch bởi clearTimeout()
.
Đáng kinh ngạc! Phiên bản 1 đã xong. Bạn có thể xem mã hoàn chỉnh tại đây. Hãy chuyển sang Phiên bản 2, nơi chúng tôi đặc biệt tập trung vào hiệu suất.
Kích thước khung nhìn [Phiên bản 2]
Ứng dụng của chúng tôi hoạt động, nhưng nó có đủ tốt không? Để trả lời câu hỏi đó, chúng ta cần xem xét hai điều quan trọng:
- Hiệu suất cảm nhận:trải nghiệm người dùng (UX).
- Hiệu suất phần cứng (sử dụng và hiệu quả).
Từ quan điểm của người dùng, tôi nghĩ rằng ứng dụng của chúng tôi hoạt động rất tốt. Nó có thời gian phản hồi cực nhanh và hiển thị rất tốt, ngay cả trên các màn hình cũ (tôi đã thử nghiệm trên một vài cựu chiến binh hàng chục năm tuổi). Chúng ta luôn có thể tranh luận về việc tối ưu hóa hình ảnh (Thiết kế giao diện người dùng). Nhưng dựa trên chức năng thuần túy, ứng dụng của chúng tôi hoạt động tốt.
Nhưng từ hiệu suất phần cứng quan điểm, ứng dụng của chúng tôi thực sự là tồi tệ. Ứng dụng của chúng tôi đang sử dụng rất nhiều tài nguyên CPU và GPU, so với mức độ khiêm tốn của các yêu cầu kỹ thuật.
Bạn có thể tìm ra lý do tại sao? Đó không phải là điều bạn nghĩ đến theo bản năng khi bạn không quen thuộc với bản chất háu ăn của Sự kiện Window.
Đây là sự cố:
Mỗi khi chúng tôi thay đổi kích thước cửa sổ trình duyệt của mình, sự kiện cửa sổ của ứng dụng sẽ thực thi chức năng của nó hàng trăm lần. Chỉ trong vài giây!
Tôi đã thiết lập một chức năng trên CodePen để hiển thị vấn đề. Chú ý có bao nhiêu hàm được thực thi mỗi giây. Đồng thời xem tổng số lần thổi qua mái nhà, nhanh chóng:
Ví dụ CodePen
Điều này thật kinh khủng và hoàn toàn không cần thiết! Mã của chúng tôi đang sử dụng nhiều tài nguyên hơn mức cần thiết.
Để xem CPU và GPU của bạn đang hoạt động như thế nào trong các sự kiện của trình duyệt, bạn có thể khởi chạy Trình quản lý tác vụ của Chrome:
Thử thay đổi kích thước cửa sổ trình duyệt của bạn. Bên trong Trình quản lý tác vụ, bạn sẽ nhận được% mức sử dụng CPU &GPU được trả lại bên cạnh tab trình duyệt đang hoạt động của bạn.
Sử dụng phần cứng tuyệt đối so với tương đối
Điều quan trọng là phải phân biệt giữa việc sử dụng phần cứng tuyệt đối và tương đối. Từ quan điểm sử dụng phần cứng tuyệt đối (tổng thể), ứng dụng nhỏ bé của chúng tôi sẽ không gây hại nhiều. Hầu hết các máy tính đều quá mạnh để bị rung chuyển bởi điều này.
Nhưng từ một người thân quan điểm sử dụng, ứng dụng của chúng tôi kém hiệu quả một cách khủng khiếp. Và đó là vấn đề. Chúng tôi không bao giờ muốn có thói quen bỏ bê hiệu suất, vì nó sẽ ám ảnh chúng tôi và quay lại cắn chúng tôi.
Ngay sau khi bạn bắt đầu làm việc với các ứng dụng thực thi nặng các chức năng, việc sử dụng phần cứng trở thành một vấn đề nghiêm trọng. Sẽ không đẹp nếu chúng ta thực thi mã như chúng ta làm ngay bây giờ.
Cách sự kiện Window thực thi
Theo mặc định, một sự kiện cửa sổ tiêu chuẩn sẽ thực thi các chức năng nhiều lần nhất có thể, miễn là sự kiện đang hoạt động. Mỗi thay đổi nhỏ sẽ kích hoạt một sự kiện cửa sổ mới. Đây là nguyên nhân gây ra tốc độ thực thi cao đến mức đáng kinh ngạc mà ứng dụng của chúng tôi hiện có.
Hàm thực thi bao nhiêu lần mỗi giây?
Nó có thể là bất cứ thứ gì từ 4 đến 40 mili giây một lần. Nó phụ thuộc vào nhiều yếu tố, bao gồm:
- CPU và GPU (phần cứng) của bạn mạnh đến mức nào?
- Bạn đang sử dụng trình duyệt nào?
- Phần mềm (mã) bạn đang chạy phức tạp đến mức nào? Nó có liên quan đến hoạt ảnh không?
- Phần cứng của bạn đang làm gì khác vào lúc này? Có một video YouTube đang chạy trong một tab khác?
Phần cứng của bạn sử dụng bất kỳ tài nguyên nào mà nó có sẵn tại thời điểm nhất định mà bạn yêu cầu nó làm việc gì đó.
Đôi khi nó sẽ không có đủ miễn phí tài nguyên để phản hồi ngay lập tức yêu cầu của bạn vì rất nhiều ứng dụng đang chạy cùng một lúc.
Điều này có thể gây ra thời gian phản hồi chậm, màn hình bị treo, đóng băng và các vấn đề khác.
Mức sử dụng phần cứng tăng lên. Đây là lý do tại sao chúng ta cần sửa trạng thái hiện tại của ứng dụng của mình. Bởi vì chúng tôi quan tâm đến hiệu suất!
Lưu ý:trong các trình duyệt hiện đại, tốc độ thực thi tối thiểu là 4ms về tốc độ khung hình là khoảng 250 khung hình / giây. Đây là theo tài liệu API Web của Mozilla.
Nhưng có thông tin mâu thuẫn về chủ đề này, thông tin thêm tại đây.
Tần suất “nên” một hàm thực thi?
Tốc độ thực thi chức năng lý tưởng phụ thuộc vào ngữ cảnh. Chúng tôi đang làm việc với một hình ảnh động chi tiết? Hoặc điều gì đó đơn giản, chẳng hạn như trả về một vài giá trị số (giống như chúng tôi làm với ứng dụng của mình)?
Bất kể ngữ cảnh như thế nào, chúng tôi thường không muốn các sự kiện cửa sổ thực thi các hàm nhiều lần nhất có thể. Đó là vô lý. Nó cũng không miễn phí. Chúng ta phải ngăn chặn nó!
Chúng tôi có thể kiểm soát tần suất thực thi bằng cách sử dụng quy trình gọi là điều chỉnh. Điều đó sẽ xảy ra tiếp theo.
Điều chỉnh sự kiện để giải cứu!
Chúng tôi có thể sử dụng chức năng điều tiết để hạn chế bao nhiêu lần khác chức năng thực thi theo thời gian. Ví dụ:“chỉ gọi hàm này một lần mỗi 100 mili giây”.
Chúng ta hãy xem xét một chức năng điều chỉnh sự kiện cơ bản, được kích hoạt bởi sự kiện thay đổi kích thước cửa sổ. Sau đó, bộ điều chỉnh sẽ gọi một hàm bên ngoài sau mỗi 100 mili giây.
Chức năng điều chỉnh cơ bản:
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);
Tôi đã thêm một số khoảng trắng bổ sung vào định dạng mã để dễ đọc hơn. Hãy thử đọc qua mã một vài lần và xem bạn có hiểu nó không.
Sử dụng CodePen này để kiểm tra nó. Bạn sẽ thấy rằng chúng tôi gọi functionToExecute()
tối đa 10 lần mỗi giây (10 x 100 =1000 ms =1 giây).
Tôi nói lên đến bởi vì một chức năng không phải lúc nào cũng kích hoạt chính xác ở tốc độ bạn đặt. Nó phụ thuộc vào mức độ bận rộn của phần cứng của bạn tại thời điểm chức năng của bạn đang chạy. Bạn có đang chạy các ứng dụng tính thuế năng lượng khác trên máy tính của mình không?
Điều gì đang xảy ra trong mã điều tiết:
Đừng lo lắng nếu bạn không hiểu điều này lần đầu tiên. Tôi đã dành rất nhiều thời gian với công cụ này cho đến khi nó cuối cùng được nhấp vào.
Ở đầu tệp JavaScript của chúng tôi, chúng tôi bắt đầu bằng cách khai báo hai toàn cầu biến:
var throttled; // Empty variable. Type: undefined (falsy value)
var delay = 100; // Number
Lưu ý: toàn cầu , có nghĩa là chúng ta có thể truy cập các biến này từ mọi nơi.
Chúng tôi sử dụng throttled
làm điểm tham chiếu cho throttler()
của chúng tôi để kiểm tra xem có chức năng nào trong hàng đợi hay không.
Trước throttler()
của chúng tôi hàm được kích hoạt lần đầu tiên (bởi sự kiện thay đổi kích thước), sẽ không có bất kỳ hàm nào để thực thi trong hàng đợi. Vì vậy, throttled
bắt đầu như một biến trống.
Theo mặc định, các biến trống có kiểu giá trị là undefined
- đó là một giả dối loại giá trị. Bạn sẽ thấy lý do tại sao điều này lại quan trọng trong giây lát.
Sau đó, chúng tôi gán một giá trị số (100) cho một biến có tên là delay
. Đây là giá trị chúng tôi sử dụng để điều chỉnh ms (mili giây) tốc độ thực thi của throttler()
của chúng tôi chức năng.
Chúng tôi gán 100ms cho delay
Biến đổi. Sau đó, chúng tôi chuyển biến đó vào setTimeout()
của bộ điều chỉnh của chúng tôi . Sau đó, bộ điều chỉnh của chúng tôi sẽ chạy cứ sau 100 mili giây hoặc 10 lần mỗi giây - tối đa.
Tôi nói nhiều nhất là bởi vì 100ms đặt giới hạn về tốc độ thực thi của chúng tôi. Trên thực tế, chúng tôi có thể nhận được ít hơn 10 lệnh gọi hàm mỗi giây, tùy thuộc vào phần cứng và phần mềm của người dùng. Nhưng hầu hết các máy tính sẽ có thể kích hoạt 10 lần mỗi giây.
Bên trong hàm điều chỉnh, chúng tôi thiết lập một if
có điều kiện tuyên bố:
function throttler() {
if (!throttled) {
// code to execute
}
}
Câu lệnh điều kiện kiểm tra xem một điều kiện đã chỉ định có giá trị trung thực hay không.
!
biểu tượng có nghĩa là not
. Vì vậy, if (!throttled)
dịch sang:
“Nếu điều chỉnh không không có giá trị trung thực, sau đó chạy if
Mã của ”.
“Nhưng nếu throttled
không có giá trị trung thực, thì hãy dừng lại ngay tại đó! ”
Giá trị True &False trong JavaScript là gì?
Trong JavaScript, có sự thật và giả dối các giá trị. Đúng, đó là tên gọi của họ. Chân lý là một giá trị đúng. Sai là một giá trị được coi là sai (ngạc nhiên!).
Trong JavaScript có sáu giá trị sai:
undefined
null
-
0
''
(empty string)NaN
false
In JavaScript, anything that is not one of the above values is considered a truthy value. That’s pretty simple, right?
Back to the throttler
With that quick lesson on true vs. false, go ahead and check if our throttled
variable is indeed undefined, and a falsy value type.
We can quickly find out with console.log()
by using !!
to check our variable:
var throttled;
console.log(throttled); // undefined
console.log(!!throttled); // false value
!!
is a double not
operation. It’s a quick way to check for truthy and falsy values.
Great, now we have confirmed that throttled
is undefined, and a falsy value type. This means that the very first time our throttler()
runs, if
’s condition is indeed met.
// I start out as a falsy value
var throttled;
// I’m triggered by the window resize event
function throttler() {
// I will only run my { code } if throttled is falsy (it is!)
if (!throttled) {
// code to execute
}
}
Which means that we can move onto what happens inside if’s
code block.
Inside if’s code block
Once inside the if
statement code block, we immediately start the callback to functionToExecute
.
We also set our global throttled variable to true, so it’s no longer undefined
(no longer a false value). This stops any future calls to functionToExecute()
.
if (!throttled) {
// Run callback
functionToExecute();
// Set global throttled variable to true
throttled = true;
// ...
}
But then right below, we use setTimeout
to reset the the throttled
value back to a falsy value, after the 100ms delay:
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);
}
}
So every time the window resize event is running, it triggers the throttler()
, which will call functionToExecute
every 100ms!
Throttling is power
It’s easier to understand how powerful throttling is if you look at two opposite extremes. Try the following in the throttler function on CodePen:
- Swap 100 out with 500 and resize your window. Notice how infrequent the
functionToExecute()
is called (twice per second). - Then try to swap out 500 with 16 and resize the window. Now
functionToExecute()
is bombarded with calls (up to 60 calls per second).
That’s how throttling works. We use it to limit the number of times a function can be called over time. It’s a very easy way to avoid wasting hardware resources. The hard part is finding the “optimal” execution rate for a given function, as no project is the same. More about that later.
Adding the throttler to our project
To apply the throttler function to our own project, we’ll need to do a bit of refactoring to the old code first. Refactoring is when we improve the structure of a program but don’t change the overall functionality.
In other words:we take something that already works, and makes it easier to use and understand.
We no longer want the window resize event to directly execute the function that returns width and height. Instead, we want our throttler function to regulate when and how often the code executes.
Fortunately, the change is simple. We just need to declare a named function and then put all code from the anonymous function inside that named function.
Let’s call the new function 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);
}
Now let’s take the same throttler function from earlier, and replace the functionToExecute()
with getViewportDimensions()
:
function throttler() {
if (!throttled) {
// Callback: the function we want to throttle
getViewportDimensions();
throttled = true;
setTimeout(function() {
throttled = false;
}, delay);
}
}
And finally we add the window resize event listener:
window.addEventListener("resize", throttler);
That’s it, all the hard work is done! Before we wrap up, let’s check our app’s performance, and see if we need to tweak anything.
Hardware Performance vs. Perceived Performance
I don’t know about you, but when I resize my browser window, the width and height numbers are rendering a bit laggy to my screen. It’s not as smooth as it was before we added the throttler.
But that’s not surprising. Before we added the throttler, our code’s execution rate was absurdly high. Now it’s firing around 10 times per second. That’s why it’s a bit laggy now.
There’s no doubt that our app is very performant, from a hardware usage perspective. Our hardware hardly does any work, so it absolutely loves us right now. Which is good.
But what about the perceived performance, from the User Experience point of view? Do our users love us too?
Our rendering should be smoother. Our function execution rate is currently capped at 10 times per second. Let’s increase it. But how much?
We can make it easier to make that decision if we first take a look at the relationship between time and frame rate.
Time vs. Frame Rate
Frame rate is also known as FPS (Frames Per Second). It refers to the number of individual frames displayed per second.
In general, the higher fps the smoother the rendering will be. Video games often need 60fps or more to render smoothly. Most movies and tv series run smoothly at 24-30fps. The more fast-paced action a video or graphic has, the more FPS it needs to render smooth.
For some types of graphics, you can go as low as 15fps and still get a smooth render. But you should rarely go below 15fps because then the perceived performance will start to look noticeably slower.
To find your desired FPS in ms, you divide your target fps with 1000, e.g. 1000/15(fps) =66ms.
Take a look at this FPS to millisecond table:
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
Do you recognize something? Our app is currently executing every 100ms, or in frame rate terms:10 frames per second.
Remember, I just said that anything below 15fps will usually appear slow? That could explain why current app’s rendering feels a bit off. There are not enough frames per second to render it smooth. It doesn’t feel instant.
Let’s do some testing
I’ll guarantee that if we crank our Viewport Dimensions feature up to run at 15fps (66ms), it will look significantly smoother than at the current 10fps (100ms).
And it won’t cost too much extra CPU power.
We only need it to be smooth enough.
Like I said earlier, testing opposite extremes is a good way to find out what not to do. Then the question becomes where between the two extremes does your project fit?
Try testing the following frame rates:5fps (500ms), 15fps (66ms), and 60fps (16ms) on your Viewport Dimensions app and see what happens.
What do you think?
5fps looks horrible. It looks broken. That would never work. Our hardware would love it, but our users will hate it!
15fps looks good to me. I wouldn't say that this frame rate is 100% smooth, but it’s clearly better than 10fps.
It’s also important to remember that if your eyes are trained to look for small details, it’s not comparable to the average user’s experience. Most people will consider 15fps smooth, for this type of graphic.
60fps is super smooth — not surprisingly. But honestly, it’s not worth massively extra hardware usage.
I’m going to settle at 15fps for my app.
Why 15 FPS?
The reason I decided on 15fps / 66ms, is because that frame rate is high enough to be perceived as smooth (for most people).
Most software, computers, and displays (screens) can render at 15fps without problems. Even most small, handheld devices can handle 15fps without issue.
The irony of high frame rates is that while logic tells us that more frames per second =smoother, it can also have the opposite effect.
If you try to force a frame rate or refresh rate that your user’s hardware and software can’t handle, the result is often:lagging.
Refresh rate :is how many times your display can redraw your screen. A refresh rate of 45Hz means that your display can redraw it’s entire screen 45 times in 1 second.
Now if your CPU &GPU can generate a frame rate of 60fps, but your display’s refresh rate is only 45Hz, then you have a mismatch. This often causes unpredictable results.
We developers and designers can easily get out of touch with reality. Our development machines are usually more powerful than the average person’s computer.
This is why we need to do solid research and test our assumptions before we push products on the market.
So 15fps is a happy medium that will also work for people who don’t have powerful machines.
Using 15fps will also save us at least 75% of the energy our hardware used before we added the throttler.
Anything below 15fps will usually start to look slow and laggy. It becomes really obvious once you get to around 10fps, which is why our initial frame rate wasn’t good enough.
Here’s an insightful video on the topic of response times which supports my argument for not going below 15fps (on most things).
Context &the Minimum Effective Dose
How much of a perceived difference is there from 15 to 30fps? If you test it out on the app we just built, you’ll be surprised.
You probably won’t be to notice any significant difference. Not a deal-breaking difference. But the price you pay for hardware usage is double up!
High fps costs a lot of hardware power. So why use more than necessary? There’s always a point of diminishing returns. That’s where the Minimum Effective Dose (MED) comes in. You only need enough, to get the desired effect, no more, no less.
Of course, it always depends on the context. Are we talking about an explosive first-person shooter video game or a simple pixel counter like the one we’ve built? Context is key!
A Finishing Touch
All the hard work is done. For good measure, let’s wrap all our code inside an IIFE ((function () {...})();
) and initialize use strict
mode inside:
(function() {
"use strict";
// Put all your code below
})();
IIFE (Immediately-Invoked Function Expression), keeps all your code inside a local scope.
If we don’t wrap our code like this, we risk “polluting the global namespace”. This is a fancy way of saying that we risk causing conflicts with other code, outside of our app.
use strict;
is also used to prevent conflicts with 3rd party software that don’t follow strict coding rules.
We’re done!
Here is all the code we’ve written. I've added some comments for clarification and some extra white space for readability:
Finished project code:
(function() {
"use strict";
// throttled value before window event starts
var throttled;
// Delay function calls to 66ms (frame rate: 15fps)
var delay = 66;
// Declare empty variable for later use
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() {
// Only run code if’s block if we’re not throttled
if (!throttled) {
// Callback: the function we want to throttle
getViewportDimensions();
// We're currently throttled!
throttled = true;
// Reset throttled variable back to false after delay
setTimeout(function() {
throttled = false;
}, delay);
}
}
// Listen to resize event, and run throttler()
window.addEventListener("resize", throttler);
// End of IFFE wrapper
})();
Here’s a CodePen you can play with.
How to use your app on any project
Include a script tag at the bottom of your HTML document, just above your closing body tag (</body>
) and add the path to your js file, like this:
<script src="/js/get-viewport-dimensions.js"></script>
Browser Compatibility
Our app is compatible with all modern browsers, and from Internet Explorer 9 and up.
Advanced throttling
What we learned in this tutorial is a great precursor for some the more powerful performance tools on the market.
If you need to boost performance on more complex projects than the one we built, check out the Lodash JavaScript library. Lodash has a lot of performance features and is very popular among developers. You can also learn a lot from digging through the library’s code.
Resources
- Mozilla’s Resize Event docs
- Mozilla’s Frame Rate docs
- Video on Response Times
- Frame Rate vs Refresh Rate
- Why 66ms when using setTimeout