เนื้อหานี้จะเป็นแนวทางการสร้างลิสรายการซ้ายขวา เพื่อให้เลือก
จับคู่รายการ สามารถนำไปประยุกต์ใช้กับการจับคู่คำกับความหมาย
หรือการจับคู่เสียงกับรูปภาพ เสียงกับข้อความ ข้อความกับรูปภาพ หรือ
อื่นๆ แล้วแต่การนำไปประยุกต์เพิ่มเติม แต่ในตัวอย่างจะเป็นการใช้จับคู่
คำภาษาอังกฤษกับความหมาย อย่างง่าย
รูปแบบการทำงาน
- นำข้อมูลชุดแรกฝั่งซ้าย และชุดที่สองฝั่งขวา มาแยกเป็นคำหรืออาเรย์ข้อมูล
- นำค่าที่แยกมาสุ่มลำดับแล้วจัดเรียงเป็นตัวเลือกฝั่งซ้ายและขวา
- ผู้เล่นเลือกรายการฝั่งซ้าย หรือขวาก่อนก็ได้ ตัวที่เลือกจะเลื่อนไปด้านบน
- ผู้เล่นเลือกอีกฝั่งที่สื่อหรือมีความเกี่ยวข้องกับที่เลือกฝั่งแรกไปแล้ว
- ถ้ายังไม่เลือกทั้งสองฝั่ง ก็สามารถเปลี่ยนตัวเลือกได้
- เมื่อเลือกครบทุกรายการแล้ว โปรแกรมจะทำการตรวจสอบความถูกต้อง ถ้าผ่านก็จะไปข้อต่อไป
- ถ้ายังไม่ผ่านก็ทำข้อเติมจนผ่าน เมื่อทำครบทุกข้อก็จะขึ้นปุ่มให้เล่นใหม่อีกครั้งหรือไม่
สิ่งที่จะได้เรียนรู้จากตัวอย่าง
เราจะได้รู้เกี่ยวกับ การกำหนดการเคลื่อนไหวโดยใช้งาน css property ได้รู้เกี่ยวกับการอ้างอิง element
หรือรายการด้วยเงื่อนไขการระบุลำดับ ได้รู้เกี่ยวกับการโคลนเพื่อสร้าง element ใหม่จากตัวเดิม แล้วนำไป
แทนที่กลับลงไปในตำแหน่งที่ต้องการ
ไฟล์ตัวอย่าง demo.html
ดูการทำงานที่ตัวอย่าง demo ด้านล่าง
<!doctype html>
<html lang="th">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
<title>Document</title>
<link rel="stylesheet" href="https://unpkg.com/[email protected]/dist/css/bootstrap.min.css" >
<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/font-awesome/5.11.2/css/all.min.css" >
<style>
body{
background: #CCCCCC;
}
</style>
</head>
<body>
<style>
div.wrap-zone{
display: flex;
width: 100%;
justify-content: stretch;
}
ul.target-zone,ul.choice-zone{
list-style: none;
width: 50%;
padding: 0;
margin: 0;
display: flex;
flex-direction: column;
margin-bottom: 10px;
justify-content: center;
}
ul.target-zone li,ul.choice-zone li{
display: flex;
min-width: 100%;
height: 100px;
width: auto;
align-content: center;
justify-content: center;
align-items: center;
margin-right: 10px;
cursor: pointer;
line-height: 50px;
transition: 0.5s ease;
margin-bottom: 2px;
}
ul.target-zone li{
background: #FFFFFF;
border: solid 1px #a1a1a1;
}
ul.choice-zone li{
color: #6b0808;
border: solid 1px #d4cdcb;
}
ul.target-zone li.selected,
ul.choice-zone li.selected{
border: solid 2px #ff5722;
}
ul.target-zone li.paired{
border: solid 2px #ff5722;
border-left: none !important;
}
ul.choice-zone li.paired{
border: solid 2px #ff5722;
border-right: none !important;
}
ul.target-zone li.paired.failed,
ul.choice-zone li.paired.failed{
background-color: #ff000073;
border: solid 2px #fd0c0c;
}
ul.target-zone li.paired.passed,
ul.choice-zone li.paired.passed{
background-color: #00ff0373;
border: solid 2px #0cfd0f;
}
ul.choice-zone li:hover{
box-shadow: 0 0.25em 0.25em rgb(0 0 0 / 10%);
}
button.btn-game{
margin: auto;
display: flex;
border-radius:6px;
cursor:pointer;
font-weight:bold;
padding:6px 24px;
text-decoration:none;
display: none;
}
button.btn-game:focus {
position:relative;
outline: none;
top:1px;
}
#restart{
box-shadow:inset 0px 1px 0px 0px #f7c5c0;
background:linear-gradient(to bottom, #fc8d83 5%, #e4685d 100%);
background-color:#ff9800;
border:1px solid #ff5722;
text-shadow:0px 1px 0px #b23e35;
color:#ffffff;
}
#restart:hover{
background:linear-gradient(to bottom, #e4685d 5%, #fc8d83 100%);
background-color:#e4685d;
}
#nextstep{
box-shadow: inset 0px 1px 0px 0px #37a561;
background: linear-gradient(to bottom, #31c372 5%, #44d273 100%);
background-color: #8bc34a;
border: 1px solid #37d05d;
text-shadow: 0px 1px 0px #35b250;
color:#ffffff;
}
#nextstep:hover{
background: linear-gradient(to bottom, #1fe076 5%, #44d273 100%);
background-color:#5de47a;
}
#replay{
box-shadow: inset 0px 1px 0px 0px #378fa5;
background: linear-gradient(to bottom, #31a6c3 5%, #44c9d2 100%);
background-color: #4a93c3;
border: 1px solid #3798d0;
text-shadow: 0px 1px 0px #3595b2;
color:#ffffff;
}
#replay:hover{
background: linear-gradient(to bottom, #31a6c3 5%, #44c9d2 100%);
background-color: #4a93c3;
}
.choice-item{background: #f1d4a8;}
.move-to-1{
transform: translate(0,-100px);
}
</style>
<div class="container mt-5 mx-auto">
<div class="wrap-zone">
<!-- ส่วนสำหรับคำสุ่ม -->
<ul class="choice-zone">
</ul>
<!-- ส่วนสำหรับจัดเรียงเป็นประโยคใหม่ -->
<ul class="target-zone">
</ul>
</div>
<hr>
<!-- ส่วนของปุ่มจัดการ -->
<button type="button" class="btn-game" id="restart">ลองใหม่</button>
<button type="button" class="btn-game" id="nextstep">ข้อต่อไป</button>
<button type="button" class="btn-game" id="replay">เล่นใหม่อีกครั้ง</button>
</div>
<script src="https://unpkg.com/[email protected]/dist/jquery.min.js"></script>
<script type="text/javascript">
jQuery(document).ready(function($) {
var selectSound = new Audio('https://www.ninenik.com/demo/sound/select-sound_2.wav');
var correctSound = new Audio('https://www.ninenik.com/demo/sound/correct-bell_2.wav');
var winSound = new Audio('https://www.ninenik.com/demo/sound/winning-sound_2.wav');
// ฟังก์ชั่นสำหรับสุ่มคำสำหรับเลือก
function shuffle(array) {
var currentIndex = array.length, temporaryValue, randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex -= 1;
temporaryValue = array[currentIndex];
array[currentIndex] = array[randomIndex];
array[randomIndex] = temporaryValue;
}
return array;
};
// ชุดตัวเลือกฟังซ้าย
var ansChoice = [
("Hi I love you").split(" "),
("A B C D E").split(" ")
];
// ชุดตัวเลือกฝั่งขวา
var ansTarget = [
("สวัสดี ฉัน รัก เธอ").split(" "),
("เอ บี ซี ดี อี").split(" ")
];
// ส่วนสำหรับกำหนด element ต่างๆ
var targetZone = $(".target-zone");
var choiceZone = $(".choice-zone");
var targetItem = $(".target-zone li");
var choiceItem = $(".choice-zone li");
var btnRestart = $("#restart");
var btnNextstep = $("#nextstep");
var btnReplay = $("#replay");
// นับลำดับข้อหรือรายการ
var step = 0;
// เก็บ key สำหรับสร้างตัวเลือก
var ansArrKey = [];
var keys = ansChoice[step].keys();
for (let x of keys) {
ansArrKey[x] = x;
}
// เริ่มต้นใช้ค่าแรกแถวแรกก่อน ท
var choiceVal = ansArrKey;
// ค่าเริ่มต้นสำหรับตัวเลือก การจับคู่ และ รายการที่เลือก
var choice = {};
var target = {};
var paired = {};
var item = 0;
var currentItem = 0;
// ตัวกำหนดว่ากำลังเคลื่อนไหวอยู่ หรือไม่ ป้องกันคลิกซ้ำๆ
var animate = false;
// ฟังก์ชั่นเมื่อเริ่มเกม
function startGame(){
// รีเซ็ตค่างเกมแต่ละครั้ง
choice = {}; // เก็บสถานะรายการฝั่งซ้ายที่เลือกแล้ว
target = {}; // เก็บสถานะรายการฝั่งขวาที่เลือกแล้ว
paired = {}; // เก็บสถานะรายการทั้งสองฝั่งที่เลือกจับคู่กันแล้ว
item = 0; // ลำดับรายการที่เลือก
currentItem = 0; // ลำดับรายการที่เลือกที่ยังเป็นค่าปัจจุบัน
let ran_choiceVal = []; // ค่าที่จะเก็บตัวเลือก
// รีเซ็คค่าต่างๆ ให้ว่าง
targetZone.html("");
choiceZone.html("");
ran_choiceVal = $.extend([],choiceVal); // เอาอาเรย์มาเก็บไว้สำหรับสุ่ม
shuffle(ran_choiceVal); // สุ่มอาเรย์สำหรับตัวเลือกฝั่งซ้าย
// วนลูปสร้างรายการตัวเลือกฝั่งซ้าย
$.each(ran_choiceVal,function(i,v){
choiceZone.append('<li data-ans="'+v+'" class="choice-item">'+ansChoice[step][v]+'</li>');
});
shuffle(ran_choiceVal); // สุ่มอาเรย์สำหรับตัวเลือกฝั่งขวา
// วนลูปสร้างรายการตัวเลือกฝั่งขวา
$.each(ran_choiceVal,function(i,v){
targetZone.prepend('<li data-ans="'+v+'" class="target-item">'+ansTarget[step][v]+'</li>');
});
btnRestart.hide();
}
// ฟังก์ชั่นเรียงข้อเดิมใหม่
function restartGame(){
startGame();
}
// ฟังก์ชั่นไปข้อหรือรายการถัดไป
function nextstep(){
step++; // ไปข้อต่อไป
if(step==ansChoice.length){ // ทำครบทุกข้อแล้ว
btnReplay.css("display","flex");
step=-1;
targetZone.html("");
choiceZone.html("");
}else{ // ยังไม่ครบ
ansArrKey = [];
keys = ansChoice[step].keys();
for (let x of keys) {
ansArrKey[x] = x;
}
// เริ่มต้นใช้ค่าแรกแถวแรกก่อน ที่ key เท่ากับ 0
choiceVal = ansArrKey;
startGame();
}
}
// เริ่มเกิมเมื่อโหลด
startGame();
// ปุ่มเรียกใช้ฟังก์ชั่นทำใหม่ในข้อนั้นๆ
btnRestart.on("click",function(){
restartGame();
});
// ปุ่มเรียกใช้ฟังก์ชั่นไปข้อต่อไป
btnNextstep.on("click",function(){
nextstep();
});
// ปุ่มเรียกใช้ฟังก์ชั่น เริ่มเกิมใหม่
btnReplay.on("click",function(){
btnReplay.hide();
nextstep();
});
// กำหนดการทำงานเมื่อเลือกตัวเลือกฝั่งซ้าย
$(document.body).on("click",".choice-zone li:not(.paired)",function(){
if(animate){ // ถ้ายังยังมีการทำงานการเคลื่อนไหวอยู่
return false; // ยังไม่ต้องทำคำสั่งใดๆ หลังจากบรรทัดนี้
}
animate = true; // กำหนดสถานะการเคลื่อนไหวกำลังทำงาน
choice[item] = true; // สถานะมีการเลือกรายการฝั่งซ้ายแล้ว
currentItem = (currentItem!=item)?item:currentItem; // อัพเดทให้เป็นค่าปัจจุบัน
if(target[item]){ // ฝั่งขวาตำแหน่งเดียวกันถูกเลือกแล้ว
choice[item] = true; // ฝั่งซ้ายถูกเลือกแล้ว
paired[item] = true; // จับคู่กันแล้ว
currentItem = item; // เก็บค่าลำดับที่ถูกเลือก
item++; // เพิ่มค่าลำดับที่ถูกเลือก
}
// เก็บค่าที่จะใช้กำหนดตำแหน่งที่จะเคลื่อนไหว คำนวณอ้างอิงความสูงของกล่อง
let n_order = ($(this).index()-currentItem)*100;
let new_li = $(this).clone(); // โคลนรายการที่เลือก
let old_li = $(this); // รายการที่เลือกตัวเดิม
// ทำการเคลื่อนไหว ไปยังตำแหน่งที่กำหนด
$(this).css({
transform: "translate(0,-"+n_order+"px)"
});
// เมื่อเคลื่อนไปตำแหน่งตามช่วงเวลาที่กำหนดใน css แล้วก็ให้อัพเดทรายการ
setTimeout(function(){
old_li.remove(); // ลบตัวเก่าที่เลือกออก
// เอา css class ชื่อ selected ของรายการที่ยังไม่จับคู่ออกทั้งหมด
$(".choice-zone li:not(.paired)").removeClass("selected");
new_li.addClass("selected"); // ใส่ css class ชื่อ selected ไป ตัวที่โคลน
if(currentItem==0){ // ถ้าเป็นตำแหน่งแรก
$(".choice-zone").prepend(new_li); // เพิ่มไปด้านหน้าสุด
}else{ // ถ้าเป็นตำแหน่งอื่นๆ
// เพิ่มต่อจากตัวที่เลือกล่าสุด
$(".choice-zone li.selected:eq("+(currentItem-1)+")").after(new_li);
}
if(paired[currentItem]){ // ถ้ามีการเลือกทั้งสองฝั่งแ้ว เพิ่ม css class ทั้งสองฝั่ง
$(".choice-zone li.selected:eq("+(currentItem)+")").addClass("paired");
$(".target-zone li.selected:eq("+(currentItem)+")").addClass("paired");
}
if(paired[choiceVal.length-1]){// ถ้าเลือกจับคู่ครบทุกตัวแล้ว
checkans(); // ตรวจคำอบ
}
animate = false; // กำหนดสถานะการเคลื่อนไหวหยุดทำงานแล้ว
},500);
});
// กำหนดการทำงานเมื่อเลือกตัวเลือกฝั่งขวา
$(document.body).on("click",".target-zone li:not(.paired)",function(){
if(animate){ // ถ้ายังยังมีการทำงานการเคลื่อนไหวอยู่
return false; // ยังไม่ต้องทำคำสั่งใดๆ หลังจากบรรทัดนี้
}
animate = true; // กำหนดสถานะการเคลื่อนไหวกำลังทำงาน
target[item] = true; // สถานะมีการเลือกรายการฝั่งขวาแล้ว
currentItem = (currentItem!=item)?item:currentItem; // อัพเดทให้เป็นค่าปัจจุบัน
if(choice[item]){ // ฝั่งซ้ายตำแหน่งเดียวกันถูกเลือกแล้ว
target[item] = true; // ฝั่งขวาถูกเลือกแล้ว
paired[item] = true; // จับคู่กันแล้ว
currentItem = item; // เก็บค่าลำดับที่ถูกเลือก
item++; // เพิ่มค่าลำดับที่ถูกเลือก
}
// เก็บค่าที่จะใช้กำหนดตำแหน่งที่จะเคลื่อนไหว คำนวณอ้างอิงความสูงของกล่อง
let n_order = ($(this).index()-currentItem)*100;
let new_li = $(this).clone(); // โคลนรายการที่เลือก
let old_li = $(this); // รายการที่เลือกตัวเดิม
// ทำการเคลื่อนไหว ไปยังตำแหน่งที่กำหนด
$(this).css({
transform: "translate(0,-"+n_order+"px)"
});
// เมื่อเคลื่อนไปตำแหน่งตามช่วงเวลาที่กำหนดใน css แล้วก็ให้อัพเดทรายการ
setTimeout(function(){
old_li.remove(); // ลบตัวเก่าที่เลือกออก
// เอา css class ชื่อ selected ของรายการที่ยังไม่จับคู่ออกทั้งหมด
$(".target-zone li:not(.paired)").removeClass("selected");
new_li.addClass("selected"); // ใส่ css class ชื่อ selected ไป ตัวที่โคลน
if(currentItem==0){ // ถ้าเป็นตำแหน่งแรก
$(".target-zone").prepend(new_li); // เพิ่มไปด้านหน้าสุด
}else{ // ถ้าเป็นตำแหน่งอื่นๆ
// เพิ่มต่อจากตัวที่เลือกล่าสุด
$(".target-zone li.selected:eq("+(currentItem-1)+")").after(new_li);
}
if(paired[currentItem]){// ถ้ามีการเลือกทั้งสองฝั่งแ้ว เพิ่ม css class ทั้งสองฝั่ง
$(".choice-zone li.selected:eq("+(currentItem)+")").addClass("paired");
$(".target-zone li.selected:eq("+(currentItem)+")").addClass("paired");
}
if(paired[choiceVal.length-1]){// ถ้าเลือกจับคู่ครบทุกตัวแล้ว
checkans(); // ตรวจคำอบ
}
animate = false; // กำหนดสถานะการเคลื่อนไหวหยุดทำงานแล้ว
},500);
});
// ตรวจคำตอบ
function checkans(){
// เปลี่ยน css property การกำหนดเวลาเคลื่อนไหวเป็น 0 ให้ตอนเฉยแสดงได้เร็วขึ้น
$(".choice-zone li,.target-zone li").css("transition","0s");
// วนลูปตรวจสอบค่าของแต่ละรายการ เทียบกันฝั่งซ้าย กับฝั่งขวา
$(".target-zone li").each(function(i,v){
let classCheck = "passed"; // ข้อที่ถูก จะใช้ css class นี้กำหนด
// ถ้าคำตอยฝั่งซ้าย กับฝั่งขวา ไม่เท่ากัน
if(!($(v).data("ans")==$(".choice-zone li:eq("+i+")").data("ans"))){
classCheck = "failed"; // จะใช้ css class นี้กำหนด
}
// เพิ่มสถานะถูกผิด ทั้งฝั่งซ้ายและฝั่งขวา
$(v).addClass(classCheck);
$(".choice-zone li:eq("+i+")").addClass(classCheck);
});
// แสดงผลซัก 1.5 วินาที
setTimeout(function(){
// ยังจับคู่ไม่ถูก ก็เริ่มเกมใหม่
if($("li").hasClass("failed")){
restartGame();
}else{ // จับคู่และผ่านข้อนี้แล้ว
nextstep(); // ทำข้อต่อไป
}
},1500);
}
});
</script>
<script>
$(function(){
});
</script>
</body>
</html>
คำอธิบายแสดงในโค้ด
อธิบายการทำงานเพิ่มเติมบางส่วน
เกี่ยวกับการกำหนดข้อตัวเลือก
// ชุดตัวเลือกฟังซ้าย
var ansChoice = [
("Hi I love you").split(" "),
("A B C D E").split(" ")
];
// ชุดตัวเลือกฝั่งขวา
var ansTarget = [
("สวัสดี ฉัน รัก เธอ").split(" "),
("เอ บี ซี ดี อี").split(" ")
];
ตัวอย่างฝั่งซ้ายและขวา เราจะใช้ชุดข้อมูล array สองชุด ที่ต้องวางให้สอดคล้องกัน ในตัวอย่างเรามีแค่
2 ข้อ สามารถใช้ในรูปแบบ array ได้โดยตรง แต่ในตัวอย่างใช้ฟังก์ชั่นแยกเป็น array อีกที
var ansChoice = [
["Hi","I","love","you"],
["A","B","C","D","E"]
];
เกี่ยวกับคำตอบที่ใช้สำหรับตรวจสอบ เราจะใช้ key ของ array ซึ่งจะเริ่มค่าจาก 0
ไปจนถึงจำนวนของตัวเลือกแต่ละ ลบด้วย 1 เช่น ข้อแรกมี 4 ตัว ก็จะมีค่า key เป็น 0 1 2 และ 3
ส่วนข้อ 2 มี 5 ตัว ก็จะมีค่า key เป็น 0 1 2 3 และ 4
เลข key จะถูกสุ่มลำดับแล้วไปเรียงไว้ในลิสรายการที่เราเลือกฝั่งซ้ายชุด ฝั่งขวาชุด
เมื่อเราเลือกรายการ ก็เหมือนเรากำลังจับคู่ค่า key ถ้าเราจับคู่ถูก ก็จะผ่านข้อนั้นๆ ไปได้
// เก็บ key สำหรับสร้างตัวเลือก
var ansArrKey = [];
var keys = ansChoice[step].keys();
for (let x of keys) {
ansArrKey[x] = x;
}
เวลาในการเคลื่อนไหว เรากำหนดที่ 0.5 วินาที หรือ 500 มิลลิวินาที
ดังนั้นค่านี้ต้องสัมพันธ์กับการกำหนดใน javascript ส่วนของฟังก์ชั่น setTimeout()
ul.target-zone li,ul.choice-zone li{
transition: 0.5s ease;
}
สามารถนำไปประยุกต์เพิ่มเติมได้ เช่น เปลี่ยนจากข้อความเป็นรูปภาพ หรือเป็นเสียง ตัวอย่าง
ฝั่งซ้ายเป็นข้อความ ที่เป็นความหมายของภาพฝั่งขวา แบบนี้เป็นต้น
// ชุดตัวเลือกฟังซ้าย
var ansChoice = [
("ก ข ค ง").split(" ")
];
// ชุดตัวเลือกฝั่งขวา
var ansTarget = [
("รูปไก่.jpg รูปไข่.jpg รูปควาย.jpg รูปงู.jpg ").split(" ")
];
ตอนวนลูปแสดงตัวเลือกก็กำหนดเป็นแท็ก img หรือจะใช้เป็น css style background ก็ได้
// วนลูปสร้างรายการตัวเลือกฝั่งขวา
$.each(ran_choiceVal,function(i,v){
targetZone.prepend('<li data-ans="'+v+'" class="target-item"><img src="'+ansTarget[step][v]+'"/></li>');
});
หวังว่าเนื้อหานี้จะเป็นแนวทางนำไปปรับประยุกต์ใช้งานเพิ่มเติมต่อไป ได้ไม่มากก็น้อย