การนำเลโก้® ไปไว้ในเว็บที่รองรับหลายอุปกรณ์
Build with Chrome เป็นการทดลองสนุกๆ สำหรับผู้ใช้ Chrome บนเดสก์ท็อปที่เปิดตัวครั้งแรกในออสเตรเลีย เปิดตัวอีกครั้งในปี 2014 โดยให้บริการทั่วโลก มีการเชื่อมโยงกับภาพยนตร์เรื่อง THE LEGO® MOVIE™ และเพิ่มการรองรับอุปกรณ์เคลื่อนที่เข้ามาใหม่ ในบทความนี้ เราจะแชร์สิ่งที่ได้เรียนรู้จากโปรเจ็กต์นี้ โดยเฉพาะเกี่ยวกับการเปลี่ยนจากประสบการณ์การใช้งานบนเดสก์ท็อปเท่านั้นเป็นโซลูชันแบบหลายหน้าจอที่รองรับทั้งการป้อนข้อมูลด้วยเมาส์และการสัมผัส
ประวัติของ Build with Chrome
เวอร์ชันแรกของ "สร้างด้วย Chrome" เปิดตัวในออสเตรเลียเมื่อปี 2012 เราต้องการแสดงให้เห็นถึงพลังของเว็บในแบบใหม่ทั้งหมดและนำ Chrome ไปให้ผู้ชมกลุ่มใหม่ได้รู้จัก
เว็บไซต์มี 2 ส่วนหลักๆ ได้แก่ โหมด "สร้าง" ที่ผู้ใช้สามารถสร้างผลงานโดยใช้บล็อก LEGO และโหมด "สำรวจ" สำหรับเรียกดูผลงานใน Google Maps เวอร์ชัน LEGO
โมเดล 3 มิติแบบอินเทอร์แอกทีฟเป็นสิ่งจําเป็นในการมอบประสบการณ์การสร้าง LEGO ที่ดีที่สุดให้แก่ผู้ใช้ ในปี 2012 WebGL พร้อมให้บริการแบบสาธารณะในเบราว์เซอร์เดสก์ท็อปเท่านั้น Build จึงมีเป้าหมายเป็นประสบการณ์การใช้งานบนเดสก์ท็อปเท่านั้น สำรวจใช้ Google Maps เพื่อแสดงผลงาน แต่เมื่อซูมเข้าใกล้พอ ระบบจะเปลี่ยนไปใช้ WebGL กับแผนที่ที่แสดงผลงานเป็น 3 มิติ โดยยังคงใช้ Google Maps เป็นพื้นผิวของฐาน เราหวังว่าจะสร้างสภาพแวดล้อมที่ผู้สนใจ LEGO ทุกวัยสามารถแสดงความคิดสร้างสรรค์และสำรวจผลงานของกันและกันได้ง่ายๆ และตรงไปตรงมา
ในปี 2013 เราตัดสินใจขยายบริการ "สร้างด้วย Chrome" ไปยังเทคโนโลยีเว็บใหม่ๆ เทคโนโลยีดังกล่าวรวมถึง WebGL ใน Chrome สำหรับ Android ซึ่งจะช่วยให้ Build with Chrome พัฒนาไปสู่ประสบการณ์การใช้งานบนอุปกรณ์เคลื่อนที่ได้ ในการเริ่มต้น เราพัฒนาต้นแบบการสัมผัสก่อน แล้วจึงสอบถามฮาร์ดแวร์สำหรับ "เครื่องมือสร้าง" เพื่อทําความเข้าใจลักษณะการทำงานของท่าทางสัมผัสและการตอบสนองต่อการสัมผัสที่อาจพบผ่านเบราว์เซอร์เมื่อเทียบกับแอปบนอุปกรณ์เคลื่อนที่
ฟรอนต์เอนด์ที่ปรับเปลี่ยนตามอุปกรณ์
เราต้องรองรับอุปกรณ์ทั้งแบบใช้การสัมผัสและแบบใช้เมาส์ อย่างไรก็ตาม การใช้ UI เดียวกันบนหน้าจอสัมผัสขนาดเล็กกลับกลายเป็นโซลูชันที่ไม่เหมาะที่สุดเนื่องจากข้อจำกัดด้านพื้นที่
ในโหมดสร้าง ผู้เล่นจะโต้ตอบกับสิ่งต่างๆ ได้มากมาย เช่น การซูมเข้าและออก การเปลี่ยนสีของบล็อก และแน่นอนว่าการเลือก หมุน และวางบล็อก เนื่องจากเป็นเครื่องมือที่ผู้ใช้มักใช้เวลาอยู่นาน จึงเป็นเรื่องสำคัญที่จะทำให้ผู้ใช้เข้าถึงทุกอย่างที่ใช้บ่อยได้อย่างรวดเร็วและรู้สึกสะดวกสบายเมื่อโต้ตอบกับเครื่องมือ
เมื่อออกแบบแอปพลิเคชันการสัมผัสแบบอินเทอร์แอกทีฟสูง คุณอาจพบว่าหน้าจอดูเล็กลงอย่างรวดเร็วและนิ้วของผู้ใช้มักจะบังพื้นที่ส่วนใหญ่ของหน้าจอขณะโต้ตอบ เราเห็นข้อดีนี้อย่างชัดเจนเมื่อทำงานร่วมกับ Builder คุณจะต้องพิจารณาขนาดหน้าจอจริงแทนพิกเซลในกราฟิกเมื่อออกแบบ คุณจึงควรลดจำนวนปุ่มและการควบคุมต่างๆ เพื่อเพิ่มพื้นที่หน้าจอให้กับเนื้อหาจริงให้ได้มากที่สุด
เป้าหมายของเราคือทำให้ Build ใช้งานได้อย่างเป็นธรรมชาติบนอุปกรณ์แบบสัมผัส ไม่ใช่แค่เพิ่มอินพุตแบบสัมผัสลงในการใช้งานบนเดสก์ท็อปเวอร์ชันเดิม แต่ทำให้รู้สึกเหมือนว่า Build ออกแบบมาเพื่อการใช้งานแบบสัมผัสจริงๆ สุดท้ายเราจึงออกแบบ UI ให้มี 2 รูปแบบ ได้แก่ 1 รูปแบบสำหรับเดสก์ท็อปและแท็บเล็ตที่มีหน้าจอขนาดใหญ่ และอีก 1 รูปแบบสำหรับอุปกรณ์เคลื่อนที่ที่มีหน้าจอขนาดเล็ก หากเป็นไปได้ วิธีที่ดีที่สุดคือใช้การติดตั้งใช้งานแบบเดียวและมีการเปลี่ยนไปมาระหว่างโหมดอย่างราบรื่น ในกรณีของเรา เราพบว่าประสบการณ์การใช้งานระหว่าง 2 โหมดนี้แตกต่างกันมาก เราจึงตัดสินใจใช้จุดแบ่งที่เฉพาะเจาะจง 2 เวอร์ชันนี้มีฟีเจอร์ที่เหมือนกันหลายอย่าง และเราพยายามทําสิ่งต่างๆ ส่วนใหญ่ด้วยการติดตั้งใช้งานโค้ดเพียงโค้ดเดียว แต่ UI บางแง่มุมของ 2 เวอร์ชันนี้ทํางานแตกต่างกัน
เราใช้ข้อมูล User Agent เพื่อตรวจหาอุปกรณ์เคลื่อนที่ จากนั้นตรวจสอบขนาดวิวพอร์ตเพื่อตัดสินใจว่าควรใช้ UI บนอุปกรณ์เคลื่อนที่หน้าจอขนาดเล็กหรือไม่ การเลือกเบรกพอยต์สำหรับ "หน้าจอขนาดใหญ่" นั้นค่อนข้างยาก เนื่องจากหาค่าที่เชื่อถือได้ของขนาดหน้าจอจริงได้ยาก แต่โชคดีที่ในกรณีของเรา การแสดง UI หน้าจอขนาดเล็กบนอุปกรณ์แบบสัมผัสที่มีหน้าจอขนาดใหญ่นั้นไม่สำคัญมากนัก เนื่องจากเครื่องมือจะยังคงทำงานได้อย่างถูกต้อง เพียงแต่ปุ่มบางปุ่มอาจดูใหญ่ไปหน่อย สุดท้าย เราตั้งจุดหยุดที่ 1, 000 พิกเซล หากคุณโหลดเว็บไซต์จากหน้าต่างที่กว้างกว่า 1,000 พิกเซล (ในโหมดแนวนอน) คุณจะเห็นเวอร์ชันหน้าจอขนาดใหญ่
เรามาพูดถึงขนาดหน้าจอและประสบการณ์การใช้งาน 2 แบบกัน
หน้าจอขนาดใหญ่ที่รองรับเมาส์และการสัมผัส
เวอร์ชันหน้าจอขนาดใหญ่จะแสดงในคอมพิวเตอร์เดสก์ท็อปทุกเครื่องที่รองรับเมาส์และอุปกรณ์แบบสัมผัสที่มีหน้าจอขนาดใหญ่ (เช่น Google Nexus 10) เวอร์ชันนี้คล้ายกับโซลูชันเดสก์ท็อปเวอร์ชันแรกในด้านการควบคุมการไปยังส่วนต่างๆ แต่เราได้เพิ่มการรองรับการสัมผัสและท่าทางสัมผัสบางอย่าง เราปรับ UI ตามขนาดหน้าต่าง ดังนั้นเมื่อผู้ใช้ปรับขนาดหน้าต่าง ระบบอาจนำ UI บางรายการออกหรือปรับขนาด UI บางรายการ เราทําเช่นนี้โดยใช้ Media Queries ของ CSS
ตัวอย่างเช่น เมื่อความสูงที่ใช้ได้น้อยกว่า 730 พิกเซล ระบบจะซ่อนตัวควบคุมแถบเลื่อนการซูมในโหมดสํารวจ
@media only screen and (max-height: 730px) {
.zoom-slider {
display: none;
}
}
หน้าจอขนาดเล็ก รองรับการสัมผัสเท่านั้น
เวอร์ชันนี้จะแสดงในอุปกรณ์เคลื่อนที่และแท็บเล็ตขนาดเล็ก (อุปกรณ์เป้าหมาย Nexus 4 และ Nexus 7) เวอร์ชันนี้ต้องใช้การรองรับการสัมผัสหลายจุด
ในอุปกรณ์หน้าจอขนาดเล็ก เราต้องจัดสรรพื้นที่บนหน้าจอให้เนื้อหามากที่สุด จึงทำการปรับเปลี่ยนเล็กน้อยเพื่อเพิ่มพื้นที่ให้มากที่สุด โดยส่วนใหญ่เป็นการย้ายองค์ประกอบที่ใช้ไม่บ่อยให้ออกไปให้พ้นสายตา
- เครื่องมือเลือกบล็อกการสร้างจะย่อขนาดเป็นเครื่องมือเลือกสีขณะสร้าง
- เราได้แทนที่การควบคุมการซูมและการวางแนวด้วยท่าทางสัมผัสแบบมัลติทัช
- ฟังก์ชันการทำงานแบบเต็มหน้าจอของ Chrome ยังมีประโยชน์ในการเพิ่มพื้นที่หน้าจอด้วย
ประสิทธิภาพและการรองรับ WebGL
อุปกรณ์แบบสัมผัสสมัยใหม่มี GPU ที่มีประสิทธิภาพค่อนข้างสูง แต่ก็ยังคงมีประสิทธิภาพต่ำกว่าอุปกรณ์เดสก์ท็อปอยู่มาก เราจึงทราบดีว่าอาจพบปัญหาด้านประสิทธิภาพ โดยเฉพาะในโหมดสํารวจ 3 มิติที่เราต้องแสดงผลผลงานจำนวนมากพร้อมกัน
ในด้านความคิดสร้างสรรค์ เราต้องการเพิ่มอิฐอีก 2 ประเภทที่มีรูปร่างซับซ้อนและมีความโปร่งใส ซึ่งปกติแล้วเป็นฟีเจอร์ที่ต้องใช้ GPU มาก อย่างไรก็ตาม เราจำเป็นต้องใช้งานร่วมกับเวอร์ชันเก่าและรองรับผลงานจากเวอร์ชันแรกต่อไป จึงไม่สามารถกำหนดข้อจำกัดใหม่ เช่น การลดจำนวนบล็อกทั้งหมดในผลงานลงอย่างมาก
ใน Build เวอร์ชันแรก เราจำกัดจำนวนบล็อกสูงสุดที่ใช้ในการสร้าง 1 รายการ มี "เครื่องวัดอิฐ" ที่บอกจำนวนอิฐที่เหลืออยู่ ในการใช้งานใหม่นี้ เรามีบริกส์ใหม่บางรายการที่จะส่งผลต่อ Brick Meter มากกว่าบริกส์มาตรฐาน จึงลดจํานวนบริกส์สูงสุดทั้งหมดลงเล็กน้อย วิธีนี้เป็นวิธีหนึ่งในการรวมบล็อกใหม่ในขณะที่ยังคงรักษาประสิทธิภาพที่ดี
ในโหมดสำรวจ 3 มิติ มีการดำเนินการหลายอย่างเกิดขึ้นพร้อมกัน เช่น โหลดพื้นผิวเพลตฐานรอง โหลดผลงาน แสดงภาพเคลื่อนไหวและแสดงผลผลงาน และอื่นๆ การดำเนินการนี้ต้องใช้ทั้ง GPU และ CPU เป็นจำนวนมาก เราจึงทำการโปรไฟล์เฟรมในเครื่องมือสำหรับนักพัฒนาซอฟต์แวร์ของ Chrome เป็นจำนวนมากเพื่อเพิ่มประสิทธิภาพส่วนเหล่านี้ให้ได้มากที่สุด ในอุปกรณ์เคลื่อนที่ เราตัดสินใจที่จะซูมเข้าใกล้ผลงานมากขึ้นเพื่อที่เราจะได้ไม่ต้องแสดงผลผลงานหลายรายการพร้อมกัน
อุปกรณ์บางรุ่นทำให้เราต้องกลับมาแก้ไขและลดความซับซ้อนของโปรแกรมเปลี่ยนสี WebGL บางรายการ แต่เราพบวิธีแก้ปัญหาและเดินหน้าต่อได้เสมอ
การรองรับอุปกรณ์ที่ไม่ใช่ WebGL
เราต้องการให้เว็บไซต์ใช้งานได้บ้าง แม้ว่าอุปกรณ์ของผู้เข้าชมจะไม่รองรับ WebGL ก็ตาม ในบางครั้งอาจมีวิธีแสดงภาพ 3 มิติในลักษณะที่ง่ายขึ้นโดยใช้โซลูชัน Canvas หรือฟีเจอร์ CSS3D ขออภัย เรายังไม่พบวิธีแก้ปัญหาที่ได้ผลดีพอในการจำลองฟีเจอร์สร้างและสำรวจ 3 มิติโดยไม่ใช้ WebGL
สไตล์ภาพของผลงานต้องเหมือนกันในทุกแพลตฟอร์มเพื่อให้สอดคล้องกัน เราอาจลองใช้โซลูชัน 2.5 มิติ แต่วิธีนี้จะทำให้ผลงานมีรูปลักษณ์แตกต่างออกไปในบางประการ นอกจากนี้ เรายังต้องพิจารณาวิธีตรวจสอบว่าผลงานที่สร้างด้วย "สร้างด้วย Chrome" เวอร์ชันแรกจะมีลักษณะเหมือนกันและทำงานได้อย่างราบรื่นในเว็บไซต์เวอร์ชันใหม่เช่นเดียวกับในเวอร์ชันแรก
อุปกรณ์ที่ไม่ใช่ WebGL จะยังเข้าถึงโหมดสำรวจ 2 มิติได้ แม้ว่าคุณจะสร้างผลงานใหม่หรือสำรวจแบบ 3 มิติไม่ได้ก็ตาม ดังนั้นผู้ใช้จะยังคงเห็นภาพความซับซ้อนของโปรเจ็กต์และสิ่งที่ตนเองสร้างได้โดยใช้เครื่องมือนี้หากใช้อุปกรณ์ที่เปิดใช้ WebGL เว็บไซต์อาจไม่มีคุณค่าต่อผู้ใช้ที่ไม่รองรับ WebGL แต่อย่างน้อยก็ควรทำหน้าที่เป็นตัวอย่างและดึงดูดให้ผู้ใช้ลองใช้
บางครั้งเราไม่สามารถเก็บเวอร์ชันสำรองสำหรับโซลูชัน WebGL ไว้ได้ สาเหตุที่เป็นไปได้นั้นมีมากมาย เช่น ประสิทธิภาพ สไตล์ภาพ ค่าใช้จ่ายในการพัฒนาและการบำรุงรักษา และอื่นๆ อย่างไรก็ตาม เมื่อตัดสินใจที่จะไม่ใช้การแสดงผลสำรอง คุณควรดูแลผู้เข้าชมที่ไม่ได้เปิดใช้ WebGL เป็นอย่างน้อย อธิบายสาเหตุที่ผู้เข้าชมเข้าถึงเว็บไซต์ได้ไม่เต็มรูปแบบ และบอกวิธีแก้ปัญหาโดยใช้เบราว์เซอร์ที่รองรับ WebGL
การจัดการเนื้อหา
ในปี 2013 Google ได้เปิดตัว Google Maps เวอร์ชันใหม่ที่มีการเปลี่ยนแปลง UI ที่สำคัญที่สุดนับตั้งแต่เปิดตัว เราจึงตัดสินใจออกแบบ Build with Chrome ใหม่ให้เข้ากับ UI ของ Google Maps เวอร์ชันใหม่ และนำปัจจัยอื่นๆ เข้ามาพิจารณาด้วย การออกแบบใหม่ค่อนข้างแบนโดยใช้สีพื้นแบบสะอาดตาและรูปทรงเรียบง่าย ซึ่งทำให้เราใช้ CSS ล้วนๆ ในองค์ประกอบ UI จำนวนมากได้ จึงลดการใช้รูปภาพ
ใน "สำรวจ" เราจำเป็นต้องโหลดรูปภาพจำนวนมาก ไม่ว่าจะเป็นรูปภาพขนาดย่อสำหรับผลงาน พื้นผิวแผนที่สำหรับฐาน และสุดท้ายคือผลงาน 3 มิติจริง เราตรวจสอบอย่างละเอียดเพื่อให้แน่ใจว่าไม่มีการรั่วไหลของหน่วยความจำเมื่อโหลดรูปภาพใหม่อย่างต่อเนื่อง
ผลงาน 3 มิติจะจัดเก็บในรูปแบบไฟล์ที่กำหนดเองซึ่งบรรจุเป็นภาพ PNG การเก็บข้อมูลผลงาน 3 มิติเป็นภาพทำให้เราส่งข้อมูลไปยังชิเดอร์ที่แสดงผลผลงานได้โดยตรง
สำหรับรูปภาพที่ผู้ใช้สร้างขึ้นทั้งหมด การออกแบบนี้ช่วยให้เราใช้รูปภาพขนาดเดียวกันกับทุกแพลตฟอร์มได้ จึงช่วยลดพื้นที่เก็บข้อมูลและการใช้แบนด์วิดท์
การจัดการการวางแนวหน้าจอ
คุณอาจลืมไปว่าสัดส่วนการแสดงผลของหน้าจอจะเปลี่ยนแปลงมากน้อยเพียงใดเมื่อเปลี่ยนจากโหมดแนวตั้งเป็นโหมดแนวนอนหรือในทางกลับกัน คุณต้องคำนึงถึงเรื่องนี้ตั้งแต่เริ่มต้นเมื่อปรับให้เหมาะกับอุปกรณ์เคลื่อนที่
ในเว็บไซต์แบบดั้งเดิมที่เปิดใช้การเลื่อน คุณอาจใช้กฎ CSS เพื่อให้ได้เว็บไซต์ที่ปรับเปลี่ยนตามอุปกรณ์ซึ่งจัดเรียงเนื้อหาและเมนูใหม่ ตราบใดที่คุณใช้ฟังก์ชันการเลื่อนได้ ปัญหานี้ก็จะจัดการได้
เราใช้วิธีการนี้กับ Build ด้วย แต่เรามีข้อจำกัดบางอย่างในการแก้ปัญหาเลย์เอาต์ เนื่องจากเราต้องแสดงเนื้อหาให้เห็นตลอดเวลาและยังคงเข้าถึงตัวควบคุมและปุ่มต่างๆ ได้อย่างรวดเร็ว เลย์เอาต์แบบยืดหยุ่นเหมาะสําหรับเว็บไซต์เนื้อหาล้วนๆ เช่น เว็บไซต์ข่าว แต่สําหรับแอปเกมอย่างเรา เลย์เอาต์นี้กลับทําได้ยาก การหาเลย์เอาต์ที่ใช้งานได้ทั้งในแนวนอนและแนวตั้งโดยยังคงแสดงภาพรวมของเนื้อหาได้ดีและมอบวิธีโต้ตอบที่สะดวกสบายนั้นเป็นเรื่องยาก สุดท้ายเราตัดสินใจที่จะใช้โหมดแนวนอนเท่านั้นและบอกให้ผู้ใช้หมุนอุปกรณ์
สำรวจแก้ปัญหาได้ง่ายขึ้นมากทั้งในแนวตั้งและแนวนอน เราแค่ต้องปรับระดับการซูมของภาพ 3 มิติตามการวางแนวเพื่อให้ได้รับประสบการณ์การใช้งานที่สอดคล้องกัน
เลย์เอาต์เนื้อหาส่วนใหญ่ควบคุมโดย CSS แต่สิ่งที่เกี่ยวข้องกับการวางแนวบางอย่างจำเป็นต้องติดตั้งใช้งานใน JavaScript เราพบว่าไม่มีโซลูชันข้ามอุปกรณ์ที่ดีในการใช้ window.orientation เพื่อระบุการวางแนว ดังนั้นในท้ายที่สุด เราจึงเปรียบเทียบเพียง window.innerWidth กับ window.innerHeight เพื่อระบุการวางแนวของอุปกรณ์
if( window.innerWidth > window.innerHeight ){
//landscape
} else {
//portrait
}
การเพิ่มการรองรับการสัมผัส
การเพิ่มการรองรับการสัมผัสในเนื้อหาเว็บนั้นทําได้ค่อนข้างง่าย การโต้ตอบพื้นฐาน เช่น เหตุการณ์การคลิก จะทํางานเหมือนกันในเดสก์ท็อปและอุปกรณ์ที่เปิดใช้การสัมผัส แต่สำหรับการโต้ตอบขั้นสูงขึ้น คุณจะต้องจัดการเหตุการณ์การสัมผัสด้วย ได้แก่ touchstart, touchmove และ touchend บทความนี้จะอธิบายข้อมูลเบื้องต้นเกี่ยวกับวิธีใช้เหตุการณ์เหล่านี้ Internet Explorer ไม่รองรับเหตุการณ์การสัมผัส แต่จะใช้เหตุการณ์เคอร์เซอร์ (pointerdown, pointermove, pointerup) แทน เหตุการณ์เคอร์เซอร์ได้ส่งไปยัง W3C เพื่อกำหนดมาตรฐานแล้ว แต่ตอนนี้มีการใช้งานใน Internet Explorer เท่านั้น
ในโหมดสำรวจ 3 มิติ เราต้องการให้การไปยังส่วนต่างๆ เหมือนกับการใช้งาน Google Maps มาตรฐาน นั่นคือใช้ 1 นิ้วเพื่อเลื่อนแผนที่ไปรอบๆ และใช้ 2 นิ้วเพื่อซูม เนื่องจากผลงานเป็นแบบ 3 มิติ เราจึงเพิ่มท่าทางสัมผัสด้วยการหมุน 2 นิ้วด้วย ซึ่งโดยปกติแล้วจะต้องอาศัยเหตุการณ์การสัมผัส
แนวทางปฏิบัติที่ดีคือหลีกเลี่ยงการประมวลผลที่หนักหน่วง เช่น การอัปเดตหรือการแสดงผล 3 มิติในตัวแฮนเดิลเหตุการณ์ แต่ให้เก็บข้อมูลป้อนข้อมูลด้วยการสัมผัสไว้ในตัวแปรและตอบสนองต่อข้อมูลป้อนข้อมูลในลูปการแสดงผลของ requestAnimationFrame วิธีนี้ยังช่วยให้ใช้เมาส์ในเวลาเดียวกันได้ง่ายขึ้นด้วย เพียงเก็บค่าเมาส์ที่เกี่ยวข้องไว้ในตัวแปรเดียวกัน
เริ่มต้นด้วยการจัดเตรียมออบเจ็กต์เพื่อจัดเก็บอินพุตและเพิ่ม Listener เหตุการณ์ touchstart ใน Handler แต่ละรายการ เราจะเรียกใช้ event.preventDefault() เพื่อป้องกันไม่ให้เบราว์เซอร์ประมวลผลเหตุการณ์การแตะต่อไป ซึ่งอาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิด เช่น การเลื่อนหรือการปรับขนาดทั้งหน้า
var input = {dragStartX:0, dragStartY:0, dragX:0, dragY:0, dragDX:0, dragDY:0, dragging:false};
plateContainer.addEventListener('touchstart', onTouchStart);
function onTouchStart(event) {
event.preventDefault();
if( event.touches.length === 1){
handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
//start listening to all needed touchevents to implement the dragging
document.addEventListener('touchmove', onTouchMove);
document.addEventListener('touchend', onTouchEnd);
document.addEventListener('touchcancel', onTouchEnd);
}
}
function onTouchMove(event) {
event.preventDefault();
if( event.touches.length === 1){
handleDragging(event.touches[0].clientX, event.touches[0].clientY);
}
}
function onTouchEnd(event) {
event.preventDefault();
if( event.touches.length === 0){
handleDragStop();
//remove all eventlisteners but touchstart to minimize number of eventlisteners
document.removeEventListener('touchmove', onTouchMove);
document.removeEventListener('touchend', onTouchEnd);
//also listen to touchcancel event to avoid unexpected behavior when switching tabs and some other situations
document.removeEventListener('touchcancel', onTouchEnd);
}
}
เราไม่ได้จัดเก็บอินพุตจริงในตัวแฮนเดิลเหตุการณ์ แต่จัดเก็บไว้ในตัวแฮนเดิลแยกต่างหาก ได้แก่ handleDragStart, handleDragging และ handleDragStop เนื่องจากเราต้องการเรียกใช้เหตุการณ์เหล่านี้จากเครื่องจัดการเหตุการณ์เมาส์ด้วย โปรดทราบว่าผู้ใช้อาจใช้การสัมผัสและเมาส์พร้อมกัน แม้ว่าจะเป็นไปได้น้อยก็ตาม แทนที่จะจัดการเคสนั้นโดยตรง เราแค่ตรวจสอบว่าไม่มีอะไรผิดพลาด
function handleDragStart(x ,y ){
input.dragging = true;
input.dragStartX = input.dragX = x;
input.dragStartY = input.dragY = y;
}
function handleDragging(x ,y ){
if(input.dragging) {
input.dragDX = x - input.dragX;
input.dragDY = y - input.dragY;
input.dragX = x;
input.dragY = y;
}
}
function handleDragStop(){
if(input.dragging) {
input.dragging = false;
input.dragDX = 0;
input.dragDY = 0;
}
}
เมื่อทำภาพเคลื่อนไหวตามการแตะเพื่อเลื่อน การเก็บค่าการเปลี่ยนแปลงตั้งแต่เหตุการณ์ล่าสุดไว้ด้วยมักจะมีประโยชน์ ตัวอย่างเช่น เราใช้ข้อมูลนี้เป็นตัวแปรสำหรับความเร็วของกล้องเมื่อเลื่อนไปทั่วเพลตฐานทั้งหมดในส่วนสํารวจ เนื่องจากคุณไม่ได้ลากเพลตฐาน แต่กําลังเลื่อนกล้อง
function onAnimationFrame() {
requestAnimationFrame( onAnimationFrame );
//execute animation based on input.dragDX, input.dragDY, input.dragX or input.dragY
/*
/
*/
//because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
input.dragDX=0;
input.dragDY=0;
}
ตัวอย่างที่ฝัง: การลากวัตถุโดยใช้เหตุการณ์การสัมผัส การใช้งานคล้ายกับการลากแผนที่ 3 มิติ "สำรวจ" ใน Build with Chrome: http://cdpn.io/qDxvo
ท่าทางสัมผัสแบบมัลติทัช
มีเฟรมเวิร์กหรือไลบรารีหลายรายการ เช่น Hammer หรือ QuoJS ที่จัดการท่าทางสัมผัสแบบหลายจุดได้ง่ายขึ้น แต่หากต้องการรวมท่าทางสัมผัสหลายรายการเข้าด้วยกันและควบคุมอย่างเต็มรูปแบบ บางครั้งคุณอาจต้องเริ่มตั้งแต่ต้น
ในการจัดการท่าทางสัมผัสการบีบและการหมุน เราจะจัดเก็บระยะทางและมุมระหว่างนิ้ว 2 นิ้วเมื่อวางนิ้วที่ 2 บนหน้าจอ ดังนี้
//variables representing the actual scale/rotation of the object we are affecting
var currentScale = 1;
var currentRotation = 0;
function onTouchStart(event) {
event.preventDefault();
if( event.touches.length === 1){
handleDragStart(event.touches[0].clientX , event.touches[0].clientY);
}else if( event.touches.length === 2 ){
handleGestureStart(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
}
}
function handleGestureStart(x1, y1, x2, y2){
input.isGesture = true;
//calculate distance and angle between fingers
var dx = x2 - x1;
var dy = y2 - y1;
input.touchStartDistance=Math.sqrt(dx*dx+dy*dy);
input.touchStartAngle=Math.atan2(dy,dx);
//we also store the current scale and rotation of the actual object we are affecting. This is needed to support incremental rotation/scaling. We can't assume that an object is always the same scale when gesture starts.
input.startScale=currentScale;
input.startAngle=currentRotation;
}
ในเหตุการณ์การแตะเพื่อเลื่อน เราจะวัดระยะทางและมุมระหว่างนิ้ว 2 นิ้วนั้นอย่างต่อเนื่อง จากนั้นระบบจะใช้ความแตกต่างระหว่างระยะทางเริ่มต้นกับระยะทางปัจจุบันเพื่อกำหนดมาตราส่วน และจะใช้ความแตกต่างระหว่างมุมเริ่มต้นกับมุมปัจจุบันเพื่อกำหนดมุม
function onTouchMove(event) {
event.preventDefault();
if( event.touches.length === 1){
handleDragging(event.touches[0].clientX, event.touches[0].clientY);
}else if( event.touches.length === 2 ){
handleGesture(event.touches[0].clientX, event.touches[0].clientY, event.touches[1].clientX, event.touches[1].clientY );
}
}
function handleGesture(x1, y1, x2, y2){
if(input.isGesture){
//calculate distance and angle between fingers
var dx = x2 - x1;
var dy = y2 - y1;
var touchDistance = Math.sqrt(dx*dx+dy*dy);
var touchAngle = Math.atan2(dy,dx);
//calculate the difference between current touch values and the start values
var scalePixelChange = touchDistance - input.touchStartDistance;
var angleChange = touchAngle - input.touchStartAngle;
//calculate how much this should affect the actual object
currentScale = input.startScale + scalePixelChange*0.01;
currentRotation = input.startAngle+(angleChange*180/Math.PI);
//upper and lower limit of scaling
if(currentScale<0.5) currentScale = 0.5;
if(currentScale>3) currentScale = 3;
}
}
คุณอาจใช้การเปลี่ยนแปลงระยะทางระหว่างเหตุการณ์การแตะแต่ละรายการในลักษณะที่คล้ายกับตัวอย่างการลาก แต่แนวทางดังกล่าวมักมีประโยชน์มากกว่าเมื่อคุณต้องการการเคลื่อนไหวอย่างต่อเนื่อง
function onAnimationFrame() {
requestAnimationFrame( onAnimationFrame );
//execute transform based on currentScale and currentRotation
/*
/
*/
//because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
input.dragDX=0;
input.dragDY=0;
}
นอกจากนี้ คุณยังเปิดใช้การลากวัตถุขณะใช้ท่าทางสัมผัสบีบและบิดได้ด้วยหากต้องการ ในกรณีนี้ คุณจะใช้จุดศูนย์กลางระหว่างนิ้ว 2 นิ้วเป็นอินพุตไปยังแฮนเดิลการลาก
ตัวอย่างที่ฝัง: การบิดและปรับขนาดวัตถุใน 2 มิติ คล้ายกับวิธีติดตั้งใช้งานแผนที่ในส่วนสํารวจ: http://cdpn.io/izloq
การรองรับเมาส์และการสัมผัสในฮาร์ดแวร์เดียวกัน
ปัจจุบันมีแล็ปท็อปหลายรุ่น เช่น Chromebook Pixel ที่รองรับทั้งการป้อนข้อมูลด้วยเมาส์และการสัมผัส ซึ่งอาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิดหากคุณไม่ระมัดระวัง
สิ่งสำคัญอย่างหนึ่งคือคุณไม่ควรตรวจหาการรองรับการสัมผัสแล้วละเว้นอินพุตเมาส์ แต่ควรรองรับทั้ง 2 อย่างพร้อมกัน
หากคุณไม่ได้ใช้ event.preventDefault()
ในตัวแฮนเดิลเหตุการณ์การสัมผัส ระบบจะเรียกเหตุการณ์เมาส์จำลองบางส่วนด้วย เพื่อให้เว็บไซต์ส่วนใหญ่ที่ไม่ได้รับการปรับให้เหมาะกับการสัมผัสยังคงทำงานได้ ตัวอย่างเช่น สำหรับการแตะหน้าจอเพียงครั้งเดียว เหตุการณ์เหล่านี้อาจเริ่มทํางานตามลําดับอย่างรวดเร็วและตามลําดับนี้
- touchstart
- touchmove
- touchend
- เมาส์โอเวอร์
- mousemove
- mousedown
- mouseup
- click
หากการโต้ตอบมีความซับซ้อนมากขึ้น เหตุการณ์เมาส์เหล่านี้อาจทําให้เกิดลักษณะการทำงานที่ไม่คาดคิดและทําให้การติดตั้งใช้งานยุ่งเหยิง บ่อยครั้งที่การใช้ event.preventDefault()
ในเครื่องจัดการเหตุการณ์การสัมผัสและจัดการการป้อนข้อมูลด้วยเมาส์ในเครื่องจัดการเหตุการณ์แยกต่างหากจะดีที่สุด โปรดทราบว่าการใช้ event.preventDefault()
ในตัวแฮนเดิลเหตุการณ์การแตะจะป้องกันลักษณะการทำงานเริ่มต้นบางอย่างด้วย เช่น การเลื่อนและเหตุการณ์คลิก
"ใน Build with Chrome เราไม่ต้องการให้การซูมเกิดขึ้นเมื่อมีคนแตะสองครั้งที่เว็บไซต์ แม้ว่าการซูมจะเป็นมาตรฐานในเบราว์เซอร์ส่วนใหญ่ก็ตาม เราจึงใช้เมตาแท็กวิวพอร์ตเพื่อบอกเบราว์เซอร์ว่าอย่าซูมเมื่อผู้ใช้แตะสองครั้ง ซึ่งจะนําการเลื่อนเวลาคลิก 300 มิลลิวินาทีออกด้วย ซึ่งจะช่วยปรับปรุงการตอบสนองของเว็บไซต์ (การหน่วงเวลาการคลิกมีไว้เพื่อแยกความแตกต่างระหว่างการแตะครั้งเดียวกับการแตะสองครั้งเมื่อเปิดใช้การซูมด้วยการแตะสองครั้ง)
<meta name="viewport" content="width=device-width,user-scalable=no">
โปรดทราบว่าเมื่อใช้ฟีเจอร์นี้ คุณจะต้องทำให้เว็บไซต์อ่านได้บนหน้าจอทุกขนาด เนื่องจากผู้ใช้จะซูมเข้าไม่ได้
การป้อนข้อมูลด้วยเมาส์ การสัมผัส และแป้นพิมพ์
ในโหมดสำรวจ 3 มิติ เราต้องการให้ผู้ใช้ไปยังส่วนต่างๆ ของแผนที่ได้ 3 วิธี ได้แก่ เมาส์ (การลาก) การสัมผัส (การลาก การบีบนิ้วเพื่อซูมและบิด) และแป้นพิมพ์ (ไปยังส่วนต่างๆ ด้วยปุ่มลูกศร) วิธีการไปยังส่วนต่างๆ ทั้งหมดเหล่านี้ทํางานแตกต่างกันเล็กน้อย แต่เราใช้แนวทางเดียวกันกับวิธีการทั้งหมด ซึ่งก็คือการตั้งค่าตัวแปรในตัวแฮนเดิลเหตุการณ์และดําเนินการกับตัวแปรนั้นในลูป requestAnimationFrame ลูป requestAnimationFrame ไม่จำเป็นต้องทราบว่ามีการใช้เมธอดใดในการไปยังส่วนต่างๆ
ตัวอย่างเช่น เราอาจตั้งค่าการเคลื่อนไหวของแผนที่ (dragDX และ dragDY) ด้วยวิธีการป้อนข้อมูลทั้ง 3 วิธี การใช้งานแป้นพิมพ์มีดังนี้
document.addEventListener('keydown', onKeyDown );
document.addEventListener('keyup', onKeyUp );
function onKeyDown( event ) {
input.keyCodes[ "k" + event.keyCode ] = true;
input.shiftKey = event.shiftKey;
}
function onKeyUp( event ) {
input.keyCodes[ "k" + event.keyCode ] = false;
input.shiftKey = event.shiftKey;
}
//this needs to be called every frame before animation is executed
function handleKeyInput(){
if(input.keyCodes.k37){
input.dragDX = -5; //37 arrow left
} else if(input.keyCodes.k39){
input.dragDX = 5; //39 arrow right
}
if(input.keyCodes.k38){
input.dragDY = -5; //38 arrow up
} else if(input.keyCodes.k40){
input.dragDY = 5; //40 arrow down
}
}
function onAnimationFrame() {
requestAnimationFrame( onAnimationFrame );
//because keydown events are not fired every frame we need to process the keyboard state first
handleKeyInput();
//implement animations based on what is stored in input
/*
/
*/
//because touchmove is only fired when finger is actually moving we need to reset the delta values each frame
input.dragDX = 0;
input.dragDY = 0;
}
ตัวอย่างที่ฝัง: ใช้เมาส์ การสัมผัส และแป้นพิมพ์เพื่อไปยังส่วนต่างๆ: http://cdpn.io/catlf
สรุป
การปรับ Build with Chrome ให้รองรับอุปกรณ์แบบสัมผัสที่มีหน้าจอขนาดต่างๆ มากมายเป็นประสบการณ์การเรียนรู้ ทีมของเราไม่ค่อยมีประสบการณ์ในการสร้างการโต้ตอบในระดับนี้บนอุปกรณ์แบบสัมผัส และเราได้เรียนรู้อะไรมากมายตลอดเส้นทาง
ปัญหาที่ใหญ่ที่สุดกลับกลายเป็นวิธีแก้ปัญหาด้านประสบการณ์ของผู้ใช้และการออกแบบ ความท้าทายทางเทคนิคคือการจัดการหน้าจอหลายขนาด เหตุการณ์การแตะ และปัญหาด้านประสิทธิภาพ
แม้ว่าจะมีบางปัญหาเกี่ยวกับโปรแกรมเปลี่ยนสี WebGL ในอุปกรณ์แบบสัมผัส แต่ฟีเจอร์นี้ทำงานได้ดีกว่าที่คาดไว้เกือบจะทั้งหมด อุปกรณ์มีประสิทธิภาพมากขึ้นเรื่อยๆ และการใช้งาน WebGL ก็พัฒนาไปอย่างรวดเร็ว เราคิดว่าเราจะใช้ WebGL ในอุปกรณ์มากขึ้นในอนาคตอันใกล้
ทีนี้ไปสร้างสิ่งที่ยอดเยี่ยมกันได้เลย