สร้างสรรค์ด้วย Chrome

นำตัวต่อ LEGO® มาสู่เว็บสำหรับหลายอุปกรณ์

Build with Chrome เป็นการทดลองสนุกๆ สำหรับผู้ใช้ Chrome บนเดสก์ท็อปซึ่งเปิดตัวครั้งแรกในออสเตรเลีย โดยเปิดตัวใหม่ในปี 2014 โดยเปิดตัวใหม่ทั่วโลก ใช้ร่วมกับ THE LEGO® MOVIETM และอุปกรณ์เคลื่อนที่ที่เพิ่มเข้ามาใหม่ ในบทความนี้ เราจะแชร์สิ่งที่ได้เรียนรู้จากโปรเจ็กต์นี้ โดยเฉพาะเรื่องการเปลี่ยนจากประสบการณ์การใช้งานเฉพาะเดสก์ท็อปไปเป็นโซลูชันหลายหน้าจอที่รองรับทั้งเมาส์และการป้อนข้อมูลด้วยการสัมผัส

ประวัติของ Build with Chrome

Build with Chrome เวอร์ชันแรกเปิดตัวในออสเตรเลียในปี 2012 เราต้องการแสดงให้เห็นถึงพลังของเว็บในรูปแบบใหม่และนำ Chrome ไปสู่กลุ่มเป้าหมายใหม่

ไซต์ดังกล่าวมีส่วนสำคัญ 2 ส่วน ได้แก่ โหมด "สร้าง" ที่ผู้ใช้สามารถสร้างผลงานโดยใช้ตัวต่อ LEGO และโหมด "สำรวจ" สำหรับเรียกดูผลงานใน Google Maps เวอร์ชันที่ประกอบขึ้นเป็น LEGO

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

ในปี 2013 เราตัดสินใจขยาย Build with Chrome ไปสู่เทคโนโลยีเว็บใหม่ๆ หนึ่งในเทคโนโลยีเหล่านั้นคือ WebGL ใน Chrome สำหรับ Android ซึ่งทำให้ Build with Chrome พัฒนาไปเป็นประสบการณ์การใช้งานบนอุปกรณ์เคลื่อนที่ได้ ในการเริ่มต้น เราได้พัฒนาต้นแบบการสัมผัสก่อนที่จะตั้งคำถามกับฮาร์ดแวร์สำหรับ "เครื่องมือสร้าง" เพื่อทำความเข้าใจพฤติกรรมของท่าทางสัมผัสและการตอบสนองด้วยการสัมผัสที่เราอาจพบผ่านทางเบราว์เซอร์เมื่อเทียบกับแอปบนอุปกรณ์เคลื่อนที่

ฟรอนท์เอนด์ที่ปรับเปลี่ยนตามอุปกรณ์

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

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

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

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

เราใช้ข้อมูล User Agent เพื่อตรวจหาอุปกรณ์เคลื่อนที่ จากนั้นจะตรวจสอบขนาดวิวพอร์ตเพื่อตัดสินใจว่าควรใช้ UI อุปกรณ์เคลื่อนที่ที่มีหน้าจอขนาดเล็กหรือไม่ การเลือกเบรกพอยท์สำหรับ "หน้าจอขนาดใหญ่" ควรจะยากเล็กน้อย เนื่องจากการหาค่าที่ชัดเจนของขนาดหน้าจอจริงนั้นทำได้ยาก โชคดีที่ในกรณีของเรา ไม่สำคัญว่าเราจะแสดง UI หน้าจอขนาดเล็กบนอุปกรณ์ระบบสัมผัสที่มีหน้าจอขนาดใหญ่ เนื่องจากเครื่องมือจะยังใช้งานได้ปกติ เพียงแต่บางปุ่มอาจรู้สึกว่าใหญ่เกินไป สุดท้ายเราตั้งค่าเบรกพอยท์เป็น 1, 000 พิกเซล หากคุณโหลดเว็บไซต์จากหน้าต่างที่กว้างกว่า 1,000 พิกเซล (ในโหมดแนวนอน) คุณจะได้หน้าจอในเวอร์ชันขนาดใหญ่

มาพูดถึงขนาดและประสบการณ์หน้าจอ 2 ขนาดกันสักเล็กน้อย

หน้าจอขนาดใหญ่ รองรับเมาส์และการแตะ

เวอร์ชันหน้าจอขนาดใหญ่จะให้บริการในคอมพิวเตอร์เดสก์ท็อปทุกเครื่องที่รองรับการใช้งานเมาส์และอุปกรณ์แบบสัมผัสที่มีหน้าจอขนาดใหญ่ (เช่น Google Nexus 10) โดยเวอร์ชันนี้ใกล้เคียงกับโซลูชันเดสก์ท็อปดั้งเดิมตรงประเภทการควบคุมการนำทางที่ใช้ได้ แต่เราเพิ่มการรองรับการแตะและท่าทางสัมผัสบางอย่าง เราปรับ UI ตามขนาดหน้าต่าง ดังนั้นเมื่อผู้ใช้ปรับขนาดหน้าต่าง ระบบอาจนำออกหรือปรับขนาด UI บางส่วน ซึ่งทำได้โดยใช้คำค้นหาสื่อของ CSS

ตัวอย่างเช่น เมื่อความสูงที่ใช้ได้น้อยกว่า 730 พิกเซล การควบคุมแถบเลื่อนการซูมในโหมดสำรวจจะซ่อนอยู่ ดังนี้

@media only screen and (max-height: 730px) {
    .zoom-slider {
        display: none;
    }
}

หน้าจอขนาดเล็ก รองรับการแตะเท่านั้น

เวอร์ชันนี้จะแสดงในโทรศัพท์มือถือและแท็บเล็ตขนาดเล็ก (อุปกรณ์เป้าหมาย Nexus 4 และ Nexus 7) เวอร์ชันนี้ต้องใช้การรองรับมัลติทัช

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

  • ตัวเลือกสีตัวต่องานจะย่อขนาดลงเป็นตัวเลือกสีในขณะที่สร้าง
  • เราแทนที่การควบคุมการซูมและการวางแนวด้วยท่าทางสัมผัสแบบมัลติทัช
  • ฟังก์ชันโหมดเต็มหน้าจอของ Chrome ยังมีประโยชน์ในการเพิ่มพื้นที่หน้าจออีกด้วย
สร้างบนหน้าจอขนาดใหญ่
สร้างขึ้นในหน้าจอขนาดใหญ่ ตัวเลือกอิฐจะปรากฏอยู่เสมอและมีตัวควบคุม 2-3 รายการทางด้านขวา
สร้างบนหน้าจอขนาดเล็ก
สร้างขึ้นในหน้าจอขนาดเล็ก เครื่องมือเลือกอิฐถูกย่อขนาดลงและบางปุ่มถูกนำออก

ประสิทธิภาพและการรองรับ WebGL

อุปกรณ์ระบบสัมผัสที่ทันสมัยมี GPU ที่ทรงพลังพอสมควร แต่ก็ยังอยู่ห่างจากเครื่องเดสก์ท็อปอยู่มาก เราจึงรู้ว่าจะมีความท้าทายด้านประสิทธิภาพ โดยเฉพาะในโหมด "สำรวจ 3 มิติ" ซึ่งเราจำเป็นต้องแสดงผลผลงานจำนวนมากพร้อมกัน

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

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

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

อุปกรณ์บางรุ่นเคยให้เรากลับมาทบทวนและลดความยุ่งยากให้กับตัวปรับแสงเงา WebGL บางตัว แต่เราก็หาทางแก้ปัญหาและก้าวไปข้างหน้าอยู่เสมอ

การรองรับอุปกรณ์ที่ไม่ใช่ WebGL

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

สไตล์ภาพของผลงานจะต้องเหมือนกันในทุกแพลตฟอร์มเพื่อความสอดคล้อง เราอาจจะลองใช้โซลูชันแบบ 2.5 มิติก็ได้ แต่วิธีนี้อาจทำให้ผลงานดูแตกต่างออกไปได้ในทางใดทางหนึ่ง นอกจากนี้เรายังต้องพิจารณาวิธีตรวจสอบว่าผลงานที่สร้างด้วย Build with 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 ด้วยเช่นกัน แต่เรามีข้อจำกัดอยู่เล็กน้อยในการแก้ปัญหาเลย์เอาต์ เนื่องจากเราต้องทำให้เนื้อหาแสดงได้ทุกเมื่อและยังคงเข้าถึงการควบคุมและปุ่มต่างๆ ได้อย่างรวดเร็ว เลย์เอาต์ที่ลื่นไหลเหมาะสำหรับเว็บไซต์เนื้อหาล้วนๆ อย่างเว็บไซต์ข่าว แต่แอปเกมอย่างเราทำได้ยากมาก จึงเป็นเรื่องยากที่จะหาเลย์เอาต์ที่ใช้งานได้ทั้งในแนวนอนและแนวตั้งโดยที่ยังรักษาภาพรวมของเนื้อหาและมีวิธีการโต้ตอบที่สะดวก สุดท้าย เราตัดสินใจที่จะใช้ Build ในแนวนอนเท่านั้นและแจ้งให้ผู้ใช้หมุนอุปกรณ์

การสำรวจทำให้แก้ไขการวางแนวทั้ง 2 แบบได้ง่ายขึ้นมาก เราแค่จำเป็นต้องปรับระดับการซูมของ 3 มิติตามการวางแนวเพื่อให้ได้รับประสบการณ์ที่สอดคล้องกัน

เลย์เอาต์เนื้อหาส่วนใหญ่ควบคุมโดย CSS แต่ก็จำเป็นต้องใช้เนื้อหาบางอย่างที่เกี่ยวข้องกับการวางแนวใน JavaScript เราพบว่าไม่มีโซลูชันที่ใช้งานได้หลายอุปกรณ์ที่จะใช้ window.orientation เพื่อระบุการวางแนว ดังนั้นในท้ายที่สุดเราจึงแค่เปรียบเทียบ window.inlineWidth และ window.teriorHeight เพื่อระบุการวางแนวของอุปกรณ์

if( window.innerWidth > window.innerHeight ){
  //landscape
} else {
  //portrait
}

การเพิ่มการรองรับการสัมผัส

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

ในโหมดสำรวจ 3 มิติ เราต้องการการนำทางเช่นเดียวกับการใช้งาน Google Maps มาตรฐาน โดยใช้นิ้วเดียวเลื่อนไปรอบๆ แผนที่และ 2 นิ้วบีบเพื่อซูม เนื่องจากผลงานเป็นแบบ 3 มิติ เราจึงเพิ่มท่าทางสัมผัสหมุน 2 นิ้ว ซึ่งตามปกติแล้วจะต้องใช้เหตุการณ์การแตะ

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

เริ่มด้วยการกำหนดค่าออบเจ็กต์เพื่อจัดเก็บอินพุตและเพิ่ม Listener เหตุการณ์แบบ Touchstart เราเรียก 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);
  }
}

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

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;
}

ในเหตุการณ์ Touchmove เราจะวัดระยะทางและมุมระหว่างนิ้วทั้งสองอย่างต่อเนื่อง เราจะใช้ผลต่างระหว่างระยะทางเริ่มต้นและระยะทางปัจจุบันในการกำหนดอัตราส่วน และใช้ความแตกต่างระหว่างมุมเริ่มต้นและมุมปัจจุบันในการกำหนดมุม

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;
  }
}

คุณอาจสามารถใช้การเปลี่ยนแปลงระยะทางระหว่างเหตุการณ์ Touchmove แต่ละเหตุการณ์ในลักษณะเดียวกับตัวอย่างกับการลาก แต่วิธีการนั้นมักจะมีประโยชน์มากกว่าเมื่อคุณต้องการการเคลื่อนไหวอย่างต่อเนื่อง

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() ในเครื่องจัดการเหตุการณ์การแตะ เหตุการณ์เมาส์จำลองจะเริ่มทำงานด้วยเพื่อให้เว็บไซต์ที่เพิ่มประสิทธิภาพที่ไม่ใช่การแตะส่วนใหญ่ยังคงทำงานต่อไปได้ ตัวอย่างเช่น สำหรับการแตะบนหน้าจอ 1 ครั้ง เหตุการณ์เหล่านี้อาจเริ่มทำงานตามลำดับอย่างรวดเร็วและเรียงตามลำดับดังนี้

  1. เริ่มการสัมผัส
  2. ขยับสัมผัส
  3. Touchend
  4. เมาส์โอเวอร์
  5. mousemove
  6. เมาส์ดาวน์
  7. เมาส์อัพ
  8. click

หากคุณมีการโต้ตอบที่ซับซ้อนมากขึ้น เหตุการณ์เกี่ยวกับเมาส์เหล่านี้อาจทำให้เกิดลักษณะการทำงานที่ไม่คาดคิดและทำให้การติดตั้งใช้งานของคุณสับสน วิธีที่ดีที่สุดคือการใช้ event.preventDefault() ในเครื่องจัดการเหตุการณ์การสัมผัสและจัดการอินพุตเมาส์ในเครื่องจัดการเหตุการณ์แยกต่างหาก ทั้งนี้คุณต้องทราบว่าการใช้ event.preventDefault() ในเครื่องจัดการเหตุการณ์การสัมผัสจะป้องกันลักษณะการทำงานเริ่มต้นบางอย่างด้วย เช่น การเลื่อนและเหตุการณ์การคลิก

"ใน Build with Chrome เราไม่ต้องการให้การซูมเกิดขึ้นเมื่อมีคนแตะสองครั้งบนเว็บไซต์ แม้ว่าจะเป็นวิธีการมาตรฐานในเบราว์เซอร์ส่วนใหญ่ ดังนั้นเราจึงใช้เมตาแท็กวิวพอร์ตเพื่อบอกเบราว์เซอร์ว่าอย่าซูมเมื่อผู้ใช้แตะสองครั้ง นอกจากนี้ ยังลบการหน่วงเวลาการคลิก 300 มิลลิวินาที ซึ่งจะช่วยปรับปรุงการตอบสนองของเว็บไซต์อีกด้วย (การหน่วงเวลาการคลิกมีไว้เพื่อแยกความแตกต่างระหว่างการแตะครั้งเดียวกับการแตะ 2 ครั้งเมื่อเปิดใช้การซูมด้วยการแตะสองครั้ง)

<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 บนอุปกรณ์ต่างๆ มากขึ้นในอนาคตอันใกล้

ตอนนี้ถ้าคุณยังไม่ได้สร้าง ก็ไปสร้างสิ่งดีๆ ได้เลย!