เนื้อหานี้จะเป็นแนวทางการสร้างลิสรายการซ้ายขวา เพื่อให้เลือก
จับคู่รายการ สามารถนำไปประยุกต์ใช้กับการจับคู่คำกับความหมาย
หรือการจับคู่เสียงกับรูปภาพ เสียงกับข้อความ ข้อความกับรูปภาพ หรือ
อื่นๆ แล้วแต่การนำไปประยุกต์เพิ่มเติม แต่ในตัวอย่างจะเป็นการใช้จับคู่
คำภาษาอังกฤษกับความหมาย อย่างง่าย
รูปแบบการทำงาน
- นำข้อมูลชุดแรกฝั่งซ้าย และชุดที่สองฝั่งขวา มาแยกเป็นคำหรืออาเรย์ข้อมูล
- นำค่าที่แยกมาสุ่มลำดับแล้วจัดเรียงเป็นตัวเลือกฝั่งซ้ายและขวา
- ผู้เล่นเลือกรายการฝั่งซ้าย หรือขวาก่อนก็ได้ ตัวที่เลือกจะเลื่อนไปด้านบน
- ผู้เล่นเลือกอีกฝั่งที่สื่อหรือมีความเกี่ยวข้องกับที่เลือกฝั่งแรกไปแล้ว
- ถ้ายังไม่เลือกทั้งสองฝั่ง ก็สามารถเปลี่ยนตัวเลือกได้
- เมื่อเลือกครบทุกรายการแล้ว โปรแกรมจะทำการตรวจสอบความถูกต้อง ถ้าผ่านก็จะไปข้อต่อไป
- ถ้ายังไม่ผ่านก็ทำข้อเติมจนผ่าน เมื่อทำครบทุกข้อก็จะขึ้นปุ่มให้เล่นใหม่อีกครั้งหรือไม่
สิ่งที่จะได้เรียนรู้จากตัวอย่าง
เราจะได้รู้เกี่ยวกับ การกำหนดการเคลื่อนไหวโดยใช้งาน 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/bootstrap@4.5.0/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/jquery@3.3.1/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>'); });
หวังว่าเนื้อหานี้จะเป็นแนวทางนำไปปรับประยุกต์ใช้งานเพิ่มเติมต่อไป ได้ไม่มากก็น้อย