วิธีการทำ Drag to Swap หรือ การลากและวางใน React และ Next.js ด้วย Swapy

เมื่อการออกแบบ UI กลายเป็นเรื่องง่ายด้วย Drag to Swap
เคยรู้สึกไหมว่าการจัดเรียงข้อมูลหรือองค์ประกอบบนเว็บไซต์และแอปพลิเคชันเป็นเรื่องยุ่งยาก? วันนี้เราจะมาพาคุณสำรวจเทคนิคสุดล้ำที่จะเปลี่ยนวิธีคิดในการออกแบบอินเตอร์เฟซของคุณ - นั่นก็คือ "Drag to Swap"
เทคนิคนี้ไม่เพียงแต่ทำให้การใช้งานง่ายขึ้น แต่ยังเพิ่มประสบการณ์ผู้ใช้งาน ช่วยเพิ่ม interaction มากขึ้น ด้วยวันนี้เราจะมาแนะนำ dependency ตัวหนึ่งนั้นก็คือ Swapy สามารถใช้ได้ทั้ง
React
Vue
Svelte
โดยในวันนี้จะมาแสดงวิธีการง่ายๆ สำหรับการทำ Drag to Swap บน React (Next.js) กันครับ
อ่านเพิ่มเติมได้ที่
https://swapy.tahazsh.com/docs/installation/ขั้นตอนที่ 1: Install Swapy เข้า Project ของเรา
1npm install swapy2หรือ3<script src="https://unpkg.com/swapy/dist/swapy.min.js"></script>
ขั้นตอนที่ 2: Import Dependencies ในไฟล์ที่เราต้องการใช้งาน
1// สำหรับใครที่ใช้ Next.js ต้องเพื่ม "use client"; ด้วย เพราะทำงานบน client2import { useEffect, useRef } from "react";3import { createSwapy } from "swapy";4import type {5Swapy,6BeforeSwapEvent,7SwapStartEvent,8SwapEvent,9SwapEndEvent,10} from "swapy";
ขั้นตอนที่ 3: ประกาศตัวแปร useRef สำหรับ container และ ตัวเก็บค่า state
1const App: React.FC = () => {2const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance3const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy
ขั้นตอนที่ 4: สร้าง swapy instance ใน useEffect
1useEffect(() => {2// เช็คก่อนว่า container โหลดแล้ว3if (container.current) {4swapy.current = createSwapy(container.current);56// event ของ swapy ก่อนจะลาก จำเป็นต้อง return boolean false คือลากไม่ได้ true คือลากได้7swapy.current.onBeforeSwap((event: BeforeSwapEvent): boolean => {8console.log("swap onBeforeSwap", event);9return true;10});11// event ของ swapy เมื่อเริ่มลาก12swapy.current.onSwapStart((event: SwapStartEvent): void => {13console.log("swap onSwapStart", event);14});15// event ของ swapy ขณะลาก16swapy.current.onSwap((event: SwapEvent): void => {17console.log("swap onSwap", event);18});19// event ของ swapy เมื่อลากเสร็จ20swapy.current.onSwapEnd((event: SwapEndEvent): void => {21console.log("swap onSwapEnd", event);22});23}2425return () => {26// เคลียร์ swapy27swapy.current?.destroy();28};29}, []);
ขั้นตอนที่ 5: return div container, slot และ item ด้วย slot คือ data-swapy-slot และ item คือ data-swapy-item
1return (2<div ref={container}>34{/* slot a */}5<div data-swapy-slot="a">6{/* item a */}7<div data-swapy-item="a">8<div>A</div>9</div>10</div>1112{/* slot b */}13<div data-swapy-slot="b">14{/* item a */}15<div data-swapy-item="b">16<div>B</div>17</div>18</div>19</div>20);
code เต็ม
1import { useEffect, useRef } from "react";2import { createSwapy } from "swapy";3import type {4Swapy,5BeforeSwapEvent,6SwapStartEvent,7SwapEvent,8SwapEndEvent,9} from "swapy";1011const App: React.FC = () => {12const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance13const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy1415useEffect(() => {16// เช็คก่อนว่า container โหลดแล้ว17if (container.current) {18swapy.current = createSwapy(container.current, {}); // สามารถใส่ options เพิ่มเติมได้เช็คได้ที่ docs1920// event ของ swapy ก่อนจะลาก จำเป็นต้อง return boolean false คือลากไม่ได้ true คือลากได้21swapy.current.onBeforeSwap((event: BeforeSwapEvent): boolean => {22console.log("swap onBeforeSwap", event);23return true;24});25// event ของ swapy เมื่อเริ่มลาก26swapy.current.onSwapStart((event: SwapStartEvent): void => {27console.log("swap onSwapStart", event);28});29// event ของ swapy ขณะลาก30swapy.current.onSwap((event: SwapEvent): void => {31console.log("swap onSwap", event);32});33// event ของ swapy เมื่อลากเสร็จ34swapy.current.onSwapEnd((event: SwapEndEvent): void => {35console.log("swap onSwapEnd", event);36});37}3839return () => {40// เคลียร์ swapy41swapy.current?.destroy();42};43}, []);4445return (46<div ref={container}>4748{/* slot a */}49<div data-swapy-slot="a">50{/* item a */}51<div data-swapy-item="a">52<div>A</div>53</div>54</div>5556{/* slot b */}57<div data-swapy-slot="b">58{/* item a */}59<div data-swapy-item="b">60<div>B</div>61</div>62</div>63</div>64);65};6667export default App;
และยังมีอีกวิธีสำหรับคนที่ต้องการความ dynamic ให้เนื้อหาภายในมีการ update ตลอดเวลา
ขั้นตอนที่ 1: Install Swapy เข้า Project ของเรา
1npm install swapy2หรือ3<script src="https://unpkg.com/swapy/dist/swapy.min.js"></script>
ขั้นตอนที่ 2: Import Dependencies ในไฟล์ที่เราต้องการใช้งาน
1// สำหรับใครที่ใช้ Next.js ต้องเพื่ม "use client"; ด้วย เพราะทำงานบน client2import { useEffect, useRef, useState, useMemo } from "react";3import { createSwapy, utils } from "swapy";4import type { Swapy, SlotItemMapArray, SwapEvent } from "swapy";56type Item = {7id: string; // สำหรับเก็บ id ของ item และเป็น key ใน slotItemMap8title: string; // สำหรับเก็บชื่อ item9};1011const initialItems: Item[] = [12{ id: "1", title: "1" },13{ id: "2", title: "2" },14{ id: "3", title: "3" },15];1617let id = 4;
ขั้นตอนที่ 3: ประกาศตัวแปร useRef สำหรับ container และ ตัวเก็บค่า state
1const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance2const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy3const [items, setItems] = useState<Item[]>(initialItems); // สำหรับเก็บ items ที่จะใช้ใน swapy4const [slotItemMap, setSlotItemMap] = useState<SlotItemMapArray>(5utils.initSlotItemMap(items, "id"), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)6);7const slottedItems = useMemo(8() => utils.toSlottedItems(items, "id", slotItemMap), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)9[items, slotItemMap],10);1112// ทุกครั้งที่ items เปลี่ยนแปลงจะทำการเรียกใช้งาน dynamicSwapy เพื่อให้ swapy ทำการเรียงลำดับ item ใหม่13useEffect(14() =>15utils.dynamicSwapy(16swapy.current,17items,18"id", // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)19slotItemMap,20setSlotItemMap,21),22[items],23);
ขั้นตอนที่ 4: สร้าง swapy instance ใน useEffect
1useEffect(() => {2// เช็คก่อนว่า container โหลดแล้ว3if (container.current) {4swapy.current = createSwapy(container.current, {}); // สามารถใส่ options เพิ่มเติมได้เช็คได้ที่ docs56// event ของ swapy ขณะลาก ให้ทำการเปลี่ยน slotItemMap ตามที่ลาก7swapy.current.onSwap((event: SwapEvent): void => {8setSlotItemMap(event.newSlotItemMap.asArray);9});1011return () => {12// เคลียร์ swapy13swapy.current?.destroy();14};15}16}, []);
ขั้นตอนที่ 5: return div container, slot และ item ด้วย slot คือ data-swapy-slot และ item คือ data-swapy-item
1return (2<div className="container" ref={container}>3<div className="items">4{/* แสดง items ที่สามารถลาก */}5{slottedItems.map(({ slotId, itemId, item }, index) => (6<div className="slot" key={slotId} data-swapy-slot={slotId}>7{item && (8<div className="item" data-swapy-item={itemId} key={itemId}>9<span>{index + 1}</span>10<span>{item.title}</span>11{/* ปุ่มลบ item */}12<span13className="delete"14data-swapy-no-drag15onClick={() => {16setItems(items.filter((i) => i.id !== item.id));17}}18></span>19</div>20)}21</div>22))}23</div>2425{/* ปุ่มเพิ่ม item */}26<div27className="item item--add"28onClick={() => {29const newItem: Item = { id: `${id}`, title: `${id}` };30setItems([...items, newItem]);31id++;32}}33>34+35</div>36</div>37);
code เต็ม
1import { useEffect, useRef, useState, useMemo } from "react";2import { createSwapy, utils } from "swapy";3import type { Swapy, SlotItemMapArray, SwapEvent } from "swapy";45type Item = {6id: string; // สำหรับเก็บ id ของ item และเป็น key ใน slotItemMap7title: string; // สำหรับเก็บชื่อ item8};910const initialItems: Item[] = [11{ id: "1", title: "1" },12{ id: "2", title: "2" },13{ id: "3", title: "3" },14];1516let id = 4;1718const AppDynamic: React.FC = () => {19const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance20const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy21const [items, setItems] = useState<Item[]>(initialItems); // สำหรับเก็บ items ที่จะใช้ใน swapy22const [slotItemMap, setSlotItemMap] = useState<SlotItemMapArray>(23utils.initSlotItemMap(items, "id"), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)24);25const slottedItems = useMemo(26() => utils.toSlottedItems(items, "id", slotItemMap), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)27[items, slotItemMap],28);2930// ทุกครั้งที่ items เปลี่ยนแปลงจะทำการเรียกใช้งาน dynamicSwapy เพื่อให้ swapy ทำการเรียงลำดับ item ใหม่31useEffect(32() =>33utils.dynamicSwapy(34swapy.current,35items,36"id", // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)37slotItemMap,38setSlotItemMap,39),40[items],41);4243useEffect(() => {44// เช็คก่อนว่า container โหลดแล้ว45if (container.current) {46swapy.current = createSwapy(container.current, {}); // สามารถใส่ options เพิ่มเติมได้เช็คได้ที่ docs4748// event ของ swapy ขณะลาก ให้ทำการเปลี่ยน slotItemMap ตามที่ลาก49swapy.current.onSwap((event: SwapEvent): void => {50setSlotItemMap(event.newSlotItemMap.asArray);51});5253return () => {54// เคลียร์ swapy55swapy.current?.destroy();56};57}58}, []);5960return (61<div className="container" ref={container}>62<div className="items">63{/* แสดง items ที่สามารถลาก */}64{slottedItems.map(({ slotId, itemId, item }, index) => (65<div className="slot" key={slotId} data-swapy-slot={slotId}>66{item && (67<div className="item" data-swapy-item={itemId} key={itemId}>68<span>{index + 1}</span>69<span>{item.title}</span>70{/* ปุ่มลบ item */}71<span72className="delete"73data-swapy-no-drag74onClick={() => {75setItems(items.filter((i) => i.id !== item.id));76}}77></span>78</div>79)}80</div>81))}82</div>8384{/* ปุ่มเพิ่ม item */}85<div86className="item item--add"87onClick={() => {88const newItem: Item = { id: `${id}`, title: `${id}` };89setItems([...items, newItem]);90id++;91}}92>93+94</div>95</div>96);97};9899export default AppDynamic;
Related Blogs
Latest Blogs
View all
Next.js SEO Checklist 2026: 12 เทคนิคเพิ่มอันดับ Google สำหรับเว็บไซต์ Next.js
รวม Next.js SEO Checklist 2026 ตั้งแต่ Metadata API, Open Graph, Sitemap, Robots.txt, JSON-LD และ Core Web Vitals เพื่อช่วยให้เว็บไซต์ติดอันดับ Google ได้ดีขึ้น

ทำไม Gmail ถึงใช้ Undo แทน Confirm Dialog — และนี่คือ UX ระดับโลก
ทำไม Gmail ถึงเลือก “Undo Send” แทน Popup ถามยืนยัน? เจาะลึก UX Psychology, Human Error และ Design Pattern ที่ทำให้ Google ลดความน่ารำคาญ แต่ยังป้องกันความผิดพลาดได้

10 Poka Yoke Patterns ที่ทุกเว็บควรมี เพื่อลด User Error และเพิ่ม UX
เรียนรู้ Poka Yoke สำหรับ UX/UI และ Web Design พร้อม 10 Error Prevention Patterns ที่ช่วยลด User Error เพิ่ม Conversion และทำให้เว็บใช้งานง่ายขึ้น
