ภาพรวมพื้นฐานของวิธีสร้างคอมโพเนนต์สวิตช์ที่ปรับเปลี่ยนตามอุปกรณ์และเข้าถึงได้ง่าย
ในโพสต์นี้ ฉันอยากแชร์วิธีคิดวิธีสร้างคอมโพเนนต์สวิตช์ ลองใช้เดโม
หากชอบวิดีโอ นี่คือโพสต์นี้เวอร์ชัน YouTube
ภาพรวม
สวิตช์ทำงานคล้ายกับช่องทำเครื่องหมาย แต่จะแสดงสถานะเปิดและปิดบูลีนอย่างชัดเจน
การสาธิตนี้ใช้ <input type="checkbox" role="switch">
สำหรับฟังก์ชันการทำงานส่วนใหญ่ ซึ่งมีข้อดีที่ไม่จำเป็นต้องใช้ CSS หรือ JavaScript เพื่อให้ทำงานและเข้าถึงได้อย่างสมบูรณ์ การโหลด CSS รองรับภาษาแบบขวาไปซ้าย
แนวตั้ง ภาพเคลื่อนไหว และอื่นๆ การโหลด JavaScript ทำให้สวิตช์
เป็นแบบลากและจับต้องได้
คุณสมบัติที่กำหนดเอง
ตัวแปรต่อไปนี้แสดงส่วนต่างๆ ของสวิตช์และตัวเลือกของแต่ละส่วน ในฐานะคลาสระดับบนสุด .gui-switch
จะมีพร็อพเพอร์ตี้ที่กำหนดเองซึ่งใช้ตลอดทั้งคอมโพเนนต์ย่อย และจุดแรกเข้าสำหรับการปรับแต่งแบบรวมศูนย์
แทร็ก
ความยาว (--track-size
), ระยะห่างจากขอบ และ 2 สี ดังนี้
.gui-switch {
--track-size: calc(var(--thumb-size) * 2);
--track-padding: 2px;
--track-inactive: hsl(80 0% 80%);
--track-active: hsl(80 60% 45%);
--track-color-inactive: var(--track-inactive);
--track-color-active: var(--track-active);
@media (prefers-color-scheme: dark) {
--track-inactive: hsl(80 0% 35%);
--track-active: hsl(80 60% 60%);
}
}
Thumb
ขนาด สีพื้นหลัง และสีไฮไลต์การโต้ตอบ
.gui-switch {
--thumb-size: 2rem;
--thumb: hsl(0 0% 100%);
--thumb-highlight: hsl(0 0% 0% / 25%);
--thumb-color: var(--thumb);
--thumb-color-highlight: var(--thumb-highlight);
@media (prefers-color-scheme: dark) {
--thumb: hsl(0 0% 5%);
--thumb-highlight: hsl(0 0% 100% / 25%);
}
}
การเคลื่อนไหวลดลง
หากต้องการเพิ่มชื่อแทนที่ชัดเจนและลดการกล่าวซ้ำๆ คุณสามารถใส่คำค้นหาสื่อของผู้ใช้ค่ากำหนดการเคลื่อนไหวที่ลดลงลงในพร็อพเพอร์ตี้ที่กำหนดเองโดยใช้ปลั๊กอิน PostCSS โดยอิงตามข้อกำหนดฉบับร่างในคำค้นหาสื่อ 5 ดังนี้
@custom-media --motionOK (prefers-reduced-motion: no-preference);
Markup
ผมเลือกที่จะรวมองค์ประกอบ <input type="checkbox" role="switch">
กับ <label>
ซึ่งรวมความสัมพันธ์กันเพื่อหลีกเลี่ยงความไม่ชัดเจนเกี่ยวกับช่องทำเครื่องหมายและการเชื่อมโยงป้ายกำกับ ขณะที่ให้ผู้ใช้โต้ตอบกับป้ายกำกับเพื่อสลับอินพุตได้
<label for="switch" class="gui-switch">
Label text
<input type="checkbox" role="switch" id="switch">
</label>
<input type="checkbox">
มาพร้อมกับ API และstate เบราว์เซอร์จะจัดการพร็อพเพอร์ตี้ checked
และเหตุการณ์อินพุตเช่น oninput
และ onchanged
เลย์เอาต์
Flexbox, grid และพร็อพเพอร์ตี้ที่กำหนดเองมีความสำคัญต่อการดูแลรักษาสไตล์ของคอมโพเนนต์นี้ โดยจะรวมศูนย์ค่า ตั้งชื่อให้การคำนวณหรือพื้นที่ที่กำกวม และเปิดใช้ API สำหรับพร็อพเพอร์ตี้ที่กำหนดเองขนาดเล็กเพื่อให้ปรับแต่งคอมโพเนนต์ได้ง่าย
.gui-switch
เลย์เอาต์ระดับบนสุดสําหรับสวิตช์คือ Flexbox คลาส .gui-switch
มีพร็อพเพอร์ตี้ที่กำหนดเองแบบส่วนตัวและสาธารณะที่ผู้เผยแพร่โฆษณาย่อยใช้เพื่อคำนวณเลย์เอาต์
.gui-switch {
display: flex;
align-items: center;
gap: 2ch;
justify-content: space-between;
}
การขยายและแก้ไขเลย์เอาต์ของ Flexbox ก็เหมือนกับการเปลี่ยนเลย์เอาต์ของ Flexbox
เช่น หากต้องการใส่ป้ายกำกับไว้ด้านบนหรือด้านล่างสวิตช์ หรือวิธีเปลี่ยน flex-direction
ให้ทำดังนี้
<label for="light-switch" class="gui-switch" style="flex-direction: column">
Default
<input type="checkbox" role="switch" id="light-switch">
</label>
แทร็ก
ช่องทำเครื่องหมายในช่องจัดรูปแบบเป็นแทร็กสวิตช์โดยนำ appearance: checkbox
ปกติของแทร็กออก และใส่ขนาดของตัวเองแทน ดังนี้
.gui-switch > input {
appearance: none;
inline-size: var(--track-size);
block-size: var(--thumb-size);
padding: var(--track-padding);
flex-shrink: 0;
display: grid;
align-items: center;
grid: [track] 1fr / [track] 1fr;
}
แทร็กยังสร้างพื้นที่แทร็กตารางกริดแบบเซลล์เดียวสำหรับนิ้วโป้งเพื่ออ้างสิทธิ์ด้วย
Thumb
รูปแบบ appearance: none
จะนำเครื่องหมายถูกที่แสดงโดยเบราว์เซอร์ออกด้วย คอมโพเนนต์นี้ใช้ pseudo-element และ :checked
pseudo-class ในอินพุตเพื่อแทนที่ตัวบ่งชี้ที่เป็นภาพนี้
นิ้วโป้งคือองค์ประกอบย่อยที่ประกอบเข้ากับ input[type="checkbox"]
และสแต็กที่อยู่ด้านบนของแทร็กแทนที่จะเป็นด้านล่างโดยการอ้างสิทธิ์พื้นที่ตารางกริด track
.gui-switch > input::before {
content: "";
grid-area: track;
inline-size: var(--thumb-size);
block-size: var(--thumb-size);
}
รูปแบบ
คุณสมบัติที่กำหนดเองทำให้มีคอมโพเนนต์สวิตช์ที่ใช้ได้หลากหลายซึ่งปรับให้เข้ากับรูปแบบสี ภาษาที่อ่านจากขวาไปซ้าย และค่ากำหนดการเคลื่อนไหว
รูปแบบการโต้ตอบการสัมผัส
ในอุปกรณ์เคลื่อนที่ เบราว์เซอร์จะเพิ่มฟีเจอร์ไฮไลต์การแตะและการเลือกข้อความลงในป้ายกำกับและอินพุต ซึ่งส่งผลเสียต่อสไตล์และการตอบสนองในการโต้ตอบภาพ
ที่สวิตช์นี้ต้องการ ผมนำเอฟเฟกต์เหล่านั้นออกและเพิ่มสไตล์ cursor: pointer
ของตัวเองได้โดยใช้ CSS เพียงไม่กี่บรรทัด
.gui-switch {
cursor: pointer;
user-select: none;
-webkit-tap-highlight-color: transparent;
}
เราไม่แนะนําให้นําสไตล์เหล่านั้นออกเสมอไป เนื่องจากสไตล์เหล่านี้อาจเป็นความคิดเห็นที่มีคุณค่าต่อการโต้ตอบ อย่าลืมระบุทางเลือกที่กำหนดเองหากคุณนำทางเลือกเหล่านั้นออก
แทร็ก
รูปแบบขององค์ประกอบนี้ส่วนใหญ่เกี่ยวข้องกับรูปร่างและสี ซึ่งเข้าถึงจาก .gui-switch
ระดับบนสุดผ่านการเรียงซ้อน
.gui-switch > input {
appearance: none;
border: none;
outline-offset: 5px;
box-sizing: content-box;
padding: var(--track-padding);
background: var(--track-color-inactive);
inline-size: var(--track-size);
block-size: var(--thumb-size);
border-radius: var(--track-size);
}
ตัวเลือกการปรับแต่งที่หลากหลายสำหรับแทร็กการเปลี่ยนมาจากคุณสมบัติที่กำหนดเอง 4 รายการ เพิ่ม border: none
เนื่องจาก appearance: none
ไม่ได้นำเส้นขอบออกจากช่องทำเครื่องหมายในเบราว์เซอร์ทั้งหมด
Thumb
องค์ประกอบนิ้วโป้งอยู่ทางขวา track
อยู่แล้ว แต่ต้องใช้รูปแบบวงกลม:
.gui-switch > input::before {
background: var(--thumb-color);
border-radius: 50%;
}
การโต้ตอบ
ใช้คุณสมบัติที่กำหนดเองเพื่อเตรียมพร้อมสำหรับการโต้ตอบซึ่งจะแสดงไฮไลต์เมื่อวางเมาส์ และการเปลี่ยนตำแหน่งนิ้วโป้ง นอกจากนี้ ยังมีการตรวจสอบค่ากำหนดของผู้ใช้ก่อนเปลี่ยนสไตล์การไฮไลต์การเคลื่อนไหวหรือการวางเมาส์เหนือ
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
ตำแหน่งนิ้วโป้ง
พร็อพเพอร์ตี้ที่กำหนดเองจะมีกลไกแหล่งที่มาเดียวสำหรับการวางตำแหน่งนิ้วโป้งในแทร็ก ที่เรามีอยู่คือขนาดแทร็กและนิ้วโป้งซึ่งเราจะใช้ในการคำนวณเพื่อให้นิ้วโป้งอยู่ห่างและอยู่ระหว่างรอยทางอย่างเหมาะสม: 0%
กับ 100%
องค์ประกอบ input
เป็นเจ้าของตัวแปรตำแหน่ง --thumb-position
และองค์ประกอบจำลองแบบ Thumb จะใช้องค์ประกอบนี้เป็นตำแหน่ง translateX
ดังนี้
.gui-switch > input {
--thumb-position: 0%;
}
.gui-switch > input::before {
transform: translateX(var(--thumb-position));
}
ตอนนี้เราเปลี่ยน --thumb-position
จาก CSS และ Pseudo-class ที่ระบุไว้ในองค์ประกอบช่องทำเครื่องหมายได้อย่างอิสระ เนื่องจากเราได้กำหนด transition: transform
var(--thumb-transition-duration) ease
ล่วงหน้าในองค์ประกอบแบบมีเงื่อนไข การเปลี่ยนแปลงเหล่านี้จึงอาจเคลื่อนไหวเมื่อมีการเปลี่ยนแปลง
/* positioned at the end of the track: track length - 100% (thumb width) */
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
}
/* positioned in the center of the track: half the track - half the thumb */
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
}
ฉันคิดว่าการจัดการเป็นกลุ่มที่แยกออกมาต่างหากนี้ได้ผลเป็นอย่างดี องค์ประกอบนิ้วโป้งจะคำนึงถึง
รูปแบบเดียว ซึ่งก็คือตำแหน่ง translateX
อินพุตสามารถจัดการความซับซ้อน
และการคำนวณทั้งหมดได้
ประเภทธุรกิจ
การรองรับทำงานด้วยคลาสตัวปรับแต่ง -vertical
ซึ่งจะเพิ่มการหมุนเวียนโดย CSS ที่มีการเปลี่ยนรูปแบบไปยังองค์ประกอบ input
แต่องค์ประกอบที่หมุนได้แบบ 3 มิติไม่ได้เปลี่ยนความสูงโดยรวมของคอมโพเนนต์ จึงอาจทำให้เลย์เอาต์แบบบล็อกหายไป โดยใช้ตัวแปร --track-size
และ --track-padding
คำนวณพื้นที่ขั้นต่ำที่ต้องใช้เพื่อให้ปุ่มแนวตั้งอยู่ในเลย์เอาต์ตามที่คาดไว้
.gui-switch.-vertical {
min-block-size: calc(var(--track-size) + calc(var(--track-padding) * 2));
& > input {
transform: rotate(-90deg);
}
}
(RTL) ขวาไปซ้าย
เพื่อน CSS ชื่อ Elad Schecter และฉันได้สร้างต้นแบบ แบบเลื่อนเมนูด้านข้างโดยใช้การแปลง CSS ที่จัดการภาษาจากขวาไปซ้ายด้วยการพลิกตัวแปรเดียว เราทำเช่นนี้เนื่องจากไม่มีการแปลงคุณสมบัติเชิงตรรกะใน CSS และอาจไม่มีเลย Elad มีความคิดที่ยอดเยี่ยมในการใช้ค่าพร็อพเพอร์ตี้ที่กำหนดเองเพื่อกลับเปอร์เซ็นต์ เพื่อให้จัดการสถานที่แห่งเดียวตามตรรกะที่กำหนดเองของเราเองสำหรับการเปลี่ยนแปลงเชิงตรรกะได้ ผมใช้เทคนิคเดียวกันนี้ในการเปลี่ยนแปลงนี้ และคิดว่าให้ผลที่ดีมาก
.gui-switch {
--isLTR: 1;
&:dir(rtl) {
--isLTR: -1;
}
}
พร็อพเพอร์ตี้ที่กำหนดเองชื่อ --isLTR
มีค่าเป็น 1
ในตอนแรก ซึ่งหมายความว่าจะเป็น true
เนื่องจากเลย์เอาต์ของเราเป็นแบบซ้ายไปขวาโดยค่าเริ่มต้น จากนั้นโดยใช้คลาสเทียมของ CSS :dir()
ค่าจะตั้งเป็น -1
เมื่อคอมโพเนนต์อยู่ภายในเลย์เอาต์แบบขวาไปซ้าย
นำ --isLTR
ไปใช้จริงโดยใช้ภายใน calc()
ของการเปลี่ยนรูปแบบ:
.gui-switch.-vertical > input {
transform: rotate(-90deg);
transform: rotate(calc(90deg * var(--isLTR) * -1));
}
ตอนนี้การหมุนสวิตช์แนวตั้งจะพิจารณาตำแหน่งด้านตรงข้ามที่เลย์เอาต์จากขวาไปซ้าย
การเปลี่ยนรูปแบบ translateX
ในองค์ประกอบเทียมของภาพขนาดย่อต้องอัปเดตเพื่อให้ครอบคลุมข้อกำหนดของด้านตรงข้ามด้วย ดังนี้
.gui-switch > input:checked {
--thumb-position: calc(var(--track-size) - 100%);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
.gui-switch > input:indeterminate {
--thumb-position: calc(
(var(--track-size) / 2) - (var(--thumb-size) / 2)
);
--thumb-position: calc(
((var(--track-size) / 2) - (var(--thumb-size) / 2))
* var(--isLTR)
);
}
แม้ว่าวิธีนี้จะไม่ได้ช่วยตอบสนองความต้องการทั้งหมดที่เกี่ยวข้องกับแนวคิดอย่างการแปลง CSS อย่างสมเหตุสมผล แต่ก็มีหลักการDRY สำหรับกรณีการใช้งานจำนวนมาก
รัฐ
การใช้ input[type="checkbox"]
ในตัวจะไม่สมบูรณ์หากไม่จัดการกับสถานะต่างๆ ที่อาจอยู่ใน: :checked
, :disabled
, :indeterminate
และ :hover
:focus
ตั้งใจปล่อยไว้เพียงลำพัง โดยมีการปรับเปลี่ยนเฉพาะออฟเซ็ตเท่านั้น วงแหวนโฟกัสดูดีบน Firefox และ Safari
เลือกแล้ว
<label for="switch-checked" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-checked" checked="true">
</label>
สถานะนี้แสดงถึงรัฐ on
ในสถานะนี้ พื้นหลัง "track" ของอินพุตตั้งค่าเป็นสีที่ใช้งานได้ และตำแหน่งนิ้วหัวแม่มือตั้งค่าเป็น "จุดสิ้นสุด"
.gui-switch > input:checked {
background: var(--track-color-active);
--thumb-position: calc((var(--track-size) - 100%) * var(--isLTR));
}
ปิดใช้
<label for="switch-disabled" class="gui-switch">
Default
<input type="checkbox" role="switch" id="switch-disabled" disabled="true">
</label>
ปุ่ม :disabled
ไม่เพียงแค่มีลักษณะที่แตกต่างกันเท่านั้น แต่ยังควรทำให้องค์ประกอบเปลี่ยนแปลงไม่ได้อีกด้วย การโต้ตอบที่ไม่เปลี่ยนแปลงจากเบราว์เซอร์สามารถทำได้ฟรี แต่สถานะภาพจะต้องมีรูปแบบเนื่องจากการใช้ appearance: none
.gui-switch > input:disabled {
cursor: not-allowed;
--thumb-color: transparent;
&::before {
cursor: not-allowed;
box-shadow: inset 0 0 0 2px hsl(0 0% 100% / 50%);
@media (prefers-color-scheme: dark) { & {
box-shadow: inset 0 0 0 2px hsl(0 0% 0% / 50%);
}}
}
}
สถานะนี้ค่อนข้างซับซ้อนเนื่องจากต้องใช้ธีมสีเข้มและธีมสว่างโดยมีทั้งสถานะที่ปิดใช้และทำเครื่องหมายแล้ว ผมเลือกสไตล์แบบเรียบง่ายให้กับรัฐเหล่านี้ เพื่อลดภาระในการดูแลรักษาของการผสมสไตล์ต่างๆ
ไม่ระบุ
สถานะที่มักลืมคือ :indeterminate
ซึ่งจะไม่มีการเลือกหรือยกเลิกการเลือกช่องทำเครื่องหมาย สนุกสุดเหวี่ยง เพียงต้องเชิญชวน
และเรียบง่าย โปรดทราบว่าสถานะบูลีนอาจมีความน่าแอบซ่อนอยู่ระหว่างรัฐต่างๆ
การตั้งช่องทำเครื่องหมายให้ไม่ทราบแน่ชัดนั้นเป็นเรื่องยาก เนื่องจากมีเพียง JavaScript เท่านั้นที่ตั้งค่าได้ ดังนี้
<label for="switch-indeterminate" class="gui-switch">
Indeterminate
<input type="checkbox" role="switch" id="switch-indeterminate">
<script>document.getElementById('switch-indeterminate').indeterminate = true</script>
</label>
สำหรับผมแล้ว รัฐนั้นดูเรียบง่ายและน่าดึงดูด เราจึงคิดว่าควรวางตำแหน่งนิ้วโป้งสลับไว้ตรงกลาง:
.gui-switch > input:indeterminate {
--thumb-position: calc(
calc(calc(var(--track-size) / 2) - calc(var(--thumb-size) / 2))
* var(--isLTR)
);
}
โฮเวอร์
การโต้ตอบเมื่อวางเมาส์ควรสนับสนุนภาพสำหรับ UI ที่เชื่อมต่อ และยังแสดงทิศทางไปยัง UI แบบอินเทอร์แอกทีฟด้วย สวิตช์นี้จะไฮไลต์นิ้วโป้งที่มี วงแหวนกึ่งโปร่งใสเมื่อวางเมาส์เหนือป้ายกำกับหรืออินพุต จากนั้นภาพเคลื่อนไหวแบบวางเหนือนี้ จะแสดงทิศทางไปยังองค์ประกอบภาพขนาดย่อที่โต้ตอบได้
เอฟเฟ็กต์ "ไฮไลต์" เสร็จสิ้นแล้วด้วย box-shadow
เมื่อวางเมาส์เหนืออินพุตที่ไม่ปิดใช้ ให้เพิ่มขนาด --highlight-size
หากผู้ใช้ยอมรับการเคลื่อนไหวได้ เราจะเปลี่ยน box-shadow
และเห็นว่าตัวเลขเติบโตขึ้น แต่หากการเคลื่อนไหวไม่ปกติ ไฮไลต์จะปรากฏขึ้นทันที
.gui-switch > input::before {
box-shadow: 0 0 0 var(--highlight-size) var(--thumb-color-highlight);
@media (--motionOK) { & {
transition:
transform var(--thumb-transition-duration) ease,
box-shadow .25s ease;
}}
}
.gui-switch > input:not(:disabled):hover::before {
--highlight-size: .5rem;
}
JavaScript
สำหรับผมแล้ว อินเทอร์เฟซสวิตช์อาจดูแปลกๆ ตอนที่พยายามเลียนแบบอินเทอร์เฟซจริง โดยเฉพาะที่มีวงกลมอยู่ในแทร็ก iOS ทำแบบนี้ได้ทันที เมื่อสลับสวิตช์ คุณสามารถลากไปด้านข้างได้ จึงเป็นที่น่าพอใจมากที่มีตัวเลือกนี้ ในทางกลับกัน องค์ประกอบ UI อาจรู้สึกว่าไม่มีการใช้งานหากพยายามทำท่าทางสัมผัสด้วยการลากแล้วไม่มีอะไรเกิดขึ้น
นิ้วโป้งที่ลากได้
องค์ประกอบเทียมของภาพขนาดย่อได้รับตำแหน่งจาก var(--thumb-position)
ที่มีขอบเขต .gui-switch > input
, JavaScript สามารถระบุค่ารูปแบบแบบอินไลน์ในอินพุตเพื่ออัปเดตตำแหน่งนิ้วหัวแม่มือแบบไดนามิกโดยให้ดูเหมือนว่าเป็นไปตามท่าทางสัมผัสของตัวชี้ เมื่อปล่อยตัวชี้ ให้นำรูปแบบแทรกในบรรทัดออกและตรวจสอบว่าการลากอยู่ใกล้หรือเปิดขึ้นหรือไม่ โดยใช้คุณสมบัติที่กำหนดเอง --thumb-position
ซึ่งเป็นหัวใจสำคัญของโซลูชัน โดยเหตุการณ์ตัวชี้จะติดตามตำแหน่งตัวชี้แบบมีเงื่อนไขเพื่อแก้ไขพร็อพเพอร์ตี้ที่กำหนดเองของ CSS
เนื่องจากคอมโพเนนต์ทำงานได้ 100% แล้วก่อนที่สคริปต์นี้จะปรากฏขึ้น จึงต้องค่อนข้างต้องใช้เวลาค่อนข้างนานเพื่อรักษาลักษณะการทำงานที่มีอยู่ เช่น การคลิกป้ายกำกับเพื่อสลับอินพุต JavaScript ไม่ควรเพิ่มฟีเจอร์โดย ลดค่าฟีเจอร์ที่มีอยู่
touch-action
การลากเป็นท่าทางสัมผัสที่กำหนดเอง ซึ่งทำให้วิธีนี้เป็นตัวเลือกที่ยอดเยี่ยมสำหรับ
สิทธิประโยชน์ของ touch-action
ในกรณีของสวิตช์นี้ สคริปต์ต้องจัดการท่าทางสัมผัสแนวนอนหรือท่าทางสัมผัสแนวตั้งที่บันทึกสำหรับตัวแปรของสวิตช์แนวตั้ง เมื่อใช้ touch-action
เราสามารถบอกเบราว์เซอร์ว่าต้องจัดการท่าทางสัมผัสใดในองค์ประกอบนี้ เพื่อให้สคริปต์จัดการท่าทางสัมผัสได้โดยไม่ต้องมีการแข่งขัน
CSS ต่อไปนี้จะแนะนำเบราว์เซอร์ว่าเมื่อท่าทางสัมผัสของตัวชี้เริ่มต้นจากในแทร็กสวิตช์นี้ ให้จัดการท่าทางสัมผัสในแนวตั้งและไม่ดำเนินการใดๆ กับท่าทางสัมผัสแนวนอน
.gui-switch > input {
touch-action: pan-y;
}
ผลลัพธ์ที่ต้องการคือท่าทางสัมผัสแนวนอนซึ่งจะไม่เลื่อนหรือเลื่อนหน้าเว็บ ตัวชี้สามารถเลื่อนโดยเริ่มจากภายในอินพุตในแนวตั้งและเลื่อนหน้าได้ แต่ตัวชี้แนวนอนมีการจัดการแบบกำหนดเอง
ยูทิลิตีรูปแบบค่าพิกเซล
ในการตั้งค่าและระหว่างการลาก คุณจะต้องจับค่าตัวเลขที่คํานวณแล้วหลากหลายจากองค์ประกอบ ฟังก์ชัน JavaScript ต่อไปนี้จะแสดงผลค่าพิกเซลที่คำนวณโดยพิจารณาจากพร็อพเพอร์ตี้ CSS ซึ่งจะใช้ในสคริปต์การตั้งค่าแบบนี้ getStyle(checkbox, 'padding-left')
const getStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element).getPropertyValue(prop));
}
const getPseudoStyle = (element, prop) => {
return parseInt(window.getComputedStyle(element, ':before').getPropertyValue(prop));
}
export {
getStyle,
getPseudoStyle,
}
สังเกตวิธีที่ window.getComputedStyle()
ยอมรับอาร์กิวเมนต์ที่ 2 ซึ่งเป็นองค์ประกอบเทียมเป้าหมาย JavaScript อ่านค่าจากองค์ประกอบต่างๆ ได้มากมาย แม้กระทั่งจากองค์ประกอบสมมติ
dragging
ช่วงเวลานี้เป็นช่วงหลักของตรรกะการลากและมี 2-3 อย่างที่ควรสังเกตจากเครื่องจัดการเหตุการณ์ของฟังก์ชัน ดังนี้
const dragging = event => {
if (!state.activethumb) return
let {thumbsize, bounds, padding} = switches.get(state.activethumb.parentElement)
let directionality = getStyle(state.activethumb, '--isLTR')
let track = (directionality === -1)
? (state.activethumb.clientWidth * -1) + thumbsize + padding
: 0
let pos = Math.round(event.offsetX - thumbsize / 2)
if (pos < bounds.lower) pos = 0
if (pos > bounds.upper) pos = bounds.upper
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
}
ฮีโร่ของสคริปต์คือ state.activethumb
วงกลมเล็กๆ ที่สคริปต์นี้วางตำแหน่งพร้อมตัวชี้ ออบเจ็กต์ switches
คือ Map()
โดยที่คีย์เป็นของ .gui-switch
และค่าจะเป็นขอบเขตและขนาดที่แคชไว้ซึ่งช่วยให้สคริปต์มีประสิทธิภาพ ระบบจัดการจากขวาไปซ้ายโดยใช้พร็อพเพอร์ตี้ที่กำหนดเองเดียวกันกับที่ CSS คือ --isLTR
และสามารถใช้พร็อพเพอร์ตี้ดังกล่าวเพื่อกลับตรรกะและรองรับ RTL ต่อไปได้ event.offsetX
มีค่าเช่นกัน เนื่องจากมีค่าเดลต้าซึ่งมีประโยชน์สําหรับการวางตำแหน่งนิ้วโป้ง
state.activethumb.style.setProperty('--thumb-position', `${track + pos}px`)
บรรทัดสุดท้ายของ CSS นี้จะกำหนดพร็อพเพอร์ตี้ที่กำหนดเองซึ่งองค์ประกอบภาพขนาดย่อใช้ มิฉะนั้นการกำหนดค่านี้จะเปลี่ยนแปลงเมื่อเวลาผ่านไป แต่เหตุการณ์ตัวชี้ก่อนหน้าได้ตั้งค่า --thumb-transition-duration
เป็น 0s
ชั่วคราว ทำให้การโต้ตอบที่เอื่อยๆ ออกไป
dragEnd
เพื่อให้ผู้ใช้ลากออกนอกสวิตช์ได้ไกลออกไปและปล่อยมือ ต้องมีการลงทะเบียนเหตุการณ์หน้าต่างส่วนกลาง
window.addEventListener('pointerup', event => {
if (!state.activethumb) return
dragEnd(event)
})
ผมคิดว่าการที่ผู้ใช้มีอิสระในการลากเส้นอย่างอิสระและมีอินเทอร์เฟซ ฉลาดพอที่จะรองรับ การรับมือกับการเปลี่ยนแปลงนี้ ไม่ได้ทำอะไรมาก แต่ก็ต้องพิจารณาอย่างรอบคอบในขั้นตอนการพัฒนา
const dragEnd = event => {
if (!state.activethumb) return
state.activethumb.checked = determineChecked()
if (state.activethumb.indeterminate)
state.activethumb.indeterminate = false
state.activethumb.style.removeProperty('--thumb-transition-duration')
state.activethumb.style.removeProperty('--thumb-position')
state.activethumb.removeEventListener('pointermove', dragging)
state.activethumb = null
padRelease()
}
การโต้ตอบกับองค์ประกอบเสร็จสมบูรณ์แล้ว ได้เวลาตั้งค่าพร็อพเพอร์ตี้ที่ตรวจสอบอินพุตและนำเหตุการณ์ท่าทางสัมผัสทั้งหมดออก ช่องทําเครื่องหมายจะเปลี่ยนแปลงด้วย state.activethumb.checked = determineChecked()
determineChecked()
ฟังก์ชันนี้ (เรียกว่า dragEnd
) จะเป็นตัวกำหนดว่ากระแสนิ้วโป้งอยู่ภายในขอบเขตของแทร็กและแสดงผลเป็น "จริง" หากเท่ากับหรือเกินครึ่งของแทร็ก ดังนี้
const determineChecked = () => {
let {bounds} = switches.get(state.activethumb.parentElement)
let curpos =
Math.abs(
parseInt(
state.activethumb.style.getPropertyValue('--thumb-position')))
if (!curpos) {
curpos = state.activethumb.checked
? bounds.lower
: bounds.upper
}
return curpos >= bounds.middle
}
ความคิดเพิ่มเติม
ท่าทางสัมผัสด้วยการลากก่อให้เกิดภาระของโค้ดเล็กน้อยเนื่องจากได้เลือกโครงสร้าง HTML เริ่มต้นไว้ ซึ่งมักจะไปรวมอินพุตไว้ในป้ายกำกับ ป้ายกำกับซึ่งเป็นองค์ประกอบหลัก
จะได้รับการโต้ตอบคลิกหลังจากอินพุต เมื่อเหตุการณ์ dragEnd
สิ้นสุดลง คุณอาจสังเกตเห็นว่า padRelease()
เป็นฟังก์ชันที่ฟังดูแปลกประหลาด
const padRelease = () => {
state.recentlyDragged = true
setTimeout(_ => {
state.recentlyDragged = false
}, 300)
}
วิธีนี้ช่วยทำให้ป้ายกำกับได้รับคลิกในภายหลังโดยจะยกเลิกการเลือก หรือตรวจสอบการโต้ตอบของผู้ใช้
ถ้าฉันทำแบบนี้อีกครั้ง ฉันอาจลองปรับ DOM ด้วย JavaScript ระหว่างการอัปเกรด UX เพื่อสร้างองค์ประกอบที่จัดการการคลิกป้ายกำกับเองและไม่ต่อสู้ด้วยลักษณะการทำงานในตัว
ผมชอบเขียน JavaScript ประเภทนี้น้อยที่สุด ฉันไม่ต้องการจัดการฟองอากาศของเหตุการณ์ตามเงื่อนไข
const preventBubbles = event => {
if (state.recentlyDragged)
event.preventDefault() && event.stopPropagation()
}
บทสรุป
ส่วนประกอบการเปลี่ยนเล็กๆ น้อยๆ นี้กลายเป็นสิ่งที่ได้ผลมากที่สุดในชาเลนจ์ GUI ทั้งหมด จนถึงตอนนี้! ตอนนี้คุณก็รู้แล้วว่าตัวเองทำยังไง คุณจะทำอะไรบ้าง‽ 🙂
มาลองเปลี่ยนแนวทางของเราและเรียนรู้วิธีทั้งหมดเพื่อสร้างเว็บกันเถอะ สร้างเดโม ลิงก์ทวีตฉัน แล้วฉันจะเพิ่มลงในส่วนรีมิกซ์ของชุมชนด้านล่าง
รีมิกซ์ของชุมชน
- @KonstantinRouda ที่มีองค์ประกอบที่กำหนดเอง: การสาธิตและโค้ด
- @jhvanderschee พร้อมปุ่ม: Codepen
แหล่งข้อมูล
ค้นหา .gui-switch
ซอร์สโค้ดใน GitHub