copyright © 2026 | creaml4tt3 personal website.

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

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

06 Mar 2025 04:00a year ago
วิธีการทำ 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 ของเรา

1
npm install swapy
2
หรือ
3
<script src="https://unpkg.com/swapy/dist/swapy.min.js"></script>

ขั้นตอนที่ 2: Import Dependencies ในไฟล์ที่เราต้องการใช้งาน

1
// สำหรับใครที่ใช้ Next.js ต้องเพื่ม "use client"; ด้วย เพราะทำงานบน client
2
import { useEffect, useRef } from "react";
3
import { createSwapy } from "swapy";
4
import type {
5
Swapy,
6
BeforeSwapEvent,
7
SwapStartEvent,
8
SwapEvent,
9
SwapEndEvent,
10
} from "swapy";

ขั้นตอนที่ 3: ประกาศตัวแปร useRef สำหรับ container และ ตัวเก็บค่า state

1
const App: React.FC = () => {
2
const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance
3
const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy

ขั้นตอนที่ 4: สร้าง swapy instance ใน useEffect

1
useEffect(() => {
2
// เช็คก่อนว่า container โหลดแล้ว
3
if (container.current) {
4
swapy.current = createSwapy(container.current);
5
6
// event ของ swapy ก่อนจะลาก จำเป็นต้อง return boolean false คือลากไม่ได้ true คือลากได้
7
swapy.current.onBeforeSwap((event: BeforeSwapEvent): boolean => {
8
console.log("swap onBeforeSwap", event);
9
return true;
10
});
11
// event ของ swapy เมื่อเริ่มลาก
12
swapy.current.onSwapStart((event: SwapStartEvent): void => {
13
console.log("swap onSwapStart", event);
14
});
15
// event ของ swapy ขณะลาก
16
swapy.current.onSwap((event: SwapEvent): void => {
17
console.log("swap onSwap", event);
18
});
19
// event ของ swapy เมื่อลากเสร็จ
20
swapy.current.onSwapEnd((event: SwapEndEvent): void => {
21
console.log("swap onSwapEnd", event);
22
});
23
}
24
25
return () => {
26
// เคลียร์ swapy
27
swapy.current?.destroy();
28
};
29
}, []);

ขั้นตอนที่ 5: return div container, slot และ item ด้วย slot คือ data-swapy-slot และ item คือ data-swapy-item

1
return (
2
<div ref={container}>
3
4
{/* 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>
11
12
{/* 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 เต็ม

1
import { useEffect, useRef } from "react";
2
import { createSwapy } from "swapy";
3
import type {
4
Swapy,
5
BeforeSwapEvent,
6
SwapStartEvent,
7
SwapEvent,
8
SwapEndEvent,
9
} from "swapy";
10
11
const App: React.FC = () => {
12
const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance
13
const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy
14
15
useEffect(() => {
16
// เช็คก่อนว่า container โหลดแล้ว
17
if (container.current) {
18
swapy.current = createSwapy(container.current, {}); // สามารถใส่ options เพิ่มเติมได้เช็คได้ที่ docs
19
20
// event ของ swapy ก่อนจะลาก จำเป็นต้อง return boolean false คือลากไม่ได้ true คือลากได้
21
swapy.current.onBeforeSwap((event: BeforeSwapEvent): boolean => {
22
console.log("swap onBeforeSwap", event);
23
return true;
24
});
25
// event ของ swapy เมื่อเริ่มลาก
26
swapy.current.onSwapStart((event: SwapStartEvent): void => {
27
console.log("swap onSwapStart", event);
28
});
29
// event ของ swapy ขณะลาก
30
swapy.current.onSwap((event: SwapEvent): void => {
31
console.log("swap onSwap", event);
32
});
33
// event ของ swapy เมื่อลากเสร็จ
34
swapy.current.onSwapEnd((event: SwapEndEvent): void => {
35
console.log("swap onSwapEnd", event);
36
});
37
}
38
39
return () => {
40
// เคลียร์ swapy
41
swapy.current?.destroy();
42
};
43
}, []);
44
45
return (
46
<div ref={container}>
47
48
{/* 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>
55
56
{/* 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
};
66
67
export default App;

และยังมีอีกวิธีสำหรับคนที่ต้องการความ dynamic ให้เนื้อหาภายในมีการ update ตลอดเวลา

ขั้นตอนที่ 1: Install Swapy เข้า Project ของเรา

1
npm install swapy
2
หรือ
3
<script src="https://unpkg.com/swapy/dist/swapy.min.js"></script>

ขั้นตอนที่ 2: Import Dependencies ในไฟล์ที่เราต้องการใช้งาน

1
// สำหรับใครที่ใช้ Next.js ต้องเพื่ม "use client"; ด้วย เพราะทำงานบน client
2
import { useEffect, useRef, useState, useMemo } from "react";
3
import { createSwapy, utils } from "swapy";
4
import type { Swapy, SlotItemMapArray, SwapEvent } from "swapy";
5
6
type Item = {
7
id: string; // สำหรับเก็บ id ของ item และเป็น key ใน slotItemMap
8
title: string; // สำหรับเก็บชื่อ item
9
};
10
11
const initialItems: Item[] = [
12
{ id: "1", title: "1" },
13
{ id: "2", title: "2" },
14
{ id: "3", title: "3" },
15
];
16
17
let id = 4;

ขั้นตอนที่ 3: ประกาศตัวแปร useRef สำหรับ container และ ตัวเก็บค่า state

1
const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance
2
const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy
3
const [items, setItems] = useState<Item[]>(initialItems); // สำหรับเก็บ items ที่จะใช้ใน swapy
4
const [slotItemMap, setSlotItemMap] = useState<SlotItemMapArray>(
5
utils.initSlotItemMap(items, "id"), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)
6
);
7
const slottedItems = useMemo(
8
() => utils.toSlottedItems(items, "id", slotItemMap), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)
9
[items, slotItemMap],
10
);
11
12
// ทุกครั้งที่ items เปลี่ยนแปลงจะทำการเรียกใช้งาน dynamicSwapy เพื่อให้ swapy ทำการเรียงลำดับ item ใหม่
13
useEffect(
14
() =>
15
utils.dynamicSwapy(
16
swapy.current,
17
items,
18
"id", // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)
19
slotItemMap,
20
setSlotItemMap,
21
),
22
[items],
23
);

ขั้นตอนที่ 4: สร้าง swapy instance ใน useEffect

1
useEffect(() => {
2
// เช็คก่อนว่า container โหลดแล้ว
3
if (container.current) {
4
swapy.current = createSwapy(container.current, {}); // สามารถใส่ options เพิ่มเติมได้เช็คได้ที่ docs
5
6
// event ของ swapy ขณะลาก ให้ทำการเปลี่ยน slotItemMap ตามที่ลาก
7
swapy.current.onSwap((event: SwapEvent): void => {
8
setSlotItemMap(event.newSlotItemMap.asArray);
9
});
10
11
return () => {
12
// เคลียร์ swapy
13
swapy.current?.destroy();
14
};
15
}
16
}, []);

ขั้นตอนที่ 5: return div container, slot และ item ด้วย slot คือ data-swapy-slot และ item คือ data-swapy-item

1
return (
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
<span
13
className="delete"
14
data-swapy-no-drag
15
onClick={() => {
16
setItems(items.filter((i) => i.id !== item.id));
17
}}
18
></span>
19
</div>
20
)}
21
</div>
22
))}
23
</div>
24
25
{/* ปุ่มเพิ่ม item */}
26
<div
27
className="item item--add"
28
onClick={() => {
29
const newItem: Item = { id: `${id}`, title: `${id}` };
30
setItems([...items, newItem]);
31
id++;
32
}}
33
>
34
+
35
</div>
36
</div>
37
);

code เต็ม

1
import { useEffect, useRef, useState, useMemo } from "react";
2
import { createSwapy, utils } from "swapy";
3
import type { Swapy, SlotItemMapArray, SwapEvent } from "swapy";
4
5
type Item = {
6
id: string; // สำหรับเก็บ id ของ item และเป็น key ใน slotItemMap
7
title: string; // สำหรับเก็บชื่อ item
8
};
9
10
const initialItems: Item[] = [
11
{ id: "1", title: "1" },
12
{ id: "2", title: "2" },
13
{ id: "3", title: "3" },
14
];
15
16
let id = 4;
17
18
const AppDynamic: React.FC = () => {
19
const swapy = useRef<Swapy | null>(null); // สำหรับการเก็บ swapy instance
20
const container = useRef<HTMLDivElement | null>(null); // สำหรับการเก็บ container ของ swapy
21
const [items, setItems] = useState<Item[]>(initialItems); // สำหรับเก็บ items ที่จะใช้ใน swapy
22
const [slotItemMap, setSlotItemMap] = useState<SlotItemMapArray>(
23
utils.initSlotItemMap(items, "id"), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)
24
);
25
const slottedItems = useMemo(
26
() => utils.toSlottedItems(items, "id", slotItemMap), // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)
27
[items, slotItemMap],
28
);
29
30
// ทุกครั้งที่ items เปลี่ยนแปลงจะทำการเรียกใช้งาน dynamicSwapy เพื่อให้ swapy ทำการเรียงลำดับ item ใหม่
31
useEffect(
32
() =>
33
utils.dynamicSwapy(
34
swapy.current,
35
items,
36
"id", // "id" ต้องเป็น string เท่านั่นเป็นตัวเลขไม่ได้ (v.1.0.5)
37
slotItemMap,
38
setSlotItemMap,
39
),
40
[items],
41
);
42
43
useEffect(() => {
44
// เช็คก่อนว่า container โหลดแล้ว
45
if (container.current) {
46
swapy.current = createSwapy(container.current, {}); // สามารถใส่ options เพิ่มเติมได้เช็คได้ที่ docs
47
48
// event ของ swapy ขณะลาก ให้ทำการเปลี่ยน slotItemMap ตามที่ลาก
49
swapy.current.onSwap((event: SwapEvent): void => {
50
setSlotItemMap(event.newSlotItemMap.asArray);
51
});
52
53
return () => {
54
// เคลียร์ swapy
55
swapy.current?.destroy();
56
};
57
}
58
}, []);
59
60
return (
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
<span
72
className="delete"
73
data-swapy-no-drag
74
onClick={() => {
75
setItems(items.filter((i) => i.id !== item.id));
76
}}
77
></span>
78
</div>
79
)}
80
</div>
81
))}
82
</div>
83
84
{/* ปุ่มเพิ่ม item */}
85
<div
86
className="item item--add"
87
onClick={() => {
88
const newItem: Item = { id: `${id}`, title: `${id}` };
89
setItems([...items, newItem]);
90
id++;
91
}}
92
>
93
+
94
</div>
95
</div>
96
);
97
};
98
99
export default AppDynamic;
Next.jsReactSwapyDrag to Swapวิธีการทำ Drag to SwapTypescript
Teekayu Poongkawabutr (Pon)
Teekayu Poongkawabutr (Pon)(Creaml4tt3)

Developer ท่านหนึ่งที่ทำงานบ้าง ไม่ทำงานบ้าง ทำงานเป็น dev แต่จบ graphic ชีวิตสับสน website: creaml4tt3.me

Related Blogs

Next.js View Transitions ใช้ยังไง? พร้อมตัวอย่างจริง

Next.js View Transitions ใช้ยังไง? พร้อมตัวอย่างจริง

03 May 2026 15:30a month ago

ทำให้ Next.js ของคุณลื่นเหมือน native app ด้วย View Transitions API พร้อมวิธีเปิดใช้งาน, ทำ global transition และทำ shared element animation สำหรับ blog image แบบ production-ready

Latest Blogs

View all
Next.js SEO Checklist 2026: 12 เทคนิคเพิ่มอันดับ Google สำหรับเว็บไซต์ Next.js

Next.js SEO Checklist 2026: 12 เทคนิคเพิ่มอันดับ Google สำหรับเว็บไซต์ Next.js

01 Jun 2026 17:0013 days ago

รวม 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 แทน Confirm Dialog — และนี่คือ UX ระดับโลก

27 May 2026 18:0018 days ago

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

10 Poka Yoke Patterns ที่ทุกเว็บควรมี เพื่อลด User Error และเพิ่ม UX

10 Poka Yoke Patterns ที่ทุกเว็บควรมี เพื่อลด User Error และเพิ่ม UX

24 May 2026 17:0021 days ago

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