ฉันเชื่อว่าทุกคนควรต้องเผชิญกับความต้องการดังกล่าวเมื่อเรียนรู้ Canvas หรือใช้ Canvas ในการพัฒนาโครงการ: เพื่อใช้อุปกรณ์ Sketchpad ที่สามารถเขียนได้
ฉันเชื่อว่าสำหรับเด็กที่คุ้นเคยกับ Canvas มากขึ้น สามารถทำได้โดยใช้โค้ดเพียงไม่กี่สิบบรรทัด ตัวอย่างต่อไปนี้เป็นตัวอย่างง่ายๆ:
<!DOCTYPE html><html><head> <title>การสาธิต Sketchpad</title> <style type=text/css> canvas { border: 1px blue solid; } </style></head><body> <canvas id=canvas width=800 height=500></canvas> <script type=text/javascript> ให้ isDown = false; ให้ beginningPoint = null; document.querySelector('#canvas'); const ctx = canvas.getContext('2d'); // ตั้งค่าสีของเส้น ctx. strokeStyle = 'red'; .lineCap = 'รอบ'; canvas.addEventListener('mousedown', down, false); canvas.addEventListener('mousemove', ย้าย, เท็จ); canvas.addEventListener('mouseup', up, false); canvas.addEventListener('mouseout', up, false); ฟังก์ชั่น down(evt) { isDown = true; beginningPoint = getPos(evt) ฟังก์ชัน move(evt) { if (!isDown) return; const endPoint; up(evt) { if (!isDown) return; const endPoint (evt) { กลับ { x: evt.clientX, y: evt.clientY } } ฟังก์ชั่น DrawLine(beginPoint, endPoint) { ctx.beginPath(); ctx.moveTo(beginPoint.x, beginningPoint.y); ctx.lineTo(endPoint.x, endPoint.y); ctx.closePath(); } </script></body></html >
ตรรกะของการนำไปปฏิบัตินั้นง่ายมากเช่นกัน:
mousedown
, mouseup
และ mousemove
และเรายังสร้างตัวแปร isDown
อีกด้วยmousedown
) ให้ตั้งค่า isDown
เป็น true
และเมื่อผู้ใช้วางเมาส์ลง ( mouseup
) ให้ตั้งค่าเป็น false
ข้อดีของสิ่งนี้คือสามารถระบุได้ว่าผู้ใช้อยู่ในสถานะการวาดหรือไม่ ;mousemove
อย่างต่อเนื่อง หาก isDown
เป็น true
(นั่นคือในสถานะการเขียน) จุดปัจจุบันจะถูกเชื่อมต่อและวาดด้วยจุดก่อนหน้าผ่านเมธอด lineTo
ของ ผ้าใบ;จากขั้นตอนข้างต้น เราสามารถทราบถึงฟังก์ชันกระดานวาดภาพขั้นพื้นฐานได้ อย่างไรก็ตาม สิ่งต่าง ๆ ไม่ใช่เรื่องง่าย รองเท้าเด็กที่ต้องระวังอาจพบปัญหาร้ายแรง - เส้นที่วาดในลักษณะนี้จะขรุขระและไม่เรียบเพียงพอ และยิ่งคุณเร็วเท่าไร วาดเส้นที่แตกหักก็จะยิ่งแข็งแกร่งขึ้นเท่านั้น ประสิทธิภาพดังแสดงด้านล่าง:
ทำไมสิ่งนี้ถึงเกิดขึ้น? การวิเคราะห์ปัญหาสาเหตุหลักของปรากฏการณ์นี้คือ:
เราเชื่อมต่อจุดต่างๆ โดยใช้วิธี lineTo
ของ canvas สิ่งที่เชื่อมต่อจุดสองจุดที่อยู่ติดกันคือเส้นตรง ไม่ใช่เส้นโค้ง ดังนั้นสิ่งที่วาดด้วยวิธีนี้คือเส้นหลายเส้น
ถูกจำกัดโดยความถี่ในการรวบรวมเหตุการณ์ mousemove
ของเบราว์เซอร์ ทุกคนรู้ดีว่าในระหว่าง mousemove
เบราว์เซอร์จะรวบรวมพิกัดของเมาส์ปัจจุบันทุก ๆ ช่วงเวลาสั้น ๆ ดังนั้น ยิ่งเมาส์เคลื่อนที่เร็วเท่าไร ระยะห่างระหว่างจุดสองจุดที่อยู่ติดกันก็จะยิ่งไกลออกไป ห่างออกไปความรู้สึกของเส้นพับที่ชัดเจนมากขึ้น;
จริงๆ แล้วมีหลายวิธีในการวาดเส้นโค้งที่ราบรื่น หาก lineTo
ไม่น่าเชื่อถือ เราสามารถใช้ API การวาดภาพอื่นของ canvas - quadraticCurveTo
ซึ่งใช้ในการวาดเส้นโค้ง Bezier กำลังสอง
เส้นโค้งเบซีเยร์กำลังสอง
quadraticCurveTo(cp1x, cp1y, x, y)
การเรียกเมธอด quadraticCurveTo
ต้องใช้พารามิเตอร์สี่ตัว cp1x
และ cp1y
อธิบายจุดควบคุม ในขณะที่ x
และ y
เป็นจุดสิ้นสุดของเส้นโค้ง:
ข้อมูลรายละเอียดเพิ่มเติมสามารถพบได้ใน MDN
เนื่องจากเราต้องการใช้เส้นโค้ง Bezier จึงชัดเจนว่าข้อมูลของเรายังไม่เพียงพอ เพื่ออธิบายเส้นโค้ง Bezier แบบสมการกำลังสอง เราจำเป็นต้องมีจุดเริ่มต้น จุดควบคุม และจุดสิ้นสุด
มีอัลกอริธึมที่ชาญฉลาดมากที่สามารถช่วยให้เราได้รับข้อมูลนี้
อัลกอริทึมในการรับประเด็นสำคัญของเบซิเยร์กำลังสองอัลกอริทึมนี้เข้าใจได้ไม่ยาก ผมขอยกตัวอย่างโดยตรง:
สมมติว่าเรารวบรวมพิกัดของเมาส์ทั้งหมด 6 จุดในภาพวาด ได้แก่ A, B, C, D, E, F
; นำจุดสามจุดก่อนหน้า A, B, C
, คำนวณจุดกึ่งกลาง B1
ของ B
และ C
และใช้ A
คือ จุดเริ่มต้น B
คือจุดควบคุม B1
คือจุดสิ้นสุด ใช้ quadraticCurveTo
เพื่อวาดส่วนโค้ง Bezier กำลังสอง
จากนั้น คำนวณจุดกึ่งกลาง C1
ระหว่างจุด C
และ D
และวาดเส้นโค้งต่อโดยให้ B1
เป็นจุดเริ่มต้น C
เป็นจุดควบคุม และ C1
เป็นจุดสิ้นสุด
การวาดภาพดำเนินต่อไปโดยการเปรียบเทียบ เมื่อถึงจุดสุดท้าย F
เส้นโค้ง Bezier จะสิ้นสุดด้วย D1
ซึ่งเป็นจุดกึ่งกลางของ D
และ E
เป็นจุดเริ่มต้น E
เป็นจุดควบคุม และ F
เป็นจุดสิ้นสุด
ตกลง อัลกอริธึมเป็นเช่นนี้ จากนั้นเราจะอัปเกรดโค้ดที่มีอยู่ตามอัลกอริธึมนี้:
ให้ isDown = false;ให้จุด = [];ให้ startPoint = null;const canvas = document.querySelector('#canvas');const ctx = canvas.getContext('2d');//ตั้งค่าสีของเส้น ctx. strokeStyle = 'red';ctx.lineWidth = 1;ctx.lineJoin = 'round';ctx.lineCap = 'รอบ';canvas.addEventListener('mousedown', ลง, false);canvas.addEventListener('mousemove', ย้าย, false);canvas.addEventListener('mouseup', ขึ้น, false);canvas.addEventListener('mouseout' , ขึ้น, เท็จ); ฟังก์ชั่นลง (evt) { isDown = true; point.push({x, y}); beginningPoint = {x, y};} ฟังก์ชัน move(evt) { if (!isDown) return; const { x, y} = getPos(evt); x, y}); if (points.length > 3) { const LastTwoPoints = { x: (lastTwoPoints[0].x + LastTwoPoints[1].x) / 2, y: (lastTwoPoints[0].y + LastTwoPoints[1].y) / 2, } DrawLine(beginPoint, controlPoint, endPoint); BeginPoint = endPoint; }}ฟังก์ชั่นขึ้น(evt) { ถ้า (!isDown) กลับ; point.push({x, y}); if (points.length > 3) { const LastTwoPoints = point.slice(-2); const controlPoint = LastTwoPoints[0]; , controlPoint, endPoint); } beginningPoint = null; isDown = false; จุด = [];} ฟังก์ชั่น getPos(evt) { กลับ { x: evt.clientX, y: evt.clientY }} ฟังก์ชั่น DrawLine (beginPoint, controlPoint, endPoint) { ctx.beginPath(); y, endPoint.x, endPoint.y); ctx.closePath();}
บนพื้นฐานของต้นฉบับ เราได้สร้าง points
ตัวแปรเพื่อบันทึกจุดที่เมาส์ผ่านในเหตุการณ์ mousemove
ก่อนหน้า ตามอัลกอริทึมนี้ ต้องใช้อย่างน้อย 3 จุดในการวาดเส้นโค้ง Bezier กำลังสอง ดังนั้นเราจึงมีเพียง คะแนนเป็น points
การวาดจะเริ่มเมื่อจำนวนคะแนนมากกว่า 3 เท่านั้น การประมวลผลครั้งต่อไปจะเหมือนกับอัลกอริทึมนี้ทุกประการ ดังนั้นฉันจะไม่ลงรายละเอียดที่นี่
หลังจากอัปเดตโค้ดแล้ว เส้นโค้งของเราก็จะเรียบขึ้นมาก ดังแสดงในรูปด้านล่าง:
บทความนี้จบเพียงเท่านี้ หวังว่าทุกคนจะสนุกกับการวาดรูปบนผืนผ้าใบนะครับ~ เจอกันใหม่ตอนหน้าครับ :)
หากคุณสนใจรองเท้าเด็ก คุณสามารถคลิกที่นี่เพื่อติดตามบล็อกของฉัน เราจะแชร์บล็อกโพสต์ใหม่ๆ ที่น่าสนใจที่นี่โดยเร็วที่สุด~
ข้างต้นคือเนื้อหาทั้งหมดของบทความนี้ ฉันหวังว่ามันจะเป็นประโยชน์ต่อการศึกษาของทุกคน ฉันหวังว่าทุกคนจะสนับสนุน VeVb Wulin Network