ถ้าฉันบอกนะ มีวิวพอร์ตมากกว่า 1 วิวพอร์ต
BRRRRAAAAAAAMMMMMMMM
และวิวพอร์ตที่คุณใช้อยู่ตอนนี้ก็เป็นวิวพอร์ตภายในวิวพอร์ต
BRRRRAAAAAAAMMMMMMMM
และบางครั้งข้อมูลที่ DOM ให้มาหมายถึงหนึ่งในวิวพอร์ตเหล่านั้น แต่ไม่ใช่อีกวิวพอร์ตหนึ่ง
BRRRRAAAAM... เดี๋ยวนะ
จริง ลองดู:
วิวพอร์ตเลย์เอาต์เทียบกับวิวพอร์ตภาพ
วิดีโอด้านบนแสดงให้เห็นหน้าเว็บที่กำลังเลื่อนและซูมแบบบีบ พร้อมด้วยแผนที่ขนาดเล็กทางด้านขวาซึ่งแสดงตำแหน่งของวิวพอร์ตภายในหน้า
สิ่งต่างๆ จะค่อนข้างตรงไปข้างหน้าในระหว่างการเลื่อนตามปกติ พื้นที่สีเขียวจะแสดงวิวพอร์ตของเลย์เอาต์ ซึ่งแสดงอยู่ position: fixed
รายการ
สิ่งต่างๆ อาจดูแปลกประหลาดเมื่อมีการใช้การซูมแบบบีบนิ้ว กล่องสีแดงจะแสดงวิวพอร์ตภาพ ซึ่งเป็นส่วนของหน้าเว็บที่เรามองเห็นได้จริงๆ วิวพอร์ตนี้สามารถเลื่อนไปมาได้ขณะที่องค์ประกอบ position: fixed
ยังคงอยู่ ณ ที่เดิม โดยแนบกับวิวพอร์ตของเลย์เอาต์ หากเราแพนดูบริเวณเส้นขอบของวิวพอร์ตเลย์เอาต์ ระบบจะลากวิวพอร์ตเลย์เอาต์ควบคู่ไปด้วย
การปรับปรุงความเข้ากันได้
แต่ Web API ไม่สอดคล้องกันในแง่ของวิวพอร์ตที่อ้างถึงและในเบราว์เซอร์ต่างๆ ก็ไม่สอดคล้องกันด้วย
ตัวอย่างเช่น element.getBoundingClientRect().y
จะแสดงผลออฟเซ็ตภายในวิวพอร์ตของเลย์เอาต์ เยี่ยมไปเลย แต่เรามักต้องการตำแหน่งภายในหน้าเว็บ
เราจึงเขียนว่า
element.getBoundingClientRect().y + window.scrollY
อย่างไรก็ตาม เบราว์เซอร์จำนวนมากใช้วิวพอร์ตภาพสำหรับ window.scrollY
ซึ่งหมายความว่าโค้ดด้านบนจะขัดข้องเมื่อผู้ใช้บีบซูม
Chrome 61 จะเปลี่ยน window.scrollY
ให้อ้างอิงถึงวิวพอร์ตของเลย์เอาต์แทน ซึ่งหมายความว่าโค้ดข้างต้นจะทำงานได้แม้ว่าจะบีบนิ้วเข้าหากันก็ตาม ที่จริงแล้วเบราว์เซอร์กำลังค่อยๆ เปลี่ยนคุณสมบัติตำแหน่งทั้งหมดเพื่ออ้างอิงถึงวิวพอร์ตเลย์เอาต์
ยกเว้นพร็อพเพอร์ตี้ใหม่ 1 รายการ...
การแสดงวิวพอร์ตภาพต่อสคริปต์
API ใหม่แสดงวิวพอร์ตภาพเป็น window.visualViewport
ซึ่งเป็นข้อกำหนดฉบับร่างที่มีการอนุมัติในเบราว์เซอร์ต่างๆ และหน้า Landing Page อยู่ใน Chrome 61
console.log(window.visualViewport.width);
สิ่งที่ window.visualViewport
มอบให้เรามีดังนี้
ที่พัก visualViewport แห่ง |
|
---|---|
offsetLeft
|
ระยะห่างระหว่างขอบด้านซ้ายของวิวพอร์ตภาพและวิวพอร์ตของเลย์เอาต์ในหน่วยพิกเซล CSS |
offsetTop
|
ระยะห่างระหว่างขอบด้านบนของวิวพอร์ตภาพและวิวพอร์ตของเลย์เอาต์ในหน่วยพิกเซล CSS |
pageLeft
|
ระยะห่างระหว่างขอบด้านซ้ายของวิวพอร์ตภาพและขอบเขตด้านซ้ายของเอกสารในหน่วยพิกเซล CSS |
pageTop
|
ระยะห่างระหว่างขอบด้านบนของวิวพอร์ตภาพและขอบเขตด้านบนของเอกสารในหน่วยพิกเซล CSS |
width
|
ความกว้างของวิวพอร์ตภาพในหน่วยพิกเซล CSS |
height
|
ความสูงของวิวพอร์ตภาพในหน่วยพิกเซล CSS |
scale
|
ขนาดที่ใช้โดยการบีบนิ้วเพื่อซูม หากเนื้อหามีขนาดเป็น 2 เท่าของการซูม ระบบจะแสดงผล 2 ซึ่งจะไม่ได้รับผลกระทบจาก devicePixelRatio
|
นอกจากนี้ยังมีอีก 2 เหตุการณ์ ดังนี้
window.visualViewport.addEventListener('resize', listener);
visualViewport เหตุการณ์ |
|
---|---|
resize
|
เริ่มทำงานเมื่อมีการเปลี่ยนแปลง width , height หรือ scale
|
scroll
|
เริ่มทำงานเมื่อมีการเปลี่ยนแปลง offsetLeft หรือ offsetTop
|
การสาธิต
วิดีโอที่ตอนต้นของบทความนี้สร้างขึ้นโดยใช้ visualViewport
ลองดูใน Chrome 61 ขึ้นไป โดยจะใช้ visualViewport
เพื่อทำให้แผนที่ขนาดเล็กยึดอยู่กับมุมบนขวาของวิวพอร์ตภาพ และใช้สเกลกลับด้านเพื่อให้ปรากฏเป็นขนาดเดียวกันเสมอแม้จะมีการซูมแบบบีบนิ้ว
รับทราบ
เหตุการณ์จะเริ่มทำงานเมื่อวิวพอร์ตภาพมีการเปลี่ยนแปลงเท่านั้น
แม้จะเป็นคำพูดที่บ่งบอกอย่างชัดเจน แต่ฉันก็เข้าใจฉันเมื่อเล่นกับ visualViewport
เป็นครั้งแรก
หากวิวพอร์ตของเลย์เอาต์ปรับขนาด แต่วิวพอร์ตภาพไม่ปรับขนาด คุณจะไม่ได้รับเหตุการณ์ resize
อย่างไรก็ตาม มีความผิดปกติที่วิวพอร์ตของเลย์เอาต์จะปรับขนาดโดยที่วิวพอร์ตของภาพไม่เปลี่ยนความกว้าง/ความสูงไปด้วย
Gotcha ที่แท้จริงกำลังเลื่อนดู หากเกิดการเลื่อน แต่วิวพอร์ตแบบภาพยังคงมีค่าคงที่โดยสัมพันธ์กับวิวพอร์ตแบบเลย์เอาต์ คุณจะไม่ได้รับเหตุการณ์ scroll
ใน visualViewport
ซึ่งกรณีนี้พบได้บ่อยจริงๆ ระหว่างที่เลื่อนเอกสารตามปกติ วิวพอร์ตภาพจะยังล็อกไว้ที่ด้านซ้ายบนของวิวพอร์ตเลย์เอาต์เพื่อให้ scroll
ไม่เริ่มทำงานใน visualViewport
หากต้องการฟังเกี่ยวกับการเปลี่ยนแปลงทั้งหมดของวิวพอร์ตแบบภาพ ซึ่งรวมถึง pageTop
และ pageLeft
คุณจะต้องฟังเหตุการณ์การเลื่อนของหน้าต่างด้วย โดยทำดังนี้
visualViewport.addEventListener('scroll', update);
visualViewport.addEventListener('resize', update);
window.addEventListener('scroll', update);
หลีกเลี่ยงการทำซ้ำงานกับผู้ฟังหลายคน
เช่นเดียวกับการฟัง scroll
และ resize
ในหน้าต่าง คุณก็มีแนวโน้มที่จะเรียกใช้ฟังก์ชัน "อัปเดต" บางประเภท แต่เหตุการณ์เหล่านี้มักจะเกิดขึ้นพร้อมกัน หากผู้ใช้ปรับขนาดหน้าต่าง ไอคอนจะทริกเกอร์ resize
แต่ก็มักจะเป็น scroll
ด้วย หากต้องการปรับปรุงประสิทธิภาพ ให้หลีกเลี่ยง
การเปลี่ยนแปลงหลายครั้ง ดังนี้
// 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
});
}
ฉันยื่นเรื่องข้อกำหนดเฉพาะสำหรับกรณีนี้ เพราะคิดว่าอาจมีวิธีที่ดีกว่า เช่น เหตุการณ์ update
รายการเดียว
ตัวแฮนเดิลเหตุการณ์ไม่ทำงาน
เนื่องจากข้อบกพร่องของ Chrome การดำเนินการนี้ไม่สำเร็จ
ข้อบกพร่อง – ใช้เครื่องจัดการเหตุการณ์
visualViewport.onscroll = () => console.log('scroll!');
ให้ดำเนินการต่อไปนี้แทน
งาน – ใช้ Listener เหตุการณ์
visualViewport.addEventListener('scroll', () => console.log('scroll'));
ค่าออฟเซ็ตจะปัดเศษ
ฉันคิดเหมือนกัน (หวังว่านี่จะเป็นข้อบกพร่องอีกรายการของ Chrome นะ
offsetLeft
และ offsetTop
จะโค้งมน ซึ่งค่อนข้างไม่ถูกต้องเมื่อผู้ใช้ซูมเข้า คุณเห็นปัญหาของการดำเนินการนี้ระหว่างการสาธิต หากผู้ใช้ซูมเข้าและเลื่อนอย่างช้าๆ แผนที่ขนาดเล็กจะสแนประหว่างพิกเซลที่ไม่ได้ซูม
อัตราเหตุการณ์ช้า
เหตุการณ์เหล่านี้ไม่ได้เริ่มทำงานทุกเฟรม โดยเฉพาะเหตุการณ์ในอุปกรณ์เคลื่อนที่ เช่นเดียวกับเหตุการณ์ resize
และ scroll
อื่นๆ ซึ่งคุณจะดูได้ระหว่างการสาธิต เมื่อบีบซูม แผนที่ขนาดเล็กจะมีปัญหาในการล็อกวิวพอร์ต
การช่วยเหลือพิเศษ
ในการสาธิต ผมใช้ visualViewport
เพื่อโต้แย้งการบีบนิ้วของผู้ใช้ การสาธิตนี้เหมาะสำหรับการสาธิตนี้ แต่คุณควรคิดอย่างรอบคอบก่อนที่จะดำเนินการใดๆ ที่ลบล้างความต้องการของผู้ใช้ที่จะซูมเข้า
ใช้ visualViewport
เพื่อปรับปรุงการช่วยเหลือพิเศษได้ ตัวอย่างเช่น หากผู้ใช้กำลังซูมเข้า คุณอาจเลือกซ่อนสิ่งของตกแต่ง position: fixed
เพื่อไม่ให้เกะกะผู้ใช้ ขอย้ำว่า โปรดระวังคุณไม่ได้ซ่อนบางสิ่งที่ผู้ใช้
ต้องการที่จะสอดส่องอย่างใกล้ชิด
คุณอาจพิจารณาโพสต์ไปยังบริการวิเคราะห์เมื่อผู้ใช้ซูมเข้า ซึ่งจะช่วยให้คุณระบุหน้าเว็บที่ผู้ใช้พบปัญหาที่ระดับการซูมเริ่มต้นได้
visualViewport.addEventListener('resize', () => {
if (visualViewport.scale > 1) {
// Post data to analytics service
}
});
เพียงเท่านี้ก็เรียบร้อยแล้ว visualViewport
เป็น API เล็กๆ ที่มีประโยชน์ซึ่งช่วยแก้ไขปัญหาความเข้ากันได้ระหว่างทาง