การสร้างปุ่มกด Arduino ใหม่โดยใช้ SVG และ <lit-element>

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างย่อ ↬ HTML มาพร้อมกับการควบคุมอินพุตจำนวนมาก และมีไลบรารีส่วนประกอบจำนวนมากที่มีการควบคุมมาตรฐานมากมาย เช่น ช่องทำเครื่องหมาย และปุ่มตัวเลือก แต่จะเกิดอะไรขึ้นเมื่อคุณต้องการสิ่งผิดปกติ

ในบทความนี้ คุณจะได้เรียนรู้วิธีสร้างส่วนประกอบ HTML แบบกำหนดเองที่เลียนแบบวัตถุทางกายภาพ เช่น ปุ่มกด Arduino เราจะวาดส่วนประกอบใน Inkscape ตั้งแต่เริ่มต้น เพิ่มประสิทธิภาพโค้ด SVG ที่สร้างขึ้นสำหรับเว็บ และรวมเป็นส่วนประกอบเว็บแบบสแตนด์อโลนโดยใช้ไลบรารี lit-element ที่มีน้ำหนักเบา โดยให้ความสนใจเป็นพิเศษกับการพิจารณาความสามารถในการเข้าถึงและการใช้งานบนมือถือ

วันนี้ ฉันจะพาคุณผ่านการเดินทางของการสร้างองค์ประกอบ HTML ที่เลียนแบบส่วนประกอบปุ่มกดชั่วขณะซึ่งมักใช้กับ Arduino และในโครงการอิเล็กทรอนิกส์ เราจะใช้เทคโนโลยี เช่น SVG, Web Components และ lit-element และเรียนรู้วิธีทำให้ปุ่มสามารถเข้าถึงได้ผ่านกลอุบาย JavaScript-CSS

เริ่มกันเลย!

จาก Arduino เป็น HTML: ความต้องการส่วนประกอบปุ่มกด

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

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

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

ดังนั้น นอกจากตัวจำลองของโปรเซสเซอร์ ฉันยังทำงานกับไลบรารีของส่วนประกอบ HTML ที่เลียนแบบฮาร์ดแวร์จริง โดยเริ่มจากสององค์ประกอบแรกที่คุณจะพบในเกือบทุกโครงการอิเล็กทรอนิกส์: LED และปุ่มกด

การทำงานขององค์ประกอบ LED และปุ่มกด
การทำงานขององค์ประกอบ LED และปุ่มกด

LED ค่อนข้างเรียบง่าย เนื่องจากมีสถานะเอาต์พุตเพียงสองสถานะ: เปิดและปิด เบื้องหลังจะใช้ฟิลเตอร์ SVG เพื่อสร้างเอฟเฟกต์แสง

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

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

การกำหนดข้อกำหนดสำหรับปุ่มกด

ส่วนประกอบของเราจะคล้ายกับปุ่มกดขนาด 12 มม. ปุ่มเหล่านี้มีอยู่ทั่วไปในชุดสตาร์ทอุปกรณ์อิเล็กทรอนิกส์ และมาพร้อมกับฝาครอบหลายสี ดังที่คุณเห็นในภาพด้านล่าง:

เกม Simon พร้อมปุ่มกดสีเหลือง แดง น้ำเงิน และเขียว
เกม Simon พร้อมปุ่มกดสีเหลือง แดง น้ำเงิน และเขียว (ตัวอย่างขนาดใหญ่)

ในแง่ของพฤติกรรม ปุ่มกดควรมีสองสถานะ: กดและปล่อย สิ่งเหล่านี้คล้ายกับเหตุการณ์ HTML ของ mousedown/mouseup แต่เราต้องตรวจสอบให้แน่ใจว่าสามารถใช้ปุ่มกดจากอุปกรณ์มือถือและสามารถเข้าถึงได้สำหรับผู้ใช้โดยไม่ต้องใช้เมาส์

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

หากคุณต้องการเรียนรู้เพิ่มเติม โปรดดูการพูดคุยที่ฉันจัดขึ้นกับ Benjamin Gruenbaum ที่ SmashingConf Freiburg ในปี 2019: “Anatomy of a Click”

เพื่อสรุปข้อกำหนดของเรา ปุ่มกดของเราจำเป็นต้อง:

  1. ดูคล้ายกับปุ่มกดขนาด 12 มม.
  2. มีสองสถานะที่แตกต่างกัน: ถูกกดและปล่อย และควรมองเห็นได้ชัดเจน
  3. รองรับการโต้ตอบของเมาส์ อุปกรณ์พกพา และผู้ใช้คีย์บอร์ดสามารถเข้าถึงได้
  4. รองรับสีหมวกที่แตกต่างกัน (อย่างน้อยสีแดง, สีเขียว, สีฟ้า, สีเหลือง, สีขาวและสีดำ)

ตอนนี้เราได้กำหนดข้อกำหนดแล้ว เราสามารถเริ่มทำงานในการดำเนินการได้

SVG เพื่อชัยชนะ

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

อย่างไรก็ตาม ในกรณีของเรา เราจะใช้วิธีอื่น: กราฟิก SVG SVG รองรับกราฟิกที่ซับซ้อนได้ง่ายกว่า CSS มาก (ใช่ ฉันรู้ คุณสามารถสร้างสิ่งที่น่าสนใจด้วย CSS ได้ แต่ไม่ได้หมายความว่ามันควรจะเป็น) แต่อย่ากังวล เราไม่ทิ้ง CSS ทั้งหมด มันจะช่วยเราในการกำหนดสไตล์ของปุ่มกด และในที่สุดก็ทำให้เข้าถึงได้

SVG มีข้อได้เปรียบที่สำคัญอีกประการหนึ่ง เมื่อเปรียบเทียบกับภาพกราฟิกแรสเตอร์: ง่ายต่อการจัดการจาก JavaScript และสามารถจัดรูปแบบผ่าน CSS ได้ ซึ่งหมายความว่าเราสามารถจัดเตรียมรูปภาพเดียวสำหรับปุ่ม และใช้ JavaScript เพื่อปรับแต่งตัวพิมพ์สี และรูปแบบ CSS เพื่อระบุสถานะของปุ่ม เรียบร้อยใช่มั้ย?

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

การวาดปุ่มกดด้วย Inkscape

Inkscape เป็นเครื่องมือที่ฉันโปรดปรานในการสร้างกราฟิกแบบเวกเตอร์ SVG ฟรีและอัดแน่นไปด้วยคุณสมบัติอันทรงพลัง เช่น ชุดตัวกรองที่ตั้งไว้ล่วงหน้าจำนวนมาก การติดตามบิตแมป และการดำเนินการไบนารีของพาธ ฉันเริ่มใช้ Inkscape เพื่อสร้างงานศิลปะ PCB แต่ในช่วงสองปีที่ผ่านมา ฉันเริ่มใช้มันสำหรับงานตัดต่อกราฟิกส่วนใหญ่ของฉัน

การวาดปุ่มกดใน Inkscape นั้นค่อนข้างตรงไปตรงมา เราจะวาดภาพมุมมองด้านบนของปุ่มและสายโลหะทั้งสี่ที่เชื่อมต่อกับส่วนอื่น ๆ ดังนี้:

  1. สี่เหลี่ยมสีเทาเข้มขนาด 12×12 มม. สำหรับกล่องพลาสติก โดยมีมุมโค้งมนเล็กน้อยเพื่อให้ดูนุ่มขึ้น
  2. สี่เหลี่ยมสีเทาอ่อนขนาดเล็กกว่า 10.5×10.5 สำหรับฝาครอบโลหะ
  3. วงกลมสีเข้มสี่วง แต่ละมุมสำหรับหมุดที่ยึดปุ่มไว้ด้วยกัน
  4. วงกลมขนาดใหญ่ตรงกลาง นั่นคือรูปร่างของฝาครอบปุ่ม
  5. วงกลมขนาดเล็กที่อยู่ตรงกลางสำหรับด้านบนของฝาครอบปุ่ม
  6. สี่เหลี่ยมสีเทาอ่อนสี่รูปในรูปร่าง "T" สำหรับสายโลหะของปุ่ม

และผลลัพธ์ก็ขยายใหญ่ขึ้นเล็กน้อย:

Sketchbutton ที่วาดด้วยมือของเรา
ภาพร่างปุ่มกดที่วาดด้วยมือของเรา (ตัวอย่างขนาดใหญ่)

ในขั้นสุดท้าย เราจะเพิ่มเวทย์มนตร์การไล่ระดับสี SVG ให้กับเส้นขอบของปุ่ม เพื่อให้เกิดความรู้สึก 3 มิติ:

การเติมสีไล่ระดับเพื่อสร้างความรู้สึก 3 มิติ
การเติมไล่ระดับเพื่อสร้างความรู้สึก 3 มิติ (ตัวอย่างขนาดใหญ่)

เราจะไปที่นั่น! เรามีภาพจริงแล้ว ตอนนี้เราต้องนำมันเข้าสู่เว็บ

จาก Inkscape สู่ Web SVG

ดังที่ได้กล่าวไว้ข้างต้น SVG นั้นค่อนข้างตรงไปตรงมาในการฝังลงใน HTML — คุณสามารถวางเนื้อหาของไฟล์ SVG ลงในเอกสาร HTML ของคุณ เปิดในเบราว์เซอร์ แล้วเนื้อหาก็จะแสดงบนหน้าจอของคุณ คุณสามารถเห็นการทำงานจริงในตัวอย่าง CodePen ต่อไปนี้:

ดูปุ่มกด Pen SVG ในรูปแบบ HTML โดย @Uri Shaked

ดูปุ่มกด Pen SVG ในรูปแบบ HTML โดย @Uri Shaked

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

โชคดีที่ Inkscape สามารถล้างสิ่งสกปรกส่วนใหญ่ให้เราได้ นี่คือวิธีที่คุณทำ:

  1. ไปที่เมนู ไฟล์ และคลิกที่ ล้างเอกสาร (การดำเนินการนี้จะลบคำจำกัดความที่ไม่ได้ใช้ออกจากเอกสารของคุณ)
  2. ไปที่ File อีกครั้งแล้วคลิก Save as… เมื่อบันทึก ให้เลือก Optimized SVG ( *.svg ) ในรายการแบบเลื่อนลง บันทึกเป็นประเภท
  3. คุณจะเห็นกล่องโต้ตอบ "เอาต์พุต SVG ที่ปรับให้เหมาะสม" พร้อมแท็บสามแท็บ ตรวจสอบตัวเลือกทั้งหมด ยกเว้น "เก็บข้อมูลตัวแก้ไข", "เก็บคำจำกัดความที่ไม่อ้างอิง" และ "รักษารหัสที่สร้างด้วยตนเอง..."
(ตัวอย่างขนาดใหญ่)

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

SVG ที่ปรับให้เหมาะสมยังอ่านและเข้าใจได้ง่ายขึ้นมาก ในข้อความที่ตัดตอนมาต่อไปนี้ คุณควรสามารถระบุสี่เหลี่ยมสองรูปที่สร้างร่างกายของปุ่มกดได้อย่างง่ายดาย:

 <rect width="12" height="12" rx=".44" ry=".44" fill="#464646" stroke-width="1.0003"/> <rect x=".75" y=".75" width="10.5" height="10.5" rx=".211" ry=".211" fill="#eaeaea"/> <g fill="#1b1b1b"> <circle cx="1.767" cy="1.7916" r=".37"/> <circle cx="10.161" cy="1.7916" r=".37"/> <circle cx="10.161" cy="10.197" r=".37"/> <circle cx="1.767" cy="10.197" r=".37"/> </g> <circle cx="6" cy="6" r="3.822" fill="url(#a)"/> <circle cx="6" cy="6" r="2.9" fill="#ff2a2a" stroke="#2f2f2f" stroke-opacity=".47" stroke-width=".08"/>

คุณสามารถย่อโค้ดให้สั้นลงได้อีก เช่น โดยเปลี่ยนความกว้างเส้นโครงร่างของสี่เหลี่ยมแรกจาก 1.0003 เหลือเพียง 1 ไม่ได้สร้างความแตกต่างอย่างมีนัยสำคัญในขนาดไฟล์ แต่ช่วยให้อ่านโค้ดได้ง่ายขึ้น

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

จากนี้ไป เราจะเลิกใช้ Inkscape และทำงานกับการแสดงข้อความของรูปภาพ SVG

การสร้างส่วนประกอบเว็บที่นำกลับมาใช้ใหม่ได้

จนถึงตอนนี้ เรามีกราฟิกสำหรับปุ่มกดของเรา พร้อมที่จะแทรกลงในโปรแกรมจำลองของเรา เราสามารถปรับแต่งสีของปุ่มได้อย่างง่ายดายโดยการเปลี่ยนแอตทริบิวต์การ fill ของวงกลมที่เล็กกว่า และสีเริ่มต้นของการไล่ระดับสีของวงกลมที่ใหญ่กว่า

เป้าหมายต่อไปของเราคือการเปลี่ยนปุ่มกดของเราให้เป็น Web Component ที่นำกลับมาใช้ใหม่ได้ ซึ่งสามารถปรับแต่งได้โดยส่งผ่านแอตทริบิวต์ color และตอบสนองต่อการโต้ตอบของผู้ใช้ (เหตุการณ์กด/เผยแพร่) เราจะใช้ lit-element ซึ่งเป็นไลบรารีขนาดเล็กที่ช่วยให้การสร้าง Web Components ง่ายขึ้น

lit-element เลิศในการสร้างไลบรารีส่วนประกอบแบบสแตนด์อโลนขนาดเล็ก มันถูกสร้างขึ้นบนมาตรฐาน Web Components ซึ่งช่วยให้ส่วนประกอบเหล่านี้ถูกใช้โดยเว็บแอปพลิเคชันใด ๆ โดยไม่คำนึงถึงเฟรมเวิร์กที่ใช้: Angular, React, Vue หรือ Vanilla JS ทั้งหมดจะสามารถใช้ส่วนประกอบของเราได้

การสร้างส่วนประกอบในองค์ประกอบ lit-element ทำได้โดยใช้ไวยากรณ์ตามคลาส ด้วยวิธีการ render() ที่ส่งคืนโค้ด HTML สำหรับองค์ประกอบ คล้ายกับ React เล็กน้อยหากคุณคุ้นเคย อย่างไรก็ตาม ไม่เหมือนกับ react ที่ lit-element ใช้ตัวอักษรเทมเพลตที่ติดแท็ก Javascript มาตรฐานเพื่อกำหนดเนื้อหาของส่วนประกอบ

นี่คือวิธีสร้างองค์ประกอบ hello-world แบบง่ายๆ:

 import { customElement, html, LitElement } from 'lit-element'; @customElement('hello-world') export class HelloWorldElement extends LitElement { render() { return html` <h1> Hello, World! </h1> `; } }

คอมโพเนนต์นี้สามารถใช้ที่ใดก็ได้ในโค้ด HTML ของคุณง่ายๆ โดยการเขียน <hello-world></hello-world>

หมายเหตุ : ที่จริงแล้ว ปุ่มกดของเราต้องการโค้ดเพิ่มอีกเล็กน้อย: เราจำเป็นต้องประกาศคุณสมบัติอินพุตสำหรับสี โดยใช้ตัวตกแต่ง @property() (และด้วยค่าเริ่มต้นเป็นสีแดง) และวางโค้ด SVG ลงในการ render() วิธีการ แทนที่สีของฝาครอบปุ่มด้วยค่าของคุณสมบัติสี (ดูตัวอย่าง) บิตที่สำคัญอยู่ในบรรทัดที่ 5 ซึ่งเรากำหนดคุณสมบัติสี: @property() color = 'red'; นอกจากนี้ ในบรรทัดที่ 35 (ที่เราใช้คุณสมบัตินี้เพื่อกำหนดสีเติมสำหรับวงกลมที่ทำให้เป็นฝาของปุ่ม) โดยใช้ไวยากรณ์ตามตัวอักษรของเทมเพลต JavaScript เขียนเป็น ${color} :

 <circle cx="6" cy="6" r="2.9" fill="${color}" stroke="#2f2f2f" stroke-opacity=".47" stroke-width=".08" />

ทำให้เป็นแบบโต้ตอบ

จิ๊กซอว์ชิ้นสุดท้ายคือการทำให้ปุ่มโต้ตอบได้ มีสองด้านที่เราต้องพิจารณา: การ ตอบสนองด้วยภาพต่อ การโต้ตอบ เช่นเดียวกับ การตอบสนองทางโปรแกรมต่อ การโต้ตอบ

สำหรับส่วนที่มองเห็น เราสามารถผกผันการเติมไล่ระดับของเส้นขอบปุ่ม ซึ่งจะสร้างภาพลวงตาเมื่อกดปุ่ม:

สลับการไล่ระดับรูปร่างของปุ่ม
สลับการไล่ระดับเส้นขอบของปุ่ม (แสดงตัวอย่างขนาดใหญ่)

การไล่ระดับสีสำหรับรูปร่างของปุ่มถูกกำหนดโดยโค้ด SVG ต่อไปนี้ โดยที่ ${color} ถูกแทนที่ด้วยสีของปุ่มด้วย lit-element ดังที่อธิบายไว้ข้างต้น:

 <linearGradient x1="0" x2="1" y1="0" y2="1"> <stop stop-color="#ffffff" offset="0" /> <stop stop-color="${color}" offset="0.3" /> <stop stop-color="${color}" offset="0.5" /> <stop offset="1" /> </linearGradient>

วิธีหนึ่งสำหรับรูปลักษณ์ของปุ่มที่ถูกกดคือการกำหนดการไล่ระดับสีที่สอง สลับลำดับของสี และใช้เป็นสีเติมของวงกลมทุกครั้งที่กดปุ่ม อย่างไรก็ตาม มีเคล็ดลับดีๆ ที่ช่วยให้เราใช้การไล่ระดับสีแบบเดิมซ้ำได้: เราสามารถหมุนองค์ประกอบ svg ได้ 180 องศาโดยใช้การแปลง SVG:

 <circle cx="6" cy="6" r="3.822" fill="url(#a)" transform="rotate(180 6 6)" />

แอตทริบิวต์ transform บอก SVG ว่าเราต้องการหมุนวงกลม 180 องศา และการหมุนควรเกิดขึ้นที่จุด (6, 6) ซึ่งเป็นศูนย์กลางของวงกลม (กำหนดโดย cx และ cy ) การแปลง SVG ยังส่งผลต่อการเติมของรูปร่าง ดังนั้นการไล่ระดับสีของเราจะหมุนไปด้วย

เราเพียงต้องการเปลี่ยนการไล่ระดับสีเมื่อกดปุ่ม ดังนั้นแทนที่จะเพิ่มแอตทริบิวต์ transform โดยตรงบนองค์ประกอบ <circle> ตามที่เราได้ทำข้างต้น เราจะตั้งค่าคลาส CSS สำหรับองค์ประกอบนี้ แล้วจึงใช้ประโยชน์ จากข้อเท็จจริงที่ว่าแอตทริบิวต์ SVG สามารถตั้งค่าผ่าน CSS ได้ แม้ว่าจะใช้รูปแบบที่แตกต่างกันเล็กน้อย:

 transform: rotate(180deg); transform-origin: 6px 6px;

กฎ CSS สองข้อนี้ทำเหมือนกับการ transform ที่เราทำด้านบนทุกประการ — หมุนวงกลม 180 องศารอบจุดศูนย์กลางที่ (6, 6) เราต้องการให้กฎเหล่านี้ใช้เฉพาะเมื่อมีการกดปุ่ม ดังนั้นเราจะเพิ่มชื่อคลาส CSS ให้กับแวดวงของเรา:

 <circle class="button-contour" cx="6" cy="6" r="3.822" fill="url(#a)" />

และตอนนี้ เราสามารถใช้ :active CSS pseudo-class เพื่อใช้การแปลงกับ button-contour ทุกครั้งที่องค์ประกอบ SVG ถูกคลิก:

 svg:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; }

lit-element ช่วยให้เราแนบสไตล์ชีตเข้ากับองค์ประกอบของเราโดยการประกาศใน getter แบบคงที่ภายในคลาสส่วนประกอบของเรา โดยใช้เทมเพลตที่ติดแท็กตามตัวอักษร:

 static get styles() { return css` svg:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; } `; }

เช่นเดียวกับเทมเพลต HTML ไวยากรณ์นี้ช่วยให้เราสามารถใส่ค่าที่กำหนดเองลงในโค้ด CSS ของเราได้ แม้ว่าเราจะไม่ต้องการมันที่นี่ก็ตาม lit-element ยังดูแลการสร้าง Shadow DOM สำหรับส่วนประกอบของเรา ดังนั้น CSS จะมีผลกับองค์ประกอบภายในส่วนประกอบของเราเท่านั้น และไม่ตกไปยังส่วนอื่นๆ ของแอปพลิเคชัน

แล้วพฤติกรรมทางโปรแกรมของปุ่มเมื่อกดเป็นอย่างไร เราต้องการเริ่มเหตุการณ์เพื่อให้ผู้ใช้องค์ประกอบของเราทราบเมื่อใดก็ตามที่สถานะของปุ่มเปลี่ยนแปลง วิธีหนึ่งในการทำเช่นนี้คือการฟังเหตุการณ์ mousedown และ mouseup ในองค์ประกอบ SVG และเริ่มต้นเหตุการณ์ "button-press"/"button-release" ตามลำดับ นี่คือสิ่งที่ดูเหมือนกับไวยากรณ์ lit-element :

 render() { const { color } = this; return html` <svg @mousedown=${() => this.dispatchEvent(new Event('button-press'))} @mouseup=${() => this.dispatchEvent(new Event('button-release'))} ... </svg> `; }

อย่างไรก็ตาม นี่ไม่ใช่ทางออกที่ดีที่สุด อย่างที่เราจะได้เห็นในเร็วๆ นี้ แต่ก่อนอื่น มาดูโค้ดที่เราได้รับอย่างรวดเร็ว:

 import { customElement, css, html, LitElement, property } from 'lit-element'; @customElement('wokwi-pushbutton') export class PushbuttonElement extends LitElement { @property() color = 'red'; static get styles() { return css` svg:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; } `; } render() { const { color } = this; return html` <svg @mousedown=${() => this.dispatchEvent(new Event('button-press'))} @mouseup=${() => this.dispatchEvent(new Event('button-release'))} width="18mm" height="12mm" version="1.1" viewBox="-3 0 18 12" xmlns="https://www.w3.org/2000/svg" > <defs> <linearGradient x1="0" x2="1" y1="0" y2="1"> <stop stop-color="#ffffff" offset="0" /> <stop stop-color="${color}" offset="0.3" /> <stop stop-color="${color}" offset="0.5" /> <stop offset="1" /> </linearGradient> </defs> <rect x="0" y="0" width="12" height="12" rx=".44" ry=".44" fill="#464646" /> <rect x=".75" y=".75" width="10.5" height="10.5" rx=".211" ry=".211" fill="#eaeaea" /> <g fill="#1b1b1"> <circle cx="1.767" cy="1.7916" r=".37" /> <circle cx="10.161" cy="1.7916" r=".37" /> <circle cx="10.161" cy="10.197" r=".37" /> <circle cx="1.767" cy="10.197" r=".37" /> </g> <g fill="#eaeaea"> <path d="m-0.3538 1.4672c-0.058299 0-0.10523 0.0469-0.10523 0.10522v0.38698h-2.1504c-0.1166 0-0.21045 0.0938-0.21045 0.21045v0.50721c0 0.1166 0.093855 0.21045 0.21045 0.21045h2.1504v0.40101c0 0.0583 0.046928 0.10528 0.10523 0.10528h0.35723v-1.9266z" /> <path d="m-0.35376 8.6067c-0.058299 0-0.10523 0.0469-0.10523 0.10523v0.38697h-2.1504c-0.1166 0-0.21045 0.0939-0.21045 0.21045v0.50721c0 0.1166 0.093855 0.21046 0.21045 0.21046h2.1504v0.401c0 0.0583 0.046928 0.10528 0.10523 0.10528h0.35723v-1.9266z" /> <path d="m12.354 1.4672c0.0583 0 0.10522 0.0469 0.10523 0.10522v0.38698h2.1504c0.1166 0 0.21045 0.0938 0.21045 0.21045v0.50721c0 0.1166-0.09385 0.21045-0.21045 0.21045h-2.1504v0.40101c0 0.0583-0.04693 0.10528-0.10523 0.10528h-0.35723v-1.9266z" /> <path d="m12.354 8.6067c0.0583 0 0.10523 0.0469 0.10523 0.10522v0.38698h2.1504c0.1166 0 0.21045 0.0938 0.21045 0.21045v0.50721c0 0.1166-0.09386 0.21045-0.21045 0.21045h-2.1504v0.40101c0 0.0583-0.04693 0.10528-0.10523 0.10528h-0.35723v-1.9266z" /> </g> <g> <circle class="button-contour" cx="6" cy="6" r="3.822" fill="url(#a)" /> <circle cx="6" cy="6" r="2.9" fill="${color}" stroke="#2f2f2f" stroke-opacity=".47" stroke-width=".08" /> </g> </svg> `; } }
  • ดูตัวอย่าง →

คุณสามารถคลิกปุ่มแต่ละปุ่มและดูว่ามันตอบสนองอย่างไร สีแดงยังมีตัวฟังเหตุการณ์บางตัว (กำหนดไว้ใน index.html ) ดังนั้นเมื่อคุณคลิก คุณจะเห็นข้อความบางข้อความที่เขียนไปยังคอนโซล แต่เดี๋ยวก่อน ถ้าคุณต้องการใช้แป้นพิมพ์แทนล่ะ

ทำให้ส่วนประกอบเข้าถึงได้และเหมาะกับอุปกรณ์พกพา

ไชโย! เราได้สร้างส่วนประกอบปุ่มกดแบบใช้ซ้ำได้ด้วย SVG และ lit-element !

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

เริ่มต้นด้วยการแก้ปัญหาแป้นพิมพ์ เราสามารถทำให้ปุ่มเข้าถึงแป้นพิมพ์ได้โดยการเพิ่มแอตทริบิวต์ tabindex ให้กับองค์ประกอบ svg ทำให้โฟกัสได้ ในความคิดของฉัน ทางเลือกที่ดีกว่าคือการห่อปุ่มด้วยองค์ประกอบ <button> มาตรฐาน ด้วยการใช้องค์ประกอบมาตรฐาน เรายังทำให้มันเล่นได้ดีกับโปรแกรมอ่านหน้าจอและเทคโนโลยีช่วยเหลืออื่นๆ

วิธีการนี้มีข้อเสียเปรียบเพียงข้อเดียว ดังที่คุณเห็นด้านล่าง:

องค์ประกอบที่สวยงามของเราถูกห่อหุ้มไว้ภายในองค์ประกอบปุ่ม
องค์ประกอบที่สวยงามของเราอยู่ภายในองค์ประกอบ <button> (ตัวอย่างขนาดใหญ่)

<button> องค์ประกอบมาพร้อมกับสไตล์ในตัว ซึ่งสามารถแก้ไขได้ง่ายโดยใช้ CSS บางส่วนเพื่อลบสไตล์เหล่านี้:

 button { border: none; background: none; padding: 0; margin: 0; text-decoration: none; -webkit-appearance: none; -moz-appearance: none; } button:active .button-contour { transform: rotate(180deg); transform-origin: 6px 6px; }

โปรดทราบว่าเรายังได้เปลี่ยนตัวเลือกที่สลับกริดของรูปร่างปุ่มโดยใช้ button:active แทน svg:active เพื่อให้แน่ใจว่ารูปแบบการกดปุ่มจะถูกนำไปใช้ทุกครั้งที่มีการกดองค์ประกอบ <button> จริง โดยไม่คำนึงถึงอุปกรณ์อินพุตที่ใช้

เราสามารถทำให้คอมโพเนนต์ของเราอ่านหน้าจอได้ง่ายขึ้นด้วยการเพิ่มแอตทริบิวต์ aria-label ที่มีสีของปุ่ม:

 <button aria-label="${color} pushbutton">

ยังมีอีกสิ่งหนึ่งที่ต้องจัดการ: เหตุการณ์ "การกดปุ่ม" และ "การกดปุ่ม" ตามหลักการแล้ว เราต้องการเริ่มทำงานโดยอิงจาก CSS :active pseudo-class ของปุ่ม เช่นเดียวกับที่เราทำใน CSS ด้านบน กล่าวอีกนัยหนึ่ง เราต้องการเริ่มเหตุการณ์ "button-press" เมื่อใดก็ตามที่ปุ่มกลายเป็น :active และเหตุการณ์ "button-release" จะเริ่มทำงานเมื่อใดก็ตามที่เป็น :not(:active)

แต่คุณจะฟัง CSS pseudo-class จาก Javascript ได้อย่างไร

ปรากฎว่ามันไม่ง่ายนัก ฉันถามคำถามนี้กับชุมชน JavaScript Israel และในที่สุดก็ค้นพบแนวคิดหนึ่งที่ได้ผลจากเธรดที่ไม่มีที่สิ้นสุด: ใช้ :active ตัวเลือกเพื่อทริกเกอร์แอนิเมชั่น CSS แบบสั้นสุด ๆ จากนั้นฉันก็สามารถฟังจาก JavaScript โดยใช้ animationstart เหตุการณ์.

การทดลอง CodePen อย่างรวดเร็วได้พิสูจน์แล้วว่าการทำงานนี้เชื่อถือได้จริง เท่าที่ฉันชอบความซับซ้อนของแนวคิดนี้ ฉันตัดสินใจใช้วิธีแก้ปัญหาที่ต่างออกไปและง่ายกว่า เหตุการณ์เริ่มต้นของ animationstart ไม่พร้อมใช้งานบน Edge และ iOS Safari และการทริกเกอร์แอนิเมชั่น CSS เพียงเพื่อตรวจจับการเปลี่ยนแปลงของสถานะของปุ่มนั้นฟังดูไม่เหมือนวิธีที่ถูกต้องในการทำสิ่งต่างๆ

แต่เราจะเพิ่ม Listener เหตุการณ์สามคู่ให้กับองค์ประกอบ <button> : mousedown/mouseup สำหรับเมาส์, touchstart/touchend สำหรับอุปกรณ์มือถือ และ keyup/keydown สำหรับแป้นพิมพ์ ในความคิดของฉันไม่ใช่วิธีแก้ปัญหาที่หรูหราที่สุด แต่มันเป็นการหลอกลวงและใช้ได้กับทุกเบราว์เซอร์

 <button aria-label="${color} pushbutton" @mousedown=${this.down} @mouseup=${this.up} @touchstart=${this.down} @touchend=${this.up} @keydown=${(e: KeyboardEvent) => e.keyCode === SPACE_KEY && this.down()} @keyup=${(e: KeyboardEvent) => e.keyCode === SPACE_KEY && this.up()} >

โดยที่ SPACE_KEY เป็นค่าคงที่ที่เท่ากับ 32 และ up / down เป็นวิธีคลาสสองวิธีที่ส่งเหตุการณ์การ button-press และ button-release :

 @property() pressed = false; private down() { if (!this.pressed) { this.pressed = true; this.dispatchEvent(new Event('button-press')); } } private up() { if (this.pressed) { this.pressed = false; this.dispatchEvent(new Event('button-release')); } }
  • คุณสามารถค้นหาซอร์สโค้ดแบบเต็มได้ที่นี่

เราทำได้!

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

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

และสุดท้าย หากคุณสนใจ Arduino ให้ดูที่การเขียนโปรแกรม Simon สำหรับหลักสูตร Arduino ที่ฉันกำลังสร้าง ซึ่งคุณยังสามารถดูการทำงานของปุ่มกดได้อีกด้วย

ครั้งต่อไปแล้ว!