SVG 및 <lit-element>를 사용하여 Arduino 푸시 버튼 재생성

게시 됨: 2022-03-10
빠른 요약 ↬ HTML에는 많은 입력 컨트롤이 포함되어 있으며 확인란 및 라디오 버튼과 같은 많은 표준 컨트롤을 포함하는 수많은 구성 요소 라이브러리가 있습니다. 그러나 특이한 것이 필요할 때 어떻게 됩니까?

이 기사에서는 Arduino 푸시 버튼과 같은 물리적 개체를 모방하는 사용자 정의 HTML 구성 요소를 빌드하는 방법을 배웁니다. Inkscape에서 구성 요소를 처음부터 그리고 생성된 SVG 코드를 웹용으로 최적화하고 가벼운 lit-element 라이브러리를 사용하여 독립 실행형 웹 구성 요소로 래핑하여 접근성 및 모바일 사용성 고려 사항에 각별한 주의를 기울일 것입니다.

오늘은 Arduino 및 전자 프로젝트에서 일반적으로 사용되는 순간 푸시 버튼 구성 요소를 모방한 HTML 구성 요소를 만드는 과정을 안내해 드리겠습니다. SVG, 웹 구성 요소 및 lit-element 와 같은 기술을 사용하고 일부 JavaScript-CSS 속임수를 통해 버튼에 액세스할 수 있도록 하는 방법을 배웁니다.

시작하자!

Arduino에서 HTML로: 푸시 버튼 구성 요소의 필요성

여정을 시작하기 전에 무엇을 만들 것인지, 더 중요하게는 그 이유를 살펴보겠습니다. avr8js라는 JavaScript로 오픈 소스 Arduino 시뮬레이터를 만들고 있습니다. 이 시뮬레이터는 Arduino 코드를 실행할 수 있으며 제작자에게 Arduino 프로그래밍 방법을 가르치는 일련의 자습서 및 과정에서 사용할 것입니다.

시뮬레이터 자체는 프로그램 실행만 처리합니다. 코드 명령을 명령으로 실행하고 프로그램 논리에 따라 내부 상태와 메모리 버퍼를 업데이트합니다. Arduino 프로그램과 상호 작용하려면 시뮬레이터에 입력을 보내거나 출력에 반응할 수 있는 가상 전자 부품을 만들어야 합니다.

시뮬레이터를 단독으로 실행하는 것은 자바스크립트를 독립적으로 실행하는 것과 매우 유사합니다. HTML 요소를 만들고 DOM을 통해 JavaScript 코드에 연결하지 않는 한 사용자와 실제로 상호 작용할 수 없습니다.

따라서 프로세서의 시뮬레이터 외에도 거의 모든 전자 프로젝트에서 볼 수 있는 처음 두 구성 요소인 LED와 푸시 버튼부터 시작하여 물리적 하드웨어를 모방하는 HTML 구성 요소 라이브러리도 작업하고 있습니다.

작동 중인 LED 및 푸시 버튼 요소
작동 중인 LED 및 푸시 버튼 요소

LED는 켜짐과 꺼짐의 두 가지 출력 상태만 있기 때문에 비교적 간단합니다. 무대 뒤에서 SVG 필터를 사용하여 조명 효과를 만듭니다.

푸시버튼이 더 재미있습니다. 또한 두 가지 상태가 있지만 사용자 입력에 반응하고 그에 따라 상태를 업데이트해야 하며, 여기서 곧 보게 될 문제가 발생합니다. 하지만 먼저 만들려는 구성 요소의 요구 사항을 정의해 보겠습니다.

점프 후 더! 아래에서 계속 읽기 ↓

푸시 버튼에 대한 요구 사항 정의

우리의 구성 요소는 12mm 푸시 버튼과 유사합니다. 이 버튼은 전자 제품 스타터 키트에서 매우 일반적이며 아래 사진에서 볼 수 있듯이 여러 색상의 캡과 함께 제공됩니다.

노란색, 빨간색, 파란색 및 녹색 푸시 버튼이 있는 Simon Game
노란색, 빨간색, 파란색 및 녹색 푸시버튼이 있는 Simon Game(큰 미리보기)

동작 측면에서 푸시 버튼에는 눌림 및 해제의 두 가지 상태가 있어야 합니다. 이것은 mousedown/mouseup HTML 이벤트와 유사하지만 푸시버튼이 모바일 장치에서도 사용될 수 있고 마우스가 없는 사용자도 액세스할 수 있는지 확인해야 합니다.

푸시버튼의 상태를 Arduino의 입력으로 사용할 것이기 때문에 "클릭" 또는 "더블 클릭" 이벤트를 지원할 필요가 없습니다. 버튼의 상태에 따라 작동하는 방법을 결정하는 것은 시뮬레이션에서 실행되는 Arduino 프로그램에 달려 있으며 물리적 버튼은 클릭 이벤트를 생성하지 않습니다.

더 자세히 알고 싶다면 2019년 SmashingConf Freiburg에서 Benjamin Gruenbaum과 함께한 "Anatomy of a Click" 강연을 확인하세요.

요구 사항을 요약하기 위해 푸시 버튼은 다음을 수행해야 합니다.

  1. 실제 12mm 푸시 버튼과 유사하게 보입니다.
  2. 눌림과 풀림의 두 가지 별개의 상태가 있으며 시각적으로 식별할 수 있어야 합니다.
  3. 마우스 상호 작용, 모바일 장치를 지원하고 키보드 사용자가 액세스할 수 있어야 합니다.
  4. 다양한 캡 색상(적어도 빨간색, 녹색, 파란색, 노란색, 흰색 및 검정색)을 지원합니다.

요구 사항을 정의했으므로 이제 구현 작업을 시작할 수 있습니다.

승리를 위한 SVG

대부분의 웹 구성 요소는 CSS와 HTML의 조합을 사용하여 구현됩니다. 더 복잡한 그래픽이 필요한 경우 일반적으로 JPG 또는 PNG 형식(또는 향수를 느끼는 경우 GIF)의 래스터 이미지를 사용합니다.

그러나 우리의 경우 SVG 그래픽이라는 또 다른 접근 방식을 사용합니다. SVG는 CSS보다 훨씬 더 쉽게 복잡한 그래픽에 적합합니다. 그러나 걱정하지 마십시오. CSS를 완전히 포기하는 것은 아닙니다. 푸시 버튼의 스타일을 지정하는 데 도움이 되며 결국에는 액세스할 수 있게 하는 데도 도움이 됩니다.

SVG는 래스터 그래픽 이미지와 비교할 때 또 다른 큰 이점이 있습니다. JavaScript에서 조작하기가 매우 쉽고 CSS를 통해 스타일을 지정할 수 있습니다. 즉, 버튼에 대한 단일 이미지를 제공하고 JavaScript를 사용하여 색상 캡을 사용자 정의하고 CSS 스타일을 사용하여 버튼 상태를 나타낼 수 있습니다. 깔끔하지 않나요?

마지막으로 SVG는 텍스트 편집기로 편집할 수 있고 HTML에 직접 포함할 수 있는 XML 문서이므로 재사용 가능한 HTML 구성 요소를 만들기 위한 완벽한 솔루션입니다. 푸시 버튼을 그릴 준비가 되셨습니까?

Inkscape로 푸시버튼 그리기

Inkscape는 SVG 벡터 그래픽을 만드는 데 가장 좋아하는 도구입니다. 무료이며 내장 필터 사전 설정의 대규모 컬렉션, 비트맵 추적 및 경로 이진 연산과 같은 강력한 기능으로 가득 차 있습니다. PCB 아트를 만들기 위해 Inkscape를 사용하기 시작했지만 지난 2년 동안 대부분의 그래픽 편집 작업에 사용하기 시작했습니다.

Inkscape에서 푸시 버튼을 그리는 것은 매우 간단합니다. 다음과 같이 버튼과 버튼을 다른 부품에 연결하는 4개의 금속 리드의 평면도 그림을 그릴 것입니다.

  1. 플라스틱 케이스용 12×12mm 짙은 회색 직사각형, 모서리를 약간 둥글게 처리하여 더 부드럽게 만듭니다.
  2. 금속 덮개를 위한 더 작은 10.5×10.5 밝은 회색 직사각형.
  3. 버튼을 함께 고정하는 핀용으로 각 모서리에 하나씩 4개의 어두운 원이 있습니다.
  4. 가운데에 있는 큰 원, 즉 버튼 캡의 윤곽입니다.
  5. 버튼 캡 상단을 위한 중앙의 작은 원.
  6. 버튼의 금속 리드용 "T"자 모양의 밝은 회색 직사각형 4개.

결과는 약간 확대되었습니다.

손으로 그린 ​​푸시버튼 스케치
손으로 그린 ​​푸시버튼 스케치(큰 미리보기)

마지막 터치로 3D 느낌을 주기 위해 버튼 윤곽에 SVG 그라디언트 마법을 추가합니다.

3D 느낌을 만들기 위한 그라디언트 채우기 추가
3D 느낌을 만들기 위한 그라디언트 채우기 추가(큰 미리보기)

우리는 거기에 갈! 비주얼이 있으니 이제 웹으로 가져와야 합니다.

Inkscape에서 웹 SVG로

위에서 언급했듯이 SVG는 HTML에 포함하는 것이 매우 간단합니다. SVG 파일의 내용을 HTML 문서에 붙여넣고 브라우저에서 열면 화면에 렌더링됩니다. 다음 CodePen 예제에서 작동하는 것을 볼 수 있습니다.

@Uri Shaked의 HTML로 된 펜 SVG 푸시버튼 보기

@Uri Shaked의 HTML로 된 펜 SVG 푸시버튼 보기

다만, 잉크스케이프에서 저장한 SVG 파일은 잉크스케이프 버전, 마지막으로 저장했을 때의 창 위치 등 불필요한 짐이 많다. 많은 경우에 빈 요소, 사용되지 않은 그라디언트 및 필터가 있으며 모두 파일 크기를 부풀려 HTML 내에서 작업하기 어렵게 만듭니다.

운 좋게도 Inkscape는 우리를 위해 대부분의 혼란을 정리할 수 있습니다. 방법은 다음과 같습니다.

  1. 파일 메뉴로 이동하여 문서 정리 를 클릭합니다. (이렇게 하면 문서에서 사용하지 않는 정의가 제거됩니다.)
  2. 다시 파일 로 이동하여 다른 이름으로 저장... 을 클릭합니다. 저장할 때 파일 형식 드롭다운에서 최적화된 SVG ( *.svg )를 선택합니다.
  3. 세 개의 탭이 있는 "최적화된 SVG 출력" 대화 상자가 표시됩니다. "편집기 데이터 유지", "참조되지 않은 정의 유지" 및 "수동으로 생성된 ID 유지..."를 제외한 모든 옵션을 선택합니다.
(큰 미리보기)

이 모든 것을 제거하면 작업하기 더 쉬운 더 작은 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 속성과 큰 원의 그라디언트 시작 색상을 변경하여 버튼의 색상을 쉽게 사용자 정의할 수 있습니다.

다음 목표는 푸시 버튼을 color 속성을 전달하여 사용자 정의할 수 있고 사용자 상호 작용(프레스/릴리스 이벤트)에 반응할 수 있는 재사용 가능한 웹 구성 요소로 바꾸는 것입니다. 웹 구성 요소 생성을 단순화하는 작은 라이브러리인 lit-element 를 사용할 것입니다.

lit-element 는 작은 독립 실행형 구성 요소 라이브러리를 만드는 데 탁월합니다. 웹 구성 요소 표준 위에 구축되어 사용된 프레임워크에 관계없이 이러한 구성 요소를 모든 웹 응용 프로그램에서 사용할 수 있습니다. Angular, React, Vue 또는 Vanilla JS는 모두 우리 구성 요소를 사용할 수 있습니다.

lit-element 에서 구성 요소를 만드는 것은 요소에 대한 HTML 코드를 반환하는 render() 메서드와 함께 클래스 기반 구문을 사용하여 수행됩니다. 익숙하다면 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> `; } }

이 구성 요소는 <hello-world></hello-world> 를 작성하여 HTML 코드의 어느 곳에서나 사용할 수 있습니다.

참고 : 실제로 푸시 버튼에는 코드가 조금 더 필요합니다. @property() 데코랙터(기본값은 red)를 사용하여 색상에 대한 입력 속성을 선언하고 SVG 코드를 render() 메서드를 사용하여 버튼 캡의 색상을 color 속성 값으로 바꿉니다(예제 참조). 중요한 비트는 5행에 있으며 여기서 color 속성을 정의합니다. @property() color = 'red'; 또한 35행(여기서 이 속성을 사용하여 버튼의 캡을 만드는 원의 채우기 색상을 정의함)에서 ${color} 로 작성된 JavaScript 템플릿 리터럴 구문을 사용합니다 .

 <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 변환을 사용하여 svg 요소를 180도 회전할 수 있습니다.

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

transform 속성은 우리가 원을 180도 회전하고 싶다는 것과 원의 중심( cxcy 로 정의)인 점 (6, 6)을 중심으로 회전이 일어나야 한다는 것을 SVG에 알립니다. SVG 변환은 모양 채우기에도 영향을 미치므로 그라디언트도 회전합니다.

우리는 버튼을 눌렀을 때만 그래디언트를 반전시키길 원합니다. 따라서 위에서 했던 것처럼 <circle> 요소에 직접 transform 속성을 추가하는 대신 실제로 이 요소에 대한 CSS 클래스를 설정한 다음 이점을 활용할 것입니다. 약간 다른 구문을 사용하더라도 SVG 속성은 CSS를 통해 설정할 수 있다는 사실:

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

이 두 CSS 규칙은 위에 있는 transform 과 정확히 동일합니다. (6, 6)에서 중심을 중심으로 원을 180도 회전합니다. 버튼을 누를 때만 이러한 규칙이 적용되기를 원하므로 CSS 클래스 이름을 서클에 추가합니다.

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

이제 SVG 요소를 클릭할 때마다 button-contour 에 변형을 적용하기 위해 :active CSS 의사 클래스를 사용할 수 있습니다.

 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가 구성 요소 내의 요소에만 영향을 미치고 응용 프로그램의 다른 부분으로 번지지 않습니다.

이제 버튼을 눌렀을 때 프로그래밍 방식의 동작은 어떻습니까? 버튼의 상태가 변경될 때마다 구성 요소의 사용자가 파악할 수 있도록 이벤트를 발생시키고 싶습니다. 이를 수행하는 한 가지 방법은 SVG 요소에서 mousedown 및 mouseup 이벤트를 수신하고 이에 따라 "버튼 누름"/"버튼 해제" 이벤트를 발생시키는 것입니다. 이것은 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 를 사용하여 재사용 가능한 푸시 버튼 구성 요소를 만들었습니다!

작업을 완료하기 전에 살펴봐야 할 몇 가지 문제가 있습니다. 첫째, 키보드를 사용하는 사람들은 버튼에 접근할 수 없습니다. 또한 모바일에서의 동작은 일관되지 않습니다. 손가락을 누르고 있으면 버튼이 눌린 것처럼 보이지만 손가락을 1초 이상 누르고 있으면 JavaScript 이벤트가 시작되지 않습니다.

키보드 문제부터 시작하겠습니다. svg 요소에 tabindex 속성을 추가하여 버튼을 키보드에서 액세스할 수 있도록 하여 포커스 가능하게 만들 수 있습니다. 내 생각에 더 나은 대안은 표준 <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; }

svg:active 대신 button:active 를 사용하여 버튼 윤곽의 그리드를 반전시키는 선택기를 교체했습니다. 이렇게 하면 사용된 입력 장치에 관계없이 실제 <button> 요소를 누를 때마다 버튼 누름 스타일이 적용됩니다.

버튼의 색상을 포함하는 aria-label 속성을 추가하여 구성 요소를 화면 판독기 친화적으로 만들 수도 있습니다.

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

아직 해결해야 할 일이 한 가지 더 있습니다. "버튼 누름" 및 "버튼 해제" 이벤트입니다. 이상적으로는 위의 CSS에서 했던 것처럼 버튼의 CSS :active 의사 클래스를 기반으로 실행하고 싶습니다. 즉, 버튼이 :active 가 될 때마다 "button-press" 이벤트를 발생시키고, :not(:active) 일 때마다 "button-release" 이벤트를 발생시키고 싶습니다.

그러나 Javascript에서 CSS 의사 클래스를 어떻게 듣습니까?

결과적으로 그렇게 간단하지 않습니다. 나는 JavaScript 이스라엘 커뮤니티에 이 질문을 했고 결국 끝없는 스레드에서 나온 한 가지 아이디어를 찾았습니다 :active 선택기를 사용하여 매우 짧은 CSS 애니메이션을 트리거한 다음 animationstart 를 사용하여 JavaScript에서 이를 들을 수 있습니다. 이벤트.

빠른 CodePen 실험은 이것이 실제로 안정적으로 작동함을 입증했습니다. 이 아이디어의 정교함이 마음에 들었던 만큼 다른 더 간단한 솔루션을 사용하기로 결정했습니다. animationstart 이벤트는 Edge 및 iOS Safari에서 사용할 수 없으며 버튼 상태의 변경을 감지하기 위해 CSS 애니메이션을 트리거하는 것은 올바른 방법처럼 들리지 않습니다.

대신 <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 / downbutton-pressbutton-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에서 요구 사항을 간략하게 설명하고 버튼의 그림을 그리는 것으로 시작하여 lit-element 를 사용하여 SVG 파일을 재사용 가능한 웹 구성 요소로 변환하는 과정을 거쳐 액세스 가능하고 모바일 친화적인지 확인한 후에는 꽤 긴 여정이었습니다. 거의 100줄에 달하는 유쾌한 가상 푸시 버튼 구성 요소의 코드로 끝났습니다.

이 버튼은 내가 만들고 있는 가상 전자 부품의 오픈 소스 라이브러리에 있는 단일 부품일 뿐입니다. 소스 코드를 살펴보거나 사용 가능한 모든 구성 요소를 보고 상호 작용할 수 있는 온라인 스토리북을 확인하세요.

마지막으로 Arduino에 관심이 있다면 현재 작성 중인 Simon for Arduino 과정을 살펴보십시오. 여기에서 푸시 버튼이 작동하는 모습도 볼 수 있습니다.

그럼 다음 시간까지!