สร้าง Circle Spinner Menu ด้วย React และ Styled Components
หลังจาก 2 อาทิตย์แรก ของการฝึกงานที่ Nextzy Technologies ตำแหน่ง Front-end Web Developer ก็ได้ทำงานชิ้นแรกเสร็จ และนี่คือ “ส่วนหนึ่ง” ของเว็บ Landing page ที่รับมา เลยอยากเอามาเหล่าให้เพื่อนๆได้อ่านกัน
มาดู Requirement ก่อนเลย
ถ้าอยากเห็น Design ทั้งหมดสามารถไปดูได้ที่
ตอนแรกที่ได้เป็นไฟล์ UI ลองมาเปิดดู
ก็สะดุดกับหน้า Our Product มาก จนรู้สึกว่า
อิหยังวะ!?
ทุกๆทีเราเคยชินกับการหา Library มาทำ แต่ไอนี่ตอนแรกเราไม่รู้จะใช้คีย์เวิร์ดอะไรด้วยซ้ำ เลยลองถามพี่ออน ที่เป็น UX/UI Designer แล้วลิสออกมาได้ประมาณนี้
- เมื่อกดที่หัวข้อ Content จะเปลี่ยนตาม และวงกลมจะหมุนให้หัวข้อที่เลือกอยู่ตรงกลาง
- ตัวเลขหัวข้อที่ถูกเลือกจะต้องเป็นสีชมพู (สี Rosy Pink ของพี่ออน) ส่วนอันที่ไม่ได้เลือกจะเป็นสีเทา (อันนี้จะเป็นสี Magnesium)
- ข้อมูลที่แสดงในหน้านี้จะต้องเพิ่มหรือลบข้อมูลทีหลังได้
- เมื่อเปิดหน้านี้จะเริ่มที่หัวข้อที่อยู่ตรงกลางเสมอ(เช่น มี 7 หัวข้อ เริ่มที่ หัวข้อที่ 4)
- วงกลมโผล่มาแค่ครึ่งเดียวของหน้าต่าง (Responsive)
โดยงานนี้เป็นโปรเจคที่รับมาจากรุ่นพี่ และที่เขาใช้ React แบบ TypeScript บอกเลยว่าตอนนั้นไม่เคยจับมาก่อน แต่พอมาลองได้เขียนจริงๆ มันก็ไม่ค่อยต่างกับ JavaScript เท่าไหร่ (ก็แค่มีประกาศ Type นิดหน่อย) ในบทความนี้เลยใช้ JavaScript แล้วก็ใช้ Styled Components ส่วนภาพที่ใช้ในโปรเจคนี้จะใช้ ไฟล์ SVG
สร้าง Menu ด้วย SVG
การใช้รูปวงกลมมาแปะแล้วสร้าง <div>
มาล้อมตามเส้นรอบวง อาจจะเป็นความคิดแรกๆของใครหลายๆคน แต่บอกเลยว่า ยากเชี่ยๆ เพราะต้อง Handle ทั้งหน้าจอ Responsive และจำนวนข้อมูลที่สามารถเพิ่มหรือลดได้ การเลือกใช้ SVG Inline จึงเป็นวิธีที่ดีกว่า
โดยภาพวงกลมที่ว่านี้พี่ออนทำมาให้เรียบร้อยแล้ว เป็นไฟล์ SVG แบบนี้
แต่ก่อนอื่นเราต้องมีโค้ดหรือไฟล์รูปวงกลมที่เป็น SVG ก่อน โดยวงกลมจะต้องอยู่ตรงกลางของรูปภาพ และมี Space เว้นระหว่างวงกลมกับขอบรูปพอสำหรับใส่ข้อความรอบๆวงกลม
ถ้าจะให้ดี ผมแนะนำให้สร้างอีกรูปที่ใส่กากบาทที่แกน XY ของวงกลมและหาตำแหน่ง X และ Y ของแต่ละจุดไว้เพื่อใช้ในการคำนวณ โดยเราจะเอารูปนี้มาใช้ก่อนแล้วค่อยเปลี่ยนภาพที่พี่ออนส่งมาให้
Let’s Code!
สร้างโปรเจคและลง Styled Components ก่อนเลย
npx create-react-app menu-spinnernpm install styled-components
หลังจากลงเสร็จเรียบร้อย เราจะได้ src/app.js แต่ผมจะเปลี่ยนเป็น .jsx แทน เพราะอยากใช้ Embedding Expressions
เราจะสร้างเป็น 3 ไฟล์ แยกกันทำหน้าที่ เพื่อให้เราสามารถจัดการโค้ดได้ง่ายขึ้น
app.jsx
— ทุกอย่างจะถูกแสดงอยู่ที่นี่spinner.jsx
— ทำหน้าที่เป็น Spinner Menucontent.jsx
— สำหรับแสดงข้อมูลที่ถูกเลือกใน Spinner Menu
app.jsx
สร้าง <Container>
ครอบ <Spinner>
และ <Content>
และเนื่องจากผมใช้ Styled Components เลยกำหนด CSS ของ Body ด้วย <GlobalStyle>
สร้าง ข้อมูลเพื่อจใช้ทดสอบ โดยตั้งชื่อว่า DataTest
และใช้ useState(…)
เพื่อส่ง select
กับ setSelect
ให้ Child Component
โดย select
คือตำแหน่งของข้อมูลที่ต้องการให้ Spinner แสดงว่ากำลังถูกเลือกอยู่
ซึ่งตำแหน่งเริ่มต้นผมกำหนดเป็นครึ่งนึงของจำนวนข้อมูลโดยคำนวณจากสูตร
Math.round(DataTest.length / 2) - 1
content.jsx
ข้อมูลจะถูกส่งเข้ามาเป็น Props ที่ชื่อว่า data
กับ select
ซึ่งค่าที่ส่งเข้ามาจะถูกนำไปแสดงไว้ใน <h2>
spinner.jsx
จากข้อมูล SVG ก่อนหน้าเราจะเริ่มโค้ด ลองมาตั้งค่าตามความต้องการก่อน
เริ่มจากเตรียมข้อมูลที่จะต้องใช้ในการแสดงผลของ Spinner Menu ก่อน โดยจะมีดังนี้
range
— ระยะห่างระหว่างตัวแรกสุดกับตัวสุดท้าย (กำหนดไว้เป็น 180 องศา)radius
— รัศมีของวงกลม ที่วัดจากรูป SVG (กำหนดไว้เป็น 320px)number
— จำนวนข้อมูลposition
— Array Object สำหรับเก็บข้อมูลตำแหน่งและองศาของแต่ละตัว
จากนั้นเรามาคิดสูตรคำนวณ เพื่อที่จะหาตำแหน่งของข้อมูลแต่ละตัว โดยจะมีตัวแปรสำคัญอยู่ 4 ตัวด้วยกัน
middle
— ศูนย์กลางของรูปภาพangle
— ระยะห่างระหว่างข้อมูลแต่ละตัว (มีค่าเป็นองศา)position
— ตำแหน่ง X, Y และองศาของข้อมูลแต่ละตัวdistance
— องศาของข้อมูลแต่ละตัว (อิงจากจุดเริ่มต้น)
ในการคำนวณหาตำแหน่ง XY ของข้อมูลแต่ละตัวจะใช้สูตรดังนี้
X = middle.x + (Cos(distance) * radius)
Y = middle.y + (Sin(distance) * radius)
เนื่องจากค่า Distance ของเราเป็น Degree เลยต้องสร้าง Function ที่ชื่อว่า toRad
เพื่อแปลง Degree ให้เป็น Radian
หลังจากคำนวณเสร็จ เราก็ได้ Array ที่เก็บตำแหน่ง XY และองศาของข้อมูลแต่ละตัวไว้
ทีนี้เหลือแค่ส่วนของ Return บอกได้เลยว่า
ยาวเชี่ยๆ
เพราะมันคือ Inline SVG ที่เกริ่นไว้ตั้งแต่แรก โดยเราจะสร้าง Node ล้อมรอบวงกลมตามตำแหน่ง XY และ องศาที่คำนวณได้
ตำแหน่ง XY ใช้เพื่อกำหนดตำแหน่งของ Node และองศาใช้เพื่อหมุน Text ตามมุมที่กระทำต่อวงกลม
นอกจากนี้จะต้องสร้าง <rect>
ครอบ Node และ Text เพื่อทำ Click Event ให้กับ Spinner Menu
การ Render ของ svg จากบนลงล่าง หมายความว่า หากเราต้องการให้ Rect สี่เหลี่ยมใส อยู่บนสุด ครอบทั้ง Node และ Text เราจะต้องใส่ไว้ท้ายสุด
ทั้งหมดทั้งมวลก็จะได้ออกมาประมาณนี้
ยังไม่จบ!!
เพราะว่าที่ผ่านมาทั้งหมดคือการกำหนดตำแหน่งและมุมของข้อมูลแต่ละตัวเท่านั้น ยังต้องทำให้ Spinner Menu หมุนเพื่อให้ข้อมูลที่เราเลือกอยู่ตรงกลางขวามือของวงกลมตลอดเวลา
โดยใช้วิธีส่ง Props จาก <Circle>
ไปให้ Styled Components เพื่อกำหนดค่า rotate
แล้วจะรู้ได้ยังไงว่าต้องกำหนดค่า rotate เท่าไร?
คำตอบก็คือ ใช้ distance
ที่ได้มาจากการคำนวณข้อมูลแต่ละตัวไงล่ะ
โดยกำหนดค่า transform
ใน Styled Components ผ่าน setRotate
เพื่อให้ <Circle>
หมุนตามที่ผมต้องการ
เมื่อเอาทั้งหมดมาประกอบรวมกันก็จะได้ออกมาเป็นแบบนี้
จริงๆ ยังเหลือการทำให้ Spinner หมุนด้วยการกดปุ่มบน Keyboard และ Scroll Mouse ด้วย แต่ช่วงนี้ยังไม่ค่อยว่าง ไหนจะ โปรเจคต่อไป ไหนจะต้องแก้ Issue ไหนจะ Performance Optimization
รอติดตามใน EP หน้าละกัน
โค้ดทั้งหมดในบทความนี้(เวอร์ชั่นสมบูรณ์) สามารถเข้าไปดู Repo ตามลิ้งค์นี้ได้เลยจ้า