2 คะแนน โดย GN⁺ 2024-12-06 | 1 ความคิดเห็น | แชร์ทาง WhatsApp
  • @stanko/dual-range-input สร้างสไลเดอร์ช่วงคู่ด้วยการนำ เนทีฟ HTML range input สองตัวมารวมกัน โดยคงการโต้ตอบและการเข้าถึงพื้นฐานของเบราว์เซอร์ไว้ตามเดิม
  • การทำงานหลักอาศัย JavaScript ราว 50 บรรทัด สำหรับซิงก์ค่าของอินพุตและ CSS จึงเรียบง่ายกว่าวิธีเดิมที่ต้องสร้างการลากและการเข้าถึงขึ้นมาใหม่เอง
  • อินพุตทั้งสองจะอัปเดต min, max และความกว้างให้สอดคล้องกับ จุดกึ่งกลาง ของค่าที่เลือก และเมื่อคลิกบนแทร็ก thumb ที่อยู่ใกล้กว่าจะย้ายไปยังค่านั้น
  • เนื่องจากแทร็กของ range input สั้นกว่าความกว้างจริงของอินพุต จึงต้องบวกความกว้างของ thumb เข้าไปในการคำนวณ และใช้ padding ของ wrapper เพื่อรองรับความกว้างส่วนเกิน
  • ธีมสามารถปรับได้ด้วยตัวแปร CSS และ gradient จึงสามารถ override สีของช่วงที่เลือก ขนาด thumb สีแทร็ก และส่วนอื่น ๆ ได้

สไลเดอร์คู่ที่สร้างด้วยเนทีฟ range input

  • @stanko/dual-range-input ใช้ HTML range input สองตัววางเคียงกันเพื่อทำอินพุตช่วงคู่
  • คำว่า “เนทีฟ” ในที่นี้ไม่ได้หมายถึงคอนโทรลของเบราว์เซอร์เพียงตัวเดียว แต่หมายถึงการใช้ range input สองตัวเพื่อคงการโต้ตอบพื้นฐานของเบราว์เซอร์และ ความสามารถด้านการเข้าถึง ไว้
  • การติดตั้งประกอบด้วย JavaScript ราว 50 บรรทัด สำหรับซิงก์อินพุต และ CSS สำหรับความสมบูรณ์ด้านภาพ
  • จุดเริ่มต้นมาจากความต้องการ UI สำหรับปรับค่าต่ำสุดและค่าสูงสุดของพารามิเตอร์ในเครื่องมือวาดภาพเชิงกำเนิด
  • มีข้อกำหนดอยู่สองข้อ
    • ต้องใช้ HTML range input แบบเนทีฟ
    • เมื่อคลิกที่แทร็ก thumb ที่อยู่ใกล้จุดคลิกมากกว่าจากสองตัวต้องย้ายไปยังค่านั้น

การซิงก์ค่าและการจัดการการคลิก

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

การคำนวณ CSS และการตกแต่งสไตล์

  • แทร็กของ range input สั้นกว่าความกว้างเต็มของอินพุต และเบราว์เซอร์จะเว้นพื้นที่ด้านซ้ายขวาไว้เพื่อไม่ให้ thumb ล้นออกไปด้านนอก
    • ถ้าแบ่งความกว้างอินพุตแบบง่าย ๆ เช่น 25% และ 75% สัดส่วนของแทร็กจริงจะไม่ตรง
    • การทำงานนี้จึงบวก --dri-thumb-width เข้าไปในความกว้างของแต่ละอินพุต และเพิ่ม padding ให้ wrapper เพื่อรองรับความกว้างส่วนเกิน
  • การตกแต่งสไตล์ใช้ความสามารถในการจัดสไตล์ CSS ของ range input ที่เบราว์เซอร์มีให้
    • มีการตกแต่ง track และ thumb และจัดการไม่ให้เกิด border radius ที่ส่วนกึ่งกลางซึ่งแทร็กทั้งสองมาชนกัน
  • ธีมสามารถสร้างได้ด้วยการ override ตัวแปร CSS
    • ตัวแปรหลักได้แก่ --dri-height, --dri-thumb-width, --dri-thumb-color, --dri-track-color, --dri-track-filled-color, --dri-track-border-radius เป็นต้น
  • ช่วงที่เลือกจะถูกระบายด้วย CSS gradient
    • ตำแหน่งของ gradient ระบุด้วยตัวแปร --dri-gradient-position
    • ในโค้ดจะอัปเดตตัวแปรนี้พร้อมกันเมื่อมีการอัปเดตความกว้างของอินพุต
  • แนวทางนี้เลือกใช้ องค์ประกอบแบบเนทีฟ ให้มากที่สุด แทนการสร้างการลากและการเข้าถึงขึ้นใหม่ด้วยไลบรารีแบบคัสตอม

1 ความคิดเห็น

 
GN⁺ 2024-12-06
ความคิดเห็นจาก Hacker News
  • คิดว่าสไลเดอร์แบบนี้ ควรลากพื้นที่ตรงกลางได้เสมอ ด้วย กล่าวคือถ้าจับตรงกลาง ก็ควรให้ตัวจับทั้งสองขยับไปพร้อมกัน
    อ้างอิงว่า Unity จัดการแบบนี้: https://docs.unity3d.com/StaticFiles/ScriptRefImages/EditorG...

    • ในทางกลับกัน ผมคงไม่คิดจะลองคลิกพื้นที่ตรงกลางเลย เพราะในหัวผมมันไม่ได้ดูเป็นวิดเจ็ตใหม่ทั้งหมด แต่เป็นแค่ สไลเดอร์ที่ดัดแปลง
      คำว่า “เสมอ” อาจแรงไปหน่อย
  • ผู้เขียนเองครับ ดีใจที่ขึ้น HN
    เห็นว่าคนเริ่มทิ้ง issue กับ bug report ไว้ที่นี่แล้ว ที่นี่ก็ดึกมากแล้ว เลยกะว่าจะไปดูพรุ่งนี้
    ผมทำมันขึ้นมาเพราะต้องใช้กับงานสร้างภาพเชิงกำเนิด และผมชอบลากสไลเดอร์ดูว่าภาพเปลี่ยนยังไงมากกว่าพิมพ์ตัวเลข
    พยายามคงความ มินิมอล ไว้ให้มากที่สุด

    • ผมชอบโพสต์นี้มาก อ่านเพลินกับกาแฟยามเช้า ทั้งวิธีคำนวณและคำอธิบาย โหมดดีบัก ก็น่าสนุกดี และรู้สึกว่าเป็นการใช้คอมโพเนนต์เนทีฟได้อย่างชาญฉลาด
      ตอนนี้ผมใช้ SwiftUI ซึ่งมันง่ายมากจนแทบง่ายเกินไปที่จะสร้างคอมโพเนนต์เอง แต่ตามที่ผู้เขียนอธิบายไว้ มันไม่ได้เป็นทางเลือกที่ดีที่สุดเสมอไป และคอมโพเนนต์เนทีฟก็มักมีฟังก์ชันค่อนข้างครบ
      ผมลองเปิดบทความนี้ใน Firefox บน iOS ซึ่งเป็นเบราว์เซอร์ที่ค่อนข้างแปลกอยู่เหมือนกัน แล้วมันก็ทำงานได้ดีมาก สร้างมาดีจริงๆ
  • ลองใช้เองแล้วเจอบั๊กทันที ถ้าลากสไลเดอร์ไปที่ 100 - 100 แล้วจะทำให้เป็น 99 - 99 มันทำไม่ได้
    จาก 99 - 100 ก็ไปไม่ได้ และจาก 98 - 99 ก็ไปไม่ได้เช่นกัน วิธีเดียวที่จะไป 99 - 99 ได้คือต้องลากค่าต่ำสุดลงไปให้ต่ำกว่า 98 ก่อน
    พออยู่ที่ 98 - 99 แล้วลดลงเป็น 97 - 99 ตอนนั้นถึงจะไป 99 - 99 ได้
    บั๊กนี้เกิดได้ทุกที่ เพียงแต่ 99 - 99 อยู่ใกล้ปลายสไลเดอร์เลยทำให้ทดสอบซ้ำได้ง่ายที่สุด โดยทั่วไป เมื่ออยู่ในสถานะ X - X ถ้าขยับปลายด้านหนึ่งไป 1 ขั้น จะลากอีกด้านเข้ามาปิดช่องว่างนั้นไม่ได้ ต้องลากอีกด้านออกไปไกลกว่าเดิมก่อนถึงจะปิดระยะได้

    • เจอได้ดีมาก! จริงๆ ผมคิดถึงเคสนี้ไว้แล้ว และมันทำงานได้ใน Chrome กับ Firefox แต่ดูเหมือน Safari จะไม่ยิง focus event ตอนกดเมาส์ลง และจะยิงก็ตอนเริ่มขยับสไลเดอร์
      ขอบคุณที่รายงาน เดี๋ยวผมแก้แน่นอน
    • บน Chromium ใน Linux เดสก์ท็อป 100-100 → 99-100 → 99-99 ใช้งานได้ปกติ
      แต่มีบั๊กด้านภาพอีกอย่าง เริ่มจาก 23-25 แล้วลากเป็น 24-25 ตัวจับด้านขวาที่อยู่ที่ 25 จะขยับไปไม่กี่พิกเซล ถ้าลากตัวจับซ้ายกลับไป 23 ตัวจับขวาก็จะกลับไปด้วย
    • นี่แหละเหตุผลที่ผมชอบทำ เครื่องมือนักพัฒนา
  • เจ๋งมาก! ผมคิดมาตลอดว่า HTML input ควรมี ตัวเลือกช่วงสองค่า เพราะมันสมเหตุสมผลมาก
    เช่นกรณีใช้งานอย่าง “อยากกรองสินค้าให้เหลือเฉพาะที่มีวิดเจ็ตอย่างน้อย X ชิ้น และไม่เกิน Y ชิ้น”
    แต่ผมพบบั๊กที่อยากเน้นใน Firefox ทุกครั้งที่คลิกตัวจับต่ำสุดหรือสูงสุด ค่าจะเปลี่ยนขึ้นหรือลงหนึ่งขั้น
    ถ้าพยายามคลิกตรงกลางของตัวจับพอดี ค่าจะไม่เปลี่ยน แต่ถ้าคลิกเยื้องไปทางซ้ายนิดหน่อย ค่าที่เลือกจะลดลงหนึ่งขั้น และถ้าคลิกเยื้องไปทางขวานิดหน่อยก็จะเพิ่มขึ้น
    พฤติกรรมที่คาดหวังคือค่าควรเปลี่ยนก็ต่อเมื่อมี การกระทำเปลี่ยนค่าอย่างชัดเจน คือกดปุ่มค้างแล้วเลื่อนตัวชี้เมาส์จริงๆ เท่านั้น

    • พูดตรงๆ ผมไม่คิดว่านั่นเป็นบั๊ก ตัวอย่างทั้งหมดตั้งค่าละเอียดพอสมควร และตัวจับก็กินพื้นที่ครอบหลายขั้นของแทร็ก
      Firefox ตีความการคลิกแบบนั้นเหมือนเป็นการคลิกบนแทร็กโดยตรง ไลบรารีนี้ไม่ได้ไปแทรกแซงอะไรตรงนั้นเลย มันเป็นแค่ พฤติกรรมของ Firefox
  • ยอดเยี่ยมที่มัน ใช้งานด้วยคีย์บอร์ดได้ และทำงานถูกต้องด้วย ถึงขั้นกด Shift-Tab ค้างไว้เพื่อย้อนกลับได้
    ทำออกมาได้ดีมาก และถึงจะเป็นรายละเอียดเล็กมาก แต่ก็ขอบคุณที่ทำอินเทอร์แอ็กชันนี้ได้ถูกต้อง

  • ใน ดีไซน์ซิสเต็ม ของบริษัทเรา เด็กฝึกงานคนหนึ่งเคยทำ proof of concept แบบวางเคียงกันลักษณะนี้เหมือนกัน แต่เจอปัญหาพิกเซลของ input ตัวหนึ่งกระโดดไปมาทีละนิดตลอด สุดท้ายเลยทำให้ดีจริงๆ ไม่ได้
    สุดท้ายเด็กฝึกงานอีกคนเลยเสนอทางแก้ด้วยการใช้ input range 2 ตัวซ้อนกัน

  • ทุกครั้งที่ต้องใช้ range input ผมใช้ jQuery UI slider และต้องใส่ทั้ง jQuery กับ jQuery UI ลงในหน้าเพราะสไลเดอร์ตัวเดียว อันนี้ดีมากจริงๆ
    แก้ไข: มีโอกาสจะเตรียม ไฟล์ JS ปกติ ที่ใช้ได้เลยแทน ESM module ไหม?

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

    • ถ้าทำให้เกิดซ้ำได้ อยากให้เปิด GitHub issue ไว้ [1]
      อยากทราบว่าคุณใช้เบราว์เซอร์กับระบบปฏิบัติการอะไร ไลบรารีนี้ใช้ native input จึงไม่ได้ทำอะไรเกี่ยวกับการบล็อกการเลื่อนเลย
      บนโทรศัพท์ iOS ของผม ไม่ว่าจะใส่สไตล์หรือไม่ input[type=range] ทุกตัวก็บล็อกการเลื่อนเหมือนกัน
      [1] https://github.com/Stanko/dual-range-input/issues
  • คำว่า “เนทีฟ” ยังถกเถียงกันได้อยู่ แม้ไลบรารีนี้จะใช้ HTML range input เนทีฟสองตัว จึงยังคงพฤติกรรมแบบเนทีฟและความสามารถด้าน accessibility เอาไว้ แต่สำหรับผมเกณฑ์ชัดเจนมาก
    ถ้าจะให้มันทำงานตามที่ตั้งใจไว้แล้ว ต้องใช้ JavaScript มันก็ไม่ใช่เนทีฟอีกต่อไป

    • เห็นด้วย ปกติก็มักใช้ความหมายแบบนั้นกัน
      แต่ก็เข้าใจว่าผู้เขียนตั้งใจจะสื่ออะไร เพราะแทนที่จะทำก้อน div ที่ขยับด้วย JS เอง เขาเอาสไลเดอร์ปกติมาประกอบกัน เลยใกล้กับวิธีแก้แบบเนทีฟมากกว่า ถ้าแทน JavaScript ด้วย CSS เจ๋งๆ ไม่ได้ นี่ก็น่าจะเป็นวิธีที่ใกล้ที่สุดเท่าที่จะทำได้
      ไม่ว่าจะเรียกเนทีฟหรือไม่ วิดเจ็ตแบบนี้ก็มีประโยชน์มากถ้าใช้งานได้ และในพื้นที่นี้ก็ยังมี implementation อยู่ไม่มาก
    • โดยทั่วไปก็ใช่ แต่ก็ไม่ได้แบ่งขาดกันชัดเจนเสมอไป มีบางกรณีที่ตัวองค์ประกอบเป็นเนทีฟ แต่ ต้องใช้ JavaScript เพื่อเปิดในสถานะแบบ modal
      ก่อนจะมี popover attribute มันเปิดไม่ได้เลยถ้าไม่มี JavaScript
      และยังมี เมธอด native vanilla JavaScript ที่พอจะแยกให้ต่างจากไลบรารีที่ไม่ใช้เครื่องมือตรวจสอบความถูกต้องของฟอร์มจากเบราว์เซอร์ภายในเลยได้เช่นกัน
  • ตอนทำ https://spodj.intermer.net/ ผมกำลังมองหาของแบบนี้อยู่
    สุดท้ายต้องใช้ React แบบงูๆ ปลาๆ เพื่อให้ได้ฟังก์ชันที่ต้องการ เพราะผมไม่ค่อยเก่งงานฟรอนต์เอนด์นัก