Nếu tôi nói với bạn rằng có nhiều khung nhìn thì sao.
BRRRRAAAAAAAMMMMMMMMMM
Và khung nhìn mà bạn đang sử dụng thực ra là một khung nhìn trong một khung nhìn.
BRRRRAAAAAAAMMMMMMMMMM
Và đôi khi, dữ liệu mà DOM cung cấp cho bạn đề cập đến một trong những khung nhìn đó chứ không phải khung nhìn còn lại.
BRRRRAAAAM... chờ gì nữa?
Thật đấy, hãy xem thử:
Khung nhìn bố cục so với khung nhìn hình ảnh
Video trên cho thấy một trang web đang được cuộn và thu phóng, cùng với một bản đồ thu nhỏ ở bên phải cho thấy vị trí của các khung nhìn trong trang.
Mọi thứ diễn ra khá thẳng trong khi cuộn thông thường. Vùng màu xanh lục thể hiện khung nhìn bố cục nơi các mục position: fixed
gắn vào.
Mọi thứ trở nên kỳ lạ khi tính năng chụm-thu phóng được ra mắt. Hộp màu đỏ đại diện cho khung nhìn trực quan, là một phần của trang mà chúng ta thực sự có thể nhìn thấy. Khung nhìn này có thể di chuyển xung quanh trong khi các phần tử position: fixed
vẫn giữ nguyên vị trí, được gắn vào khung nhìn bố cục. Nếu chúng ta xoay sang ranh giới của khung nhìn bố cục, thì khung nhìn bố cục sẽ kéo theo.
Cải thiện khả năng tương thích
Rất tiếc, API web không nhất quán về khung nhìn mà chúng đề cập đến và cũng không nhất quán trên các trình duyệt.
Ví dụ: element.getBoundingClientRect().y
trả về độ lệch trong khung nhìn bố cục. Điều đó thật thú vị, nhưng chúng tôi thường muốn có vị trí trong trang, vì vậy chúng tôi viết:
element.getBoundingClientRect().y + window.scrollY
Tuy nhiên, nhiều trình duyệt sử dụng khung nhìn trực quan cho window.scrollY
, nghĩa là các mã ở trên sẽ bị hỏng khi người dùng chụm-thu phóng.
Chrome 61 thay đổi window.scrollY
để tham chiếu đến khung nhìn bố cục,
nghĩa là mã ở trên sẽ hoạt động ngay cả khi dùng tính năng chụm thu phóng. Trên thực tế, các trình duyệt đang thay đổi dần tất cả các thuộc tính vị trí để tham chiếu đến khung nhìn bố cục.
Ngoại trừ một tài sản mới...
Hiển thị khung nhìn hình ảnh cho tập lệnh
API mới hiển thị khung nhìn hình ảnh dưới dạng window.visualViewport
. Đây là một thông số kỹ thuật nháp với tính năng phê duyệt trên nhiều trình duyệt và sẽ xuất hiện trên Chrome 61.
console.log(window.visualViewport.width);
Dưới đây là những gì window.visualViewport
cung cấp cho chúng ta:
visualViewport cơ sở lưu trú |
|
---|---|
offsetLeft
|
Khoảng cách giữa cạnh trái của khung nhìn hình ảnh và khung nhìn bố cục, tính bằng pixel CSS. |
offsetTop
|
Khoảng cách giữa cạnh trên cùng của khung nhìn hình ảnh và khung nhìn bố cục, tính bằng pixel CSS. |
pageLeft
|
Khoảng cách giữa cạnh trái của khung nhìn và ranh giới bên trái của tài liệu, tính bằng pixel CSS. |
pageTop
|
Khoảng cách giữa cạnh trên cùng của khung nhìn hình ảnh và ranh giới trên cùng của tài liệu, tính bằng pixel CSS. |
width
|
Chiều rộng của khung nhìn hình ảnh tính bằng pixel CSS. |
height
|
Chiều cao của khung nhìn hình ảnh tính bằng pixel CSS. |
scale
|
Tỷ lệ được áp dụng bằng cách chụm/thu phóng. Nếu nội dung có kích thước gấp đôi do thu phóng, thao tác này sẽ trả về 2 . Điều này không chịu ảnh hưởng của
devicePixelRatio .
|
Ngoài ra, còn có một vài sự kiện khác:
window.visualViewport.addEventListener('resize', listener);
visualViewport sự kiện |
|
---|---|
resize
|
Được kích hoạt khi width , height hoặc scale thay đổi.
|
scroll
|
Được kích hoạt khi offsetLeft hoặc offsetTop thay đổi.
|
Bản minh hoạ
Video ở đầu bài viết này được tạo bằng visualViewport
, hãy xem video này trong Chrome 61 trở lên. Video này sử dụng visualViewport
để làm cho bản đồ thu nhỏ nằm ở trên cùng bên phải của khung nhìn trực quan và áp dụng tỷ lệ nghịch đảo để video luôn hiển thị cùng một kích thước, bất kể hiện tượng chụm/thu phóng.
Sai ngữ pháp
Sự kiện chỉ kích hoạt khi khung nhìn hình ảnh thay đổi
Đây có vẻ như là một điều hiển nhiên, nhưng tôi đã nhận ra khi lần đầu chơi với visualViewport
.
Nếu khung nhìn bố cục đổi kích thước nhưng khung nhìn trực quan không đổi kích thước, thì bạn sẽ không nhận được sự kiện resize
. Tuy nhiên, sẽ có trường hợp khung nhìn bố cục thay đổi kích thước khi khung nhìn trực quan không thay đổi chiều rộng/chiều cao.
Vấn đề thực sự là cuộn. Nếu thao tác cuộn diễn ra, nhưng khung nhìn hình ảnh vẫn ở trạng thái tĩnh so với khung nhìn bố cục, thì bạn sẽ không thấy sự kiện scroll
trên visualViewport
, đây là trường hợp phổ biến. Trong khi cuộn tài liệu thông thường, khung nhìn hình ảnh vẫn được khoá ở trên cùng bên trái của khung nhìn bố cục, vì vậy scroll
không kích hoạt trên visualViewport
.
Nếu muốn biết tất cả các thay đổi đối với khung nhìn hình ảnh, bao gồm cả pageTop
và pageLeft
, bạn cũng phải theo dõi sự kiện cuộn của cửa sổ:
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
Tránh lặp lại công việc với nhiều trình nghe
Tương tự như việc nghe scroll
và resize
trên cửa sổ, do đó, bạn có thể gọi một số loại hàm "cập nhật". Tuy nhiên, nhiều sự kiện trong số này thường xảy ra cùng một lúc. Nếu người dùng đổi kích thước cửa sổ, thì thao tác này sẽ kích hoạt resize
, nhưng thường là scroll
. Để cải thiện hiệu suất, hãy tránh xử lý thay đổi nhiều lần:
// Add listeners
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
addEventListener('scroll', update);
let pendingUpdate = false;
function update() {
// If we're already going to handle an update, return
if (pendingUpdate) return;
pendingUpdate = true;
// Use requestAnimationFrame so the update happens before next render
requestAnimationFrame(() => {
pendingUpdate = false;
// Handle update here
});
}
Tôi đã gửi một vấn đề về quy cách của sự kiện này vì tôi nghĩ có thể có cách tốt hơn, chẳng hạn như một sự kiện update
.
Trình xử lý sự kiện không hoạt động
Do lỗi của Chrome, mã này không hoạt động:
Lỗi – sử dụng trình xử lý sự kiện
visualViewport.onscroll = () => console.log('scroll!');
Thay vào đó:
Hoạt động – sử dụng trình nghe sự kiện
visualViewport.addEventListener('scroll', () => console.log('scroll'));
Giá trị chênh lệch được làm tròn
Tôi nghĩ (là tôi hy vọng) đây là một lỗi khác của Chrome.
offsetLeft
và offsetTop
được làm tròn, điều này khá không chính xác khi người dùng đã phóng to. Bạn có thể thấy các vấn đề về vấn đề này trong quá trình bản minh hoạ – nếu người dùng phóng to và kéo chậm, bản đồ thu nhỏ sẽ chụp nhanh giữa các pixel không thu phóng.
Tỷ lệ sự kiện diễn ra chậm
Giống như các sự kiện resize
và scroll
khác, các sự kiện này không kích hoạt mọi khung hình, đặc biệt là trên thiết bị di động. Bạn có thể thấy điều này trong quá trình bản minh hoạ – sau khi chụm thu phóng, bản đồ thu nhỏ sẽ gặp sự cố khi cố định khung nhìn.
Hỗ trợ tiếp cận
Trong bản minh hoạ, tôi đã sử dụng visualViewport
để chống lại hiện tượng chụm thu phóng của người dùng. Bản minh hoạ này rất phù hợp, nhưng bạn nên suy nghĩ kỹ trước khi làm bất cứ điều gì ghi đè mong muốn phóng to của người dùng.
Bạn có thể dùng visualViewport
để cải thiện khả năng hỗ trợ tiếp cận. Ví dụ: nếu người dùng phóng to, bạn có thể chọn ẩn các mục position: fixed
trang trí để không ảnh hưởng đến thao tác của người dùng. Nhưng một lần nữa, hãy cẩn thận, không ẩn điều gì đó
mà người dùng đang cố gắng xem xét kỹ hơn.
Bạn có thể cân nhắc đăng lên một dịch vụ phân tích khi người dùng phóng to. Điều này có thể giúp bạn xác định các trang mà người dùng đang gặp khó khăn ở mức thu phóng mặc định.
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
Chỉ vậy thôi! visualViewport
là một API nhỏ giúp giải quyết các vấn đề về khả năng tương thích.