การทำงานภายในของกระบวนการแสดงภาพ
นี่คือบล็อกส่วนที่ 3 จาก 4 ตอนที่ดูวิดีโอเกี่ยวกับการทำงานของเบราว์เซอร์ โดยก่อนหน้านี้ เราได้พูดถึงสถาปัตยกรรมแบบหลายกระบวนการและขั้นตอนการนำทาง ในโพสต์นี้ เราจะมาดูว่า เกิดอะไรขึ้นภายในกระบวนการแสดงผล
กระบวนการแสดงภาพจะมีแง่มุมต่างๆ ของประสิทธิภาพเว็บ เนื่องจากกระบวนการแสดงผลมีสิ่งต่างๆ มากมายเกิดขึ้น โพสต์นี้จึงเป็นเพียงภาพรวมทั่วไป หากต้องการเจาะลึกยิ่งขึ้น ส่วนประสิทธิภาพของ "พื้นฐานเว็บ" มีแหล่งข้อมูลอีกมากมาย
กระบวนการแสดงภาพจะจัดการกับเนื้อหาเว็บ
กระบวนการแสดงผลจะรับผิดชอบทุกสิ่งที่เกิดขึ้นภายในแท็บ ในกระบวนการแสดงผล เทรดหลักจะจัดการโค้ดส่วนใหญ่ที่คุณส่งให้ผู้ใช้ บางครั้ง JavaScript บางส่วนจะได้รับการจัดการโดยชุดข้อความของผู้ปฏิบัติงาน หากคุณใช้ Web Worker หรือ Service Worker ชุดข้อความแบบคอมโพสิตและแรสเตอร์จะทำงานภายในกระบวนการแสดงผลเพื่อแสดงผลหน้าเว็บได้อย่างมีประสิทธิภาพและราบรื่น
งานหลักของกระบวนการแสดงผลคือการเปลี่ยน HTML, CSS และ JavaScript เป็นหน้าเว็บที่ผู้ใช้สามารถโต้ตอบด้วยได้
![กระบวนการแสดงภาพ](https://developer.chrome.com/static/blog/inside-browser-part3/image/renderer-process-df424472d0633.png?authuser=19&hl=th)
การแยกวิเคราะห์
การสร้าง DOM
เมื่อกระบวนการแสดงผลได้รับข้อความคอมมิตสำหรับการนำทางและเริ่มได้รับข้อมูล HTML เทรดหลักจะเริ่มแยกวิเคราะห์สตริงข้อความ (HTML) และเปลี่ยนสตริงดังกล่าวให้เป็นมิติข้อมูลObject Model (DOM)
DOM คือการแสดงหน้าเว็บภายในเบราว์เซอร์ ตลอดจนโครงสร้างข้อมูลและ API ที่นักพัฒนาเว็บโต้ตอบด้วยได้ผ่าน JavaScript
การแยกวิเคราะห์เอกสาร HTML ลงใน DOM จะกำหนดโดยมาตรฐาน HTML คุณอาจสังเกตเห็นว่าการป้อน HTML บนเบราว์เซอร์
นั้นไม่ได้ทำให้เกิดข้อผิดพลาดเลย เช่น แท็กปิด </p>
ที่ขาดหายไปคือ HTML ที่ถูกต้อง ระบบจะดำเนินการกับมาร์กอัปที่ผิดพลาด เช่น Hi! <b>I'm <i>Chrome</b>!</i>
(แท็ก b ปิดก่อนแท็ก i) เหมือนกับที่คุณเขียน Hi! <b>I'm <i>Chrome</i></b><i>!</i>
ทั้งนี้เพราะข้อกำหนดของ HTML ได้รับการออกแบบมาเพื่อจัดการกับข้อผิดพลาดเหล่านั้นอย่างเหมาะสม ถ้าคุณอยากทราบวิธีการทำงานเหล่านี้ คุณสามารถอ่านต่อ
"ข้อมูลเบื้องต้นเกี่ยวกับการจัดการข้อผิดพลาดและกรณีแปลกๆ ในส่วนโปรแกรมแยกวิเคราะห์"
ของข้อกำหนด HTML
กำลังโหลดทรัพยากรย่อย
โดยปกติแล้วเว็บไซต์จะใช้ทรัพยากรภายนอก เช่น รูปภาพ, CSS และ JavaScript จะต้องโหลดไฟล์เหล่านี้
จากเครือข่ายหรือแคช เทรดหลักสามารถส่งคำขอทีละรายการเมื่อพบขณะแยกวิเคราะห์เพื่อสร้าง DOM แต่หากเรียกใช้ "ตัวสแกนการโหลดล่วงหน้า" พร้อมกันเพื่อให้ทำงานได้เร็วขึ้น
หากมีข้อมูลอย่างเช่น <img>
หรือ <link>
ในเอกสาร HTML เครื่องสแกนที่โหลดล่วงหน้าจะมองหาโทเค็นที่สร้างโดยโปรแกรมแยกวิเคราะห์ HTML และส่งคำขอไปยังเธรดเครือข่ายในกระบวนการของเบราว์เซอร์
![DOM](https://developer.chrome.com/static/blog/inside-browser-part3/image/dom-ffeed8e96a6e8.png?authuser=19&hl=th)
JavaScript บล็อกการแยกวิเคราะห์ได้
เมื่อโปรแกรมแยกวิเคราะห์ HTML พบแท็ก <script>
โปรแกรมแยกวิเคราะห์จะหยุดการแยกวิเคราะห์เอกสาร HTML และต้องโหลด แยกวิเคราะห์ และเรียกใช้โค้ด JavaScript นั่นเป็นเพราะ JavaScript เปลี่ยนรูปร่างของเอกสารได้โดยใช้สิ่งต่างๆ เช่น document.write()
ซึ่งจะเปลี่ยนโครงสร้าง DOM ทั้งหมด (ภาพรวมของโมเดลการแยกวิเคราะห์ในข้อกำหนด HTML มีแผนภาพที่ดี) ซึ่งเป็นเหตุผลที่โปรแกรมแยกวิเคราะห์ HTML ต้องรอให้ JavaScript ทำงานก่อนจึงจะแยกวิเคราะห์เอกสาร HTML ต่อได้ หากคุณสงสัยว่าจะเกิดอะไรขึ้นในการใช้งาน JavaScript ทีม V8 จะมาพูดคุยและบล็อกโพสต์เกี่ยวกับเรื่องนี้
คำแนะนำเกี่ยวกับเบราว์เซอร์เกี่ยวกับวิธีโหลดทรัพยากร
มีหลายวิธีที่นักพัฒนาเว็บส่งคำแนะนำไปยังเบราว์เซอร์เพื่อให้โหลดทรัพยากรได้ดี
หาก JavaScript ไม่ได้ใช้ document.write()
คุณสามารถเพิ่มแอตทริบิวต์ async
หรือ defer
ลงในแท็ก <script>
ได้ จากนั้นเบราว์เซอร์จะโหลดและเรียกใช้โค้ด JavaScript แบบไม่พร้อมกันและไม่บล็อกการแยกวิเคราะห์ นอกจากนี้ คุณสามารถใช้โมดูล JavaScript ได้ตามความเหมาะสม <link rel="preload">
เป็นวิธีแจ้งให้เบราว์เซอร์ทราบว่าจำเป็นต้องใช้ทรัพยากรสำหรับการนำทางปัจจุบันอย่างแน่นอน และคุณต้องการดาวน์โหลดโดยเร็วที่สุด อ่านข้อมูลเพิ่มเติมได้ที่การจัดลําดับความสําคัญของทรัพยากร – การใช้เบราว์เซอร์เพื่อช่วยคุณ
การคำนวณรูปแบบ
การมี DOM ยังไม่เพียงพอที่จะรู้ว่าหน้าเว็บจะมีลักษณะอย่างไร เพราะเราสามารถจัดรูปแบบองค์ประกอบของหน้าใน CSS ได้ เทรดหลักจะแยกวิเคราะห์ CSS และกำหนดรูปแบบที่คำนวณสำหรับโหนด DOM แต่ละโหนด นี่เป็นข้อมูลเกี่ยวกับประเภทของสไตล์ที่ใช้กับแต่ละองค์ประกอบตามตัวเลือก CSS คุณดูข้อมูลนี้ได้ในส่วน computed
ของเครื่องมือสำหรับนักพัฒนาเว็บ
![รูปแบบที่คำนวณแล้ว](https://developer.chrome.com/static/blog/inside-browser-part3/image/computed-style-5e3c65a01f3f1.png?authuser=19&hl=th)
แม้ว่าคุณจะไม่ได้ระบุ CSS แต่โหนด DOM แต่ละโหนดก็มีรูปแบบที่คำนวณแล้ว แท็ก <h1>
แสดงมีขนาดใหญ่กว่าแท็ก <h2>
และกำหนดระยะขอบสำหรับแต่ละองค์ประกอบ เนื่องจากเบราว์เซอร์มีสไตล์ชีตเริ่มต้น หากต้องการทราบลักษณะ CSS เริ่มต้นของ Chrome
คุณสามารถดูซอร์สโค้ดที่นี่
เลย์เอาต์
ตอนนี้กระบวนการแสดงผลจะรู้โครงสร้างของเอกสารและรูปแบบสำหรับแต่ละโหนดแล้ว แต่ยังไม่เพียงพอที่จะแสดงผลหน้าเว็บ ลองนึกภาพว่าคุณกำลังพยายามอธิบายภาพวาดแก่เพื่อนของคุณ ทางโทรศัพท์ "มีวงกลมสีแดงขนาดใหญ่และสี่เหลี่ยมจัตุรัสสีน้ำเงินขนาดเล็ก" มีข้อมูลไม่เพียงพอที่จะให้เพื่อนของคุณรู้ว่าภาพวาดจะมีหน้าตาเป็นอย่างไร
![เกมเครื่องแฟกซ์ของมนุษย์](https://developer.chrome.com/static/blog/inside-browser-part3/image/game-human-fax-machine-17f10d8b400ac.png?authuser=19&hl=th)
เลย์เอาต์เป็นกระบวนการค้นหาเรขาคณิตขององค์ประกอบ เทรดหลักจะเดินผ่าน DOM และสไตล์ที่คำนวณแล้ว รวมถึงสร้างแผนผังเลย์เอาต์ที่มีข้อมูล เช่น พิกัด x y และขนาดกรอบที่ล้อมรอบ แผนผังเลย์เอาต์อาจมีโครงสร้างคล้ายกับแผนผัง DOM แต่มีเฉพาะข้อมูลที่เกี่ยวข้องกับสิ่งที่แสดงในหน้า หากใช้ display: none
องค์ประกอบนั้นจะไม่ได้เป็นส่วนหนึ่งของแผนผังเลย์เอาต์ (อย่างไรก็ตาม องค์ประกอบที่มี visibility: hidden
จะอยู่ในแผนผังเลย์เอาต์) ในทำนองเดียวกัน ถ้ามีการใช้องค์ประกอบเทียมที่มีเนื้อหาอย่างเช่น p::before{content:"Hi!"}
องค์ประกอบดังกล่าวจะรวมอยู่ในแผนผังเลย์เอาต์แม้ว่าจะไม่ได้อยู่ใน DOM ก็ตาม
![เลย์เอาต์](https://developer.chrome.com/static/blog/inside-browser-part3/image/layout-9d8ed8c743f45.png?authuser=19&hl=th)
การกำหนดเลย์เอาต์ของหน้าเว็บเป็นงานที่ท้าทาย แม้แต่เลย์เอาต์หน้าเว็บที่เรียบง่ายที่สุดอย่างการบล็อกจากบนลงล่างก็ต้องพิจารณาขนาดตัวอักษรและจุดที่จะขึ้นบรรทัดใหม่เพราะส่งผลต่อขนาดและรูปร่างของย่อหน้า ซึ่งจะมีผลต่อตำแหน่งที่ต้องการให้ย่อหน้าต่อไปนี้เป็น
CSS ทำให้องค์ประกอบลอยไปด้านใดด้านหนึ่ง มาสก์รายการเพิ่มเติม และเปลี่ยนทิศทางการเขียนได้ คุณอาจนึกภาพออกว่าขั้นตอน การจัดวางนี้มีงานหนัก ใน Chrome ทีมวิศวกรทั้งหมดจะสร้างเลย์เอาต์ หากคุณต้องการดูรายละเอียดต่างๆ เกี่ยวกับงานเหล่านี้ การบรรยายบางส่วนจาก BlinkOn Meetings มีการบันทึกไว้และค่อนข้างน่าสนใจสำหรับการรับชม
สี
![เกมวาดภาพ](https://developer.chrome.com/static/blog/inside-browser-part3/image/drawing-game-7902ad8a91d2f.png?authuser=19&hl=th)
การมี DOM, รูปแบบ และเลย์เอาต์ยังคงไม่เพียงพอที่จะแสดงผลหน้าเว็บ สมมติว่าคุณพยายาม จำลองภาพวาด คุณรู้ขนาด รูปร่าง และตำแหน่งขององค์ประกอบ แต่ยังต้องพิจารณาลำดับในการวาดภาพ
เช่น อาจตั้งค่า z-index
สําหรับองค์ประกอบบางอย่าง ซึ่งในกรณีนี้การวาดภาพตามลําดับองค์ประกอบที่เขียนใน HTML จะส่งผลให้การแสดงผลไม่ถูกต้อง
![ดัชนีลำดับ Z ล้มเหลว](https://developer.chrome.com/static/blog/inside-browser-part3/image/z-index-fail-2529cf989dc65.png?authuser=19&hl=th)
ในขั้นตอน Paint นี้ เทรดหลักจะเดินตามแผนผังเลย์เอาต์เพื่อสร้างเรคคอร์ดสี ระเบียนการแสดงผลคือ
ขั้นตอนการวาดภาพ เช่น "พื้นหลังก่อน ข้อความ ตามด้วยสี่เหลี่ยมผืนผ้า" หากคุณวาดบนเอลิเมนต์ <canvas>
โดยใช้ JavaScript คุณอาจคุ้นเคยกับกระบวนการนี้
![ระเบียนสี](https://developer.chrome.com/static/blog/inside-browser-part3/image/paint-records-151165f18a91e.png?authuser=19&hl=th)
การอัปเดตไปป์ไลน์การแสดงผลมีค่าใช้จ่ายสูง
สิ่งสำคัญที่สุดที่ต้องเข้าใจในการแสดงผลไปป์ไลน์ก็คือจะมีการใช้ผลของการดำเนินการก่อนหน้าในการสร้างข้อมูลใหม่ในแต่ละขั้นตอน ตัวอย่างเช่น หากมีการเปลี่ยนแปลงบางอย่างในโครงสร้างของเลย์เอาต์ คุณจะต้องสร้างลำดับการระบายสีใหม่สำหรับส่วนที่ได้รับผลกระทบของเอกสาร
ถ้าคุณทำให้องค์ประกอบเคลื่อนไหว เบราว์เซอร์จะต้องเรียกใช้การดำเนินการเหล่านี้ระหว่างทุกเฟรม จอแสดงผลส่วนใหญ่จะรีเฟรชหน้าจอ 60 ครั้งต่อวินาที (60 fps) ภาพเคลื่อนไหวจะดูลื่นไหลต่อสายตามนุษย์เมื่อคุณเคลื่อนไหวสิ่งต่างๆ ในหน้าจอทุกเฟรม แต่หากภาพเคลื่อนไหวไม่มีเฟรมที่อยู่ระหว่างนั้น หน้าจะแสดง "ภาพแตก"
![ภาพสั่นเพราะเฟรมหายไป](https://developer.chrome.com/static/blog/inside-browser-part3/image/jage-jank-missing-frames-187496b8fbc8e.png?authuser=19&hl=th)
แม้ว่าการดำเนินการแสดงผลจะตามการรีเฟรชหน้าจอตลอดเวลา แต่การคำนวณเหล่านี้จะทำงานในเทรดหลัก ซึ่งหมายความว่าอาจถูกบล็อกเมื่อแอปพลิเคชันใช้ JavaScript
![jage กระตุกตาม JavaScript](https://developer.chrome.com/static/blog/inside-browser-part3/image/jage-jank-javascript-b19c0193934a3.png?authuser=19&hl=th)
คุณแบ่งการดำเนินการ JavaScript ออกเป็นกลุ่มเล็กๆ และตั้งเวลาให้ทำงานในทุกเฟรมได้โดยใช้ requestAnimationFrame()
ดูข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้ได้ที่เพิ่มประสิทธิภาพการเรียกใช้ JavaScript นอกจากนี้คุณยังเรียกใช้ JavaScript ใน Web Workers เพื่อหลีกเลี่ยงการบล็อกเทรดหลักได้ด้วย
![ส่งคำขอเฟรมภาพเคลื่อนไหว](https://developer.chrome.com/static/blog/inside-browser-part3/image/request-animation-frame-3b04e26660019.png?authuser=19&hl=th)
การประกอบ
คุณจะวาดหน้าเว็บอย่างไร
เมื่อเบราว์เซอร์รู้โครงสร้างของเอกสาร รูปแบบของแต่ละองค์ประกอบ เรขาคณิตของหน้า และลำดับสีแล้ว หน้าเว็บจะวาดอย่างไร การเปลี่ยนข้อมูลนี้ให้เป็นพิกเซลบนหน้าจอ เรียกว่าการทำแรสเตอร์
บางวิธีอาจเป็นการแรสเตอร์ส่วนต่างๆ ภายในวิวพอร์ตแบบไร้เดียงสา หากผู้ใช้เลื่อนหน้า แล้วย้ายเฟรมที่แรสเตอร์ และใส่ส่วนที่ขาดหายไปด้วยการแรสเตอร์เพิ่มเติม ซึ่งเป็นวิธีที่ Chrome จัดการกับการแรสเตอร์เมื่อเปิดตัวครั้งแรก อย่างไรก็ตาม เบราว์เซอร์สมัยใหม่ใช้กระบวนการที่ซับซ้อนมากขึ้นซึ่งเรียกว่า "การประกอบหน้าเว็บ"
การประกอบหน้าเว็บคืออะไร
การประกอบเป็นเทคนิคในการแยกส่วนต่างๆ ของหน้าออกเป็นเลเยอร์ แรสเตอร์แยกกัน และประกอบเข้าด้วยกันเป็นหน้าในชุดข้อความแยกต่างหากซึ่งเรียกว่าเทรดคอมโพสิต ถ้าเกิดการเลื่อน เนื่องจากเลเยอร์ต่างๆ มีการแรสเตอร์แล้ว สิ่งที่ต้องทำก็คือการประกอบเฟรมใหม่ คุณสามารถทำภาพเคลื่อนไหวได้ด้วยวิธีเดียวกัน โดยการย้ายเลเยอร์และทำการประกอบเฟรมใหม่
คุณดูวิธีแบ่งเว็บไซต์เป็นเลเยอร์ในเครื่องมือสำหรับนักพัฒนาเว็บได้โดยใช้แผงเลเยอร์
การแบ่งเป็นเลเยอร์
ในการดูว่าองค์ประกอบใดต้องอยู่ในเลเยอร์ใด เทรดหลักจะเดินผ่านแผนผังเลย์เอาต์เพื่อสร้างแผนผังเลเยอร์ (ส่วนนี้เรียกว่า "อัปเดตเลเยอร์ทรี" ในแผงประสิทธิภาพของเครื่องมือสำหรับนักพัฒนาเว็บ) หากบางส่วนของหน้าเว็บที่ควรเป็นเลเยอร์แยกต่างหาก (เช่น เมนูด้านข้างแบบเลื่อนเข้า) ไม่ได้รับเลเยอร์ คุณอาจแนะนำเบราว์เซอร์ได้โดยใช้แอตทริบิวต์ will-change
ใน CSS
![แผนผังเลเยอร์](https://developer.chrome.com/static/blog/inside-browser-part3/image/layer-tree-cc92336966c73.png?authuser=19&hl=th)
คุณอาจจะอยากมอบเลเยอร์ให้กับทุกองค์ประกอบ แต่การใส่เลเยอร์ในจำนวนที่มากเกินไปอาจทำให้การทำงานช้ากว่าการแรสเตอร์ส่วนเล็กๆ ของหน้าทุกๆ เฟรม ดังนั้นการวัดประสิทธิภาพในการแสดงผลของแอปพลิเคชันจึงเป็นสิ่งสำคัญ สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้ โปรดดูใช้พร็อพเพอร์ตี้แบบผสมเท่านั้นและจัดการจำนวนเลเยอร์
แรสเตอร์และการรวมออกจากเทรดหลัก
เมื่อสร้างแผนผังเลเยอร์และได้กำหนดลำดับการแสดงผลแล้ว เทรดหลักจะรวมข้อมูลนั้นไปยังเทรดของคอมโพสิต จากนั้นเธรดเครื่องมือประกอบจะแรสเตอร์แต่ละเลเยอร์ เลเยอร์อาจมีขนาดใหญ่พอๆ กับความยาวทั้งหมดของหน้าเว็บ ดังนั้นเธรดเครื่องมือประกอบจะแบ่งชิ้นส่วนออกเป็นชิ้นส่วนต่างๆ แล้วส่งแต่ละชิ้นส่วนไปยังชุดข้อความแรสเตอร์ ชุดข้อความแรสเตอร์จะแรสเตอร์แต่ละการ์ดและจัดเก็บไว้ในหน่วยความจำ GPU
![แรสเตอร์](https://developer.chrome.com/static/blog/inside-browser-part3/image/raster-9dfd7af5a9554.png?authuser=19&hl=th)
เทรดคอมโพสิตจะจัดลำดับความสำคัญชุดข้อความแรสเตอร์ต่างๆ เพื่อแรสเตอร์สิ่งต่างๆ ภายในวิวพอร์ต (หรือที่อยู่ใกล้เคียง) ก่อนแรสเตอร์ได้ เลเยอร์ยังมีการเรียงต่อกันหลายรายการสำหรับความละเอียดต่างๆ เพื่อจัดการสิ่งต่างๆ เช่น การซูมเข้า
เมื่อมีการแรสเตอร์ชิ้นส่วนแล้ว เทรดคอมโพสิตจะรวบรวมข้อมูลชิ้นส่วนที่เรียกว่าวาดกลุ่มควอด เพื่อสร้างเฟรมคอมโพสิต
วาดกลุ่ม | มีข้อมูล เช่น ตำแหน่งของการ์ดในหน่วยความจำ และตำแหน่งในหน้าเว็บสำหรับวาดชิ้นส่วนโดยพิจารณาการจัดวางหน้าเว็บ |
เฟรมคอมโพสิต | คอลเล็กชันการวาดรูปที่เป็นตัวแทนกรอบของหน้า |
จากนั้นเฟรมคอมโพสิเตอร์จึงส่งไปยังกระบวนการของเบราว์เซอร์ผ่าน IPC ที่จุดนี้คุณจะเพิ่มเฟรมคอมโพสเซอร์อื่นจากเธรด UI สำหรับการเปลี่ยน UI ของเบราว์เซอร์หรือจากกระบวนการแสดงผลอื่นๆ สำหรับส่วนขยายได้ เฟรมคอมโพสิเตอร์เหล่านี้จะส่งไปยัง GPU เพื่อแสดงบนหน้าจอ หากมีเหตุการณ์การเลื่อนเข้ามา เทรดคอมโพสิตจะสร้างเฟรม compositor อีกเฟรมหนึ่งเพื่อส่งไปยัง GPU
![Composit](https://developer.chrome.com/static/blog/inside-browser-part3/image/composit-266744978ac93.png?authuser=19&hl=th)
ประโยชน์ของการประกอบหน้าเว็บคือระบบจะดำเนินการได้โดยไม่เกี่ยวข้องกับเทรดหลัก เทรดคอมโพสิตไม่จำเป็นต้องรอการคำนวณรูปแบบหรือการดำเนินการ JavaScript ดังนั้นการจัดองค์ประกอบเฉพาะภาพเคลื่อนไหวจึงถือเป็นวิธีที่ดีที่สุดเพื่อประสิทธิภาพที่ราบรื่น หากต้องมีการคำนวณเลย์เอาต์หรือ Paint อีกครั้ง ก็ต้องใช้เทรดหลักที่เกี่ยวข้อง
สรุป
ในโพสต์นี้ เราได้พิจารณาการแสดงผลไปป์ไลน์ ตั้งแต่การแยกวิเคราะห์ไปจนถึงการประสาน เราหวังว่าตอนนี้คุณจะมีกำลัง อ่านเพิ่มเติมเกี่ยวกับการเพิ่มประสิทธิภาพของเว็บไซต์
ในโพสต์ถัดไปและโพสต์สุดท้ายของซีรีส์นี้ เราจะมาดูชุดข้อความประกอบการพิจารณารายละเอียดเพิ่มเติมและดูว่าจะเกิดอะไรขึ้นเมื่อมีข้อมูลจากผู้ใช้อย่าง mouse move
และ click
เข้ามา
คุณชอบโพสต์นี้ไหม หากคุณมีคำถามหรือคำแนะนำสำหรับโพสต์ในอนาคต เรายินดีรับฟังความคิดเห็นจากคุณในส่วนความคิดเห็นด้านล่างหรือ @kosamari บน Twitter