การทำภาพเคลื่อนไหวเบลอ

การเบลอเป็นวิธีที่ยอดเยี่ยมในการเปลี่ยนเส้นทางจุดสนใจของผู้ใช้ การทำให้องค์ประกอบภาพบางส่วนดูเบลอในขณะที่องค์ประกอบอื่นๆ อยู่ในจุดโฟกัสจะเน้นโฟกัสของผู้ใช้อย่างเป็นธรรมชาติ ผู้ใช้จะไม่สนใจเนื้อหาที่ถูกเบลอ แต่กลับมุ่งความสนใจไปที่เนื้อหาที่อ่านได้ ตัวอย่างหนึ่งคือรายการของไอคอนที่แสดงรายละเอียด ของแต่ละรายการเมื่อวางเมาส์เหนือรายการนั้นๆ ในระหว่างนั้น ระบบอาจเบลอตัวเลือกที่เหลือเพื่อเปลี่ยนเส้นทางผู้ใช้ไปยังข้อมูลที่แสดงใหม่

สรุปคร่าวๆ

จริงๆ แล้วการสร้างภาพเคลื่อนไหวเบลอไม่ใช่ตัวเลือกเพราะทำได้ช้ามาก แต่ให้คำนวณล่วงหน้าสำหรับชุดเวอร์ชันที่เบลอขึ้นเรื่อยๆ และค่อยๆ จางหายไป เพื่อนร่วมงานของฉัน Yi Gu เขียนห้องสมุดเพื่อจัดการทุกอย่างให้คุณ! โปรดดูการสาธิต

อย่างไรก็ตาม เทคนิคนี้อาจทำให้รู้สึกหวาดกลัวมากเมื่อนำไปใช้โดยไม่มีช่วงการเปลี่ยนผ่าน การสร้างภาพเคลื่อนไหวแบบเบลอ (เปลี่ยนจากการไม่เบลอเป็นการเบลอภาพดูจะเป็นตัวเลือกที่สมเหตุสมผล แต่ถ้าคุณเคยลองทำบนเว็บ คุณอาจพบว่าภาพเคลื่อนไหวนั้นราบรื่น ดังที่การสาธิตนี้แสดงให้เห็นว่าหากคุณไม่ได้ใช้เครื่องที่มีประสิทธิภาพ เราจะปรับปรุงให้ดีขึ้นได้ไหม

ปัญหา

CPU เปลี่ยนมาร์กอัปเป็นพื้นผิว ระบบจะอัปโหลดพื้นผิวต่างๆ ไปยัง GPU GPU จะวาดพื้นผิวเหล่านี้ไปยังเฟรมบัฟเฟอร์โดยใช้ตัวปรับแสง การเบลอจะเกิดขึ้น
ในโปรแกรมให้แสงเงา

ขณะนี้เราไม่สามารถสร้างภาพเคลื่อนไหวที่เบลอได้อย่างมีประสิทธิภาพ อย่างไรก็ดี เราหาวิธีแก้ปัญหาที่ดีพอ แต่จริงๆ แล้วไม่ใช่ภาพเคลื่อนไหวเบลอ ก่อนอื่น เรามาดูกันว่าทำไมการเบลอภาพเคลื่อนไหวจึงทำงานช้า หากต้องการเบลอองค์ประกอบในเว็บ มี 2 เทคนิค ได้แก่ พร็อพเพอร์ตี้ filter ของ CSS และฟิลเตอร์ SVG เนื่องจากการสนับสนุนที่เพิ่มขึ้นและความสะดวกในการใช้งาน ระบบจึงมักจะ ใช้ตัวกรอง CSS ต้องขออภัยที่ IE 10 และ 11 รองรับ Internet Explorer ด้วย แต่ถึงจะไม่รองรับตัวกรอง CSS ก็ใช้ฟิลเตอร์ SVG ไม่ได้ ข่าวดีคือวิธีแก้ปัญหาเบื้องต้นสำหรับภาพเคลื่อนไหวเบลอสามารถใช้ได้กับทั้ง 2 เทคนิค เรามาลองหาจุดคอขวด โดยดูที่เครื่องมือสำหรับนักพัฒนาเว็บ

หากคุณเปิดใช้ "Paint Flashing" ในเครื่องมือสำหรับนักพัฒนาเว็บ คุณจะไม่เห็นแฟลชเลย ดูเหมือนจะไม่มีการสร้างภาพใหม่ขึ้นมาเลย ซึ่งในทางเทคนิคแล้ว "การวาดใหม่" หมายถึง CPU ที่มีการรีดพื้นผิวขององค์ประกอบที่โปรโมต เมื่อใดก็ตามที่องค์ประกอบได้รับทั้งการโปรโมตและเบลอ GPU จะใช้การเบลอด้วยตัวปรับแสงเงา

ทั้งฟิลเตอร์ SVG และตัวกรอง CSS จะใช้ฟิลเตอร์คอนโวลูชันเพื่อใช้การเบลอ ตัวกรองคอนโวลูชันมีราคาค่อนข้างแพงเนื่องจากทุกพิกเซลเอาต์พุตจะต้องพิจารณาจำนวนพิกเซลอินพุต ยิ่งภาพใหญ่หรือรัศมีการเบลอ ก็จะยิ่งมีค่าใช้จ่ายมากขึ้น

และนั่นคือสาเหตุของปัญหา เราใช้งาน GPU ที่ค่อนข้างแพงในทุกๆ เฟรม ซึ่งทำให้งบประมาณเฟรมของเราอยู่ที่ 16 มิลลิวินาที จึงกลายเป็นว่าต่ำกว่า 60 FPS อยู่เลย

เลื่อนลงไปตามรูกระต่าย

แล้วเราควรทำอย่างไรเพื่อให้การดำเนินการนี้เป็นไปอย่างราบรื่น เราใช้มือได้อย่างคล่องแคล่ว แทนที่จะสร้างการเคลื่อนไหวให้ค่าการเบลอจริง (รัศมีของการเบลอ) เราจะคำนวณสำเนาส่วนที่เบลอไว้ล่วงหน้า ซึ่งค่าการเบลอเพิ่มขึ้นแบบทวีคูณ จากนั้นจึงค่อยๆ เลือนหายไปโดยใช้ opacity

ภาพกากบาทคือการค่อยๆ เพิ่มความทึบแสงและการค่อยๆ เลือนหายไปเป็นชุด ตัวอย่างเช่น ถ้าเรามีระยะการเบลอ 4 ระดับ เราจะเฟดออกจากระยะแรกพร้อมกับเฟดในขั้นตอนที่ 2 พร้อมกัน เมื่อขั้นที่ 2 ความทึบแสงถึง 100% และขั้นที่ 1 ถึง 0% เราจะจางลงในขั้นตอนที่ 2 และเฟดในขั้นตอนที่ 3 เมื่อทำเสร็จแล้ว สุดท้ายเราจะเฟดขั้นตอนที่ 3 ออกมาและเฟดในเวอร์ชันที่ 4 ซึ่งเป็นเวอร์ชันที่สุดท้าย ในสถานการณ์นี้ แต่ละขั้นตอนจะใช้เวลา 1⁄4 ของระยะเวลาที่ต้องการทั้งหมด ซึ่งจะดูคล้ายกับการเบลอ แบบเคลื่อนไหวจริงๆ

ในการทดลองของเรา การเพิ่มรัศมีการเบลอแบบทวีคูณต่อระยะจะให้ผลลัพธ์ที่ดีที่สุด ตัวอย่างเช่น หากเรามีระยะการเบลอ 4 ระดับ เราจะใช้ filter: blur(2^n) กับแต่ละระยะ ได้แก่ ระยะ 0: 1px, ระยะ 1: 2px, ระยะ 2: 4px และระยะ 3: 8 px หากเราบังคับให้สำเนาที่เบลอเหล่านี้แต่ละรายการติดลงบนเลเยอร์ของตนเอง (เรียกว่า "การโปรโมต") โดยใช้ will-change: transform การเปลี่ยนแปลงความทึบแสงบนองค์ประกอบเหล่านี้ควรกระทำได้อย่างรวดเร็ว ในทางทฤษฎีแล้ว นี่ช่วยให้เรา อัปโหลดงานที่มีราคาแพงมาไว้ด้านหน้า ปรากฏว่าตรรกะมีข้อบกพร่อง หากใช้งานการสาธิตนี้ คุณจะเห็นว่าอัตราเฟรมยังคงต่ำกว่า 60 FPS และการเบลอแย่กว่าเมื่อก่อน

เครื่องมือสำหรับนักพัฒนาเว็บแสดงการติดตามที่ GPU ทำงานไม่ว่างเป็นเวลานาน

การดูเครื่องมือสำหรับนักพัฒนาเว็บอย่างรวดเร็วจะเผยให้เห็นว่า GPU ยังคงยุ่งมากและยืดแต่ละเฟรมเป็นประมาณ 90 มิลลิวินาที แต่ทำไมล่ะ เราไม่ได้เปลี่ยนแปลงค่าการเบลอ เพียงแค่ความทึบแสง แล้วเกิดอะไรขึ้น ปัญหาอยู่ตรงที่ลักษณะของเอฟเฟกต์การเบลอ ดังนั้น แม้ว่าเราจะไม่ได้ทำให้ค่าการเบลอเป็นแบบภาพเคลื่อนไหวอีกต่อไป แต่ตัวพื้นผิวเองก็ยังไม่ได้ทำให้เบลอ และ GPU จะต้องทำการเบลอทุกเฟรมอีกครั้ง สาเหตุที่อัตราเฟรมแย่ลงกว่าเดิมนั้นมาจากข้อเท็จจริงที่ว่าเมื่อเปรียบเทียบกับการใช้งานแบบไร้เดียงสาแล้ว GPU ทำงานได้มากกว่าแต่ก่อน เนื่องจากโดยส่วนใหญ่แล้ว พื้นผิว 2 แบบจะต้องเบลอแยกกัน

ภาพที่เราคิดไม่ค่อยสวยงาม แต่ก็ทำให้ภาพเคลื่อนไหวเร็วมาก เรากลับไปไม่โปรโมตองค์ประกอบที่ต้องเบลอ แต่ให้โปรโมต Wrapper หลักแทน หากทั้งเบลอและโปรโมตองค์ประกอบแล้ว GPU จะใช้เอฟเฟกต์ดังกล่าว สิ่งนี้ทำให้การสาธิตของเราช้าลง หากองค์ประกอบนั้นเบลอ แต่ไม่ได้มีการโปรโมต การเบลอจะถูกแรสเตอร์เป็นพื้นผิวระดับบนสุดที่ใกล้ที่สุดแทน ในกรณีของเราคือองค์ประกอบ Wrapper ระดับบนสุดที่โปรโมต ตอนนี้รูปภาพที่เบลอจะเป็นพื้นผิวขององค์ประกอบระดับบนสุดและสามารถนำมาใช้กับเฟรมทั้งหมดในอนาคตได้ วิธีนี้ได้ผลเท่านั้นเนื่องจากเราทราบว่าองค์ประกอบที่เบลอไม่ได้มีการเคลื่อนไหว และการแคชองค์ประกอบนั้นเป็นประโยชน์อย่างแท้จริง นี่คือการสาธิตที่ใช้เทคนิคนี้ สงสัยจังว่า Moto G4 คิดอย่างไรกับแนวทางนี้ ระวังสปอยล์: สนุกดีนะ:

เครื่องมือสำหรับนักพัฒนาเว็บแสดงการติดตามที่ GPU มีเวลาไม่มีการใช้งานจำนวนมาก

ตอนนี้เรามี GPU ที่ว่างอยู่มากมายและ 60 FPS ที่ราบรื่นราบรื่น เราทำสำเร็จแล้ว!

การผลิต

ในการสาธิต เราทำโครงสร้าง DOM ซ้ำหลายครั้งเพื่อให้มีสำเนาของเนื้อหาที่ต้องเบลอด้วยจุดแข็งที่ต่างกัน คุณอาจสงสัยว่าวิธีนี้ทำงานอย่างไรในสภาพแวดล้อมการใช้งานจริง เนื่องจากอาจส่งผลข้างเคียงที่ไม่ได้ตั้งใจกับรูปแบบ CSS ของผู้เขียนหรือแม้แต่ JavaScript ของผู้เขียน คุณคิดถูกแล้ว ป้อน Shadow DOM!

แม้ว่าผู้คนส่วนใหญ่จะมองว่า Shadow DOM เป็นวิธีติดองค์ประกอบ "ภายใน" เข้ากับองค์ประกอบที่กำหนดเอง แต่ก็ยังเป็นการแยกองค์ประกอบและมาตรฐานประสิทธิภาพด้วย JavaScript และ CSS ไม่สามารถเจาะขอบเขต Shadow DOM ซึ่งเปิดโอกาสให้เราทำซ้ำเนื้อหาโดยไม่รบกวนสไตล์หรือตรรกะของแอปพลิเคชันของนักพัฒนาซอฟต์แวร์ เรามีเอลิเมนต์ <div> อยู่แล้วสำหรับสำเนาแต่ละรายการที่จะแรสเตอร์ลงใน และใช้ <div> เหล่านี้เป็นเงาโฮสต์ เราจะสร้าง ShadowRoot โดยใช้ attachShadow({mode: 'closed'}) และแนบสำเนาของเนื้อหาลงใน ShadowRoot แทนตัว <div> เราต้องตรวจสอบให้แน่ใจว่าได้คัดลอกสไตล์ชีตทั้งหมดไปยัง ShadowRoot เพื่อรับประกันว่าสำเนาของเราจะได้รับการจัดรูปแบบเหมือนกับต้นฉบับ

บางเบราว์เซอร์ไม่รองรับ Shadow DOM v1 และสำหรับอย่างนั้น เราจะกลับไปทำซ้ำเนื้อหาและหวังว่าเนื้อหาจะสมบูรณ์แบบ เราใช้ Shadow DOM polyfill กับ ShadyCSS ได้ แต่เราไม่ได้นำไปใช้ในคลัง

จบแล้ว หลังจากที่ไล่ตามไปป์ไลน์การแสดงภาพของ Chrome เราคิดหาวิธีทำให้ภาพเคลื่อนไหวเบลออย่างมีประสิทธิภาพในเบราว์เซอร์ต่างๆ

บทสรุป

คุณควรใช้เอฟเฟกต์ประเภทนี้เพียงเล็กน้อย การที่เราคัดลอกองค์ประกอบ DOM และบังคับองค์ประกอบเหล่านั้นไปยังเลเยอร์ของตัวเอง จะทำให้เราสามารถเกินขีดจำกัดของอุปกรณ์ระดับล่างได้ การคัดลอกสไตล์ชีตทั้งหมดลงใน ShadowRoot แต่ละรายการก็ถือเป็นความเสี่ยงด้านประสิทธิภาพเช่นกัน ดังนั้น คุณควรตัดสินใจว่าคุณต้องการปรับตรรกะและรูปแบบเพื่อไม่ให้ได้รับผลกระทบจากสำเนาใน LightDOM หรือใช้เทคนิค ShadowDOM ของเรา แต่บางครั้งเทคนิคของเราอาจคุ้มค่าต่อการลงทุน ดูโค้ดในที่เก็บของ GitHub รวมถึงการสาธิตและติดต่อเราทาง Twitter หากมีข้อสงสัยใดๆ