สร้าง Circle Spinner Menu ด้วย React และ Styled Components

Jakapat Boonroj
Nextzy
Published in
3 min readJun 24, 2019

หลังจาก 2 อาทิตย์แรก ของการฝึกงานที่ Nextzy Technologies ตำแหน่ง Front-end Web Developer ก็ได้ทำงานชิ้นแรกเสร็จ และนี่คือ “ส่วนหนึ่ง” ของเว็บ Landing page ที่รับมา เลยอยากเอามาเหล่าให้เพื่อนๆได้อ่านกัน

มาดู Requirement ก่อนเลย

ถ้าอยากเห็น Design ทั้งหมดสามารถไปดูได้ที่

ตอนแรกที่ได้เป็นไฟล์ UI ลองมาเปิดดู

ก็สะดุดกับหน้า Our Product มาก จนรู้สึกว่า

อิหยังวะ!?

ทุกๆทีเราเคยชินกับการหา Library มาทำ แต่ไอนี่ตอนแรกเราไม่รู้จะใช้คีย์เวิร์ดอะไรด้วยซ้ำ เลยลองถามพี่ออน ที่เป็น UX/UI Designer แล้วลิสออกมาได้ประมาณนี้

  1. เมื่อกดที่หัวข้อ Content จะเปลี่ยนตาม และวงกลมจะหมุนให้หัวข้อที่เลือกอยู่ตรงกลาง
  2. ตัวเลขหัวข้อที่ถูกเลือกจะต้องเป็นสีชมพู (สี Rosy Pink ของพี่ออน) ส่วนอันที่ไม่ได้เลือกจะเป็นสีเทา (อันนี้จะเป็นสี Magnesium)
  3. ข้อมูลที่แสดงในหน้านี้จะต้องเพิ่มหรือลบข้อมูลทีหลังได้
  4. เมื่อเปิดหน้านี้จะเริ่มที่หัวข้อที่อยู่ตรงกลางเสมอ(เช่น มี 7 หัวข้อ เริ่มที่ หัวข้อที่ 4)
  5. วงกลมโผล่มาแค่ครึ่งเดียวของหน้าต่าง (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 ไฟล์ แยกกันทำหน้าที่ เพื่อให้เราสามารถจัดการโค้ดได้ง่ายขึ้น

  1. app.jsx — ทุกอย่างจะถูกแสดงอยู่ที่นี่
  2. spinner.jsx — ทำหน้าที่เป็น Spinner Menu
  3. content.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 หมุนเพื่อให้ข้อมูลที่เราเลือกอยู่ตรงกลางขวามือของวงกลมตลอดเวลา

ก่อนที่เราจะใส่ rotate

โดยใช้วิธีส่ง Props จาก <Circle> ไปให้ Styled Components เพื่อกำหนดค่า rotate

แล้วจะรู้ได้ยังไงว่าต้องกำหนดค่า rotate เท่าไร?

คำตอบก็คือ ใช้ distance ที่ได้มาจากการคำนวณข้อมูลแต่ละตัวไงล่ะ

โดยกำหนดค่า transform ใน Styled Components ผ่าน setRotate เพื่อให้ <Circle> หมุนตามที่ผมต้องการ

หลังจากใส่ rotate

เมื่อเอาทั้งหมดมาประกอบรวมกันก็จะได้ออกมาเป็นแบบนี้

Circle Spinner Menu เวอร์ชั่นสมบูรณ์

จริงๆ ยังเหลือการทำให้ Spinner หมุนด้วยการกดปุ่มบน Keyboard และ Scroll Mouse ด้วย แต่ช่วงนี้ยังไม่ค่อยว่าง ไหนจะ โปรเจคต่อไป ไหนจะต้องแก้ Issue ไหนจะ Performance Optimization

รอติดตามใน EP หน้าละกัน

โค้ดทั้งหมดในบทความนี้(เวอร์ชั่นสมบูรณ์) สามารถเข้าไปดู Repo ตามลิ้งค์นี้ได้เลยจ้า

--

--

Jakapat Boonroj
Nextzy
Writer for

KMITL engineering 55 | ITE19 | Front-end Web Developer