最近寫了一個前端遊戲的專案,過程中踩了滿多雷,花了滿多時間,為了不想以後重複造輪子,趁目前記憶猶新紀錄一下整個過程,提供給未來遇到相似問題的自己參考。
按鍵觸發音效
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| // 播放按鈕音效的函式 function playSoundEffect(){ var playSoundCorrect = new Audio("./public/game1/click.mp3"); playSoundCorrect.play(); } // 函式持續執行 $(function(){ // 按下元素1的按鈕 $(元素1).on("click",function(){ playSoundEffect(); }) ... ... });
|
網頁播放音樂
html
1 2 3 4 5 6
| <!-- 加音樂 --> <body> <audio id="myAudio" autoplay="true" loop> <source src="./路徑/檔名.mp3" type="audio/mpeg"> </audio> </body>
|
需要從google網頁的設定去調整播放的控制選項
解決新版google chrome 影片或聲音無法自動播放的問題 (Fix google chrome Autoplay policy)
用D3.js讀檔
html
1 2
| <!-- d3 --> <script src="https://d3js.org/d3.v6.min.js"></script>
|
js
1 2 3 4 5 6
| // 用D3載入csv ques_list = [] d3.csv(file, function(data){ ques_list.push(data); //用table的方式在console呈現json return ques_list; });
|
禁止在網頁上按右鍵、鍵盤…
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| // 禁止進行任何動作 $(document).ready(function() { // 禁止按右鍵 $(document).get(0).oncontextmenu = function() { return false; }; // 禁止按ctrl/alt/shift $(document).get(0).onkeydown = function(){ if ( event.ctrlKey ){ return false; } if ( event.altKey ){ return false; } if ( event.shiftKey ){ return false; } } // 禁止在網頁選取 $(document).get(0).onselectstart = function(){ return false; } });
|
自定義動畫keyframe
參考網路上的插件 jquery-keyframes
遇到的問題:
有一個轉盤,背後有很多題目,每一題對應到一個角度。想客製化轉盤旋轉停止的角度,也就是第五題就停止在第五題對應的角度。
解決方法:
都先轉N圈,最後一圈才決定要轉幾度(depend on 第幾題, i.e. iEnd),rotate('+(360*N+iEnd*45)+'deg)'
,整體動畫播放才會流暢
html
1 2
| <!-- jQuery.Keyframes --> <script type="text/javascript" src='./public/js/jQuery.Keyframes-master/dist/jquery.keyframes.min.js'></script>
|
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| // 判斷是否支援 var supportedFlag = $.keyframe.isSupported();
// 定義一個叫做go的動畫 $.keyframe.define([{ name: 'go', '0%': {'transform': 'translate(-50%,-50%) rotate(0deg)'}, '100%': {'transform': 'translate(-50%,-50%) rotate('+(1440+iEnd*45)+'deg)'} // 都先轉4圈,最後一圈才決定要轉幾度(depend on 第幾題, i.e. iEnd) }]);
// 在加上go這個class的元素上播放go動畫 $('元素.go').playKeyframe({ name: 'go', duration: "7s", // 動畫執行時間 timingFunction: 'ease-out', // 快到慢 iterationCount: '1', // 只執行一次動畫 });
|
設定倒數計時器
js
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| stop_time = false; // 初期設定尚未停止 sec = 60; // 設定倒數時間60秒 var setint; // 設定倒數計時器
clearInterval(setint); // 清空倒數計時器,否則會一直倒數
// 設定每8000毫秒(8秒)的倒數計時器 setint = setInterval( function(){ // 預設stop_time=true -> 直接return if(stop_time){ return } // 當倒數結束 if(sec==0){ ... } }, 8000);
|
jquery-mobile 跳頁
下載 jquery.mobile-1.4.5.min.css
html
1 2 3 4 5 6 7 8 9 10 11 12
| <head> <link rel="stylesheet" href="./public/jquery.mobile-1.4.5/jquery.mobile-1.4.5.min.css"> </head>
<script src='https://code.jquery.com/jquery-1.11.1.min.js'></script> <script src="https://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<body> <div data-role="page" id="page1"></div> // 第一頁 <div data-role="page" id="page2"></div> // 第二頁 ... </body>
|
js
1 2
| $.mobile.changePage('#page1',{allowSamePageTransition:true,transition:"slidedown"}); // 跳回第一頁 ...
|
bootstrap modal 跳視窗
1 2 3
| // 避免點擊外部空白而消失modal(重要!!) $("#myModal").modal({backdrop:'static',keyboard:false}); $('#myModal').modal('show')
|
隨機產生數字
1 2 3 4
| // 隨機產生數字 function randomNum(min, max) { return Math.floor(Math.random() * ((max - min)+1) + min); }
|
隨機sample出n個數量的陣列
1 2 3 4 5 6 7 8 9 10 11
| function generateRandomItemsArray(n, originalArray) { let res = []; let clonedArray = [...originalArray]; // ... operator -> 把迭代物件展開 if(n > clonedArray.length) n=clonedArray.length; for(let i=1; i<=n; i++) { const randomIndex = Math.floor(Math.random()*clonedArray.length); // 1~n res.push(clonedArray[randomIndex]); clonedArray.splice(randomIndex, 1); //把cloneArray隨機抽到的index丟進res,接著刪除cloneArray隨機抽到的那個元素 } return res; }
|
Drag-Drop事件
html
1 2 3 4 5
| <!-- drag的物件 --> <div class='draggable'></div>
<!-- drop的物件 --> <div class='droppable'></div>
|
js
定義 dragStart
, dragEnter
, dragOver
, dragLeave
四個觸發的函式
重點:
event.dataTransfer.setData("text", 設定drag傳送的資料)
: 可以設定drag元素要丟給drop物件的資訊
event.dataTransfer.getData("text")
: drop物件抓取drag物件傳送的資訊
dragStart
: 觸發事件(event)是drag元素
dragEnter
, dragOver
, dragLeave
: 觸發事件(event)是drop元素
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| // 被拖曳的物件觸發 function dragStart(event) { // 設定event傳送id給text event.dataTransfer.setData("text", event.target.id); // or "text/plain" }
// 滑鼠拖曳狀態下,滑鼠進入drop物件,由drop物件觸發 function dragEnter(event) { if(event.target.classList && event.target.classList.contains("droppable") && !event.target.classList.contains("dropped")) { event.target.classList.add("droppable-hover"); } } // 滑鼠拖曳狀態下,滑鼠在drop物件上,由drop物件觸發 function dragOver(event) { if(event.target.classList && event.target.classList.contains("droppable") && !event.target.classList.contains("dropped")) { event.preventDefault(); } } // 滑鼠拖曳狀態下,滑鼠離開drop物件,由drop物件觸發 function dragLeave(event) { if(event.target.classList && event.target.classList.contains("droppable") && !event.target.classList.contains("dropped")) { event.target.classList.remove("droppable-hover"); } }
|
定義 drop
函式,這個函式同時考慮了電腦滑鼠操作以及平板手機操作
重點:
- 區分觸發事件是mouse還是touch: 用
event.type
去區分,mouse是drop
,touch是touchend
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| function drop(event) { event.preventDefault();
// 電腦 if(event.type=="drop"){ event.target.classList.remove("droppable-hover"); // 移除hover的class var draggableElementBrand = event.dataTransfer.getData("text"); // 用text接收drag夾帶的id var droppableElementBrand = event.target.getAttribute("data-brand"); // 獲取drop下來的物件的屬性,作為判斷是否放對的依據 } // 觸控板 else if(event.type=="touchend"){ $(".droppable-hover").removeClass("droppable-hover") // 每次移動都把有包含hover這個動畫的元素的class去除 event.target.style.zIndex = '-10'; // 先藏下去,因為elementFromPoint是找最上層 var draggableElementBrand = event.target.id; // pickup選取的icon var drop_element = document.elementFromPoint(event.changedTouches[0].clientX, event.changedTouches[0].clientY) // elementFromPoint從位置抓元素(drop位置最上方的元素) if(drop_element!=undefined){ var droppableElementBrand = drop_element.getAttribute("data-brand"); } event.target.style.zIndex = '1'; // 再放上層,不然會看不見 }
const isCorrectMatching = draggableElementBrand===droppableElementBrand; // 判斷drop/drag是否放對
// drop對 if(isCorrectMatching) { ...
// 答對的icon去掉touch的觸發 if(event.type=='touchend'){ draggableElement.removeAttribute("ontouchstart") draggableElement.removeAttribute("ontouchmove") draggableElement.removeAttribute("ontouchend") } } // drop錯 else{ ... }
// (觸控板)重設回原訂位置 if (moving) { // reset our element moving.style.left = ''; moving.style.top = ''; moving.style.height = ''; moving.style.width = ''; moving.style.position = '';
// moving = null; event.preventDefault() } }
|
加上事件
重點:
- drag物件的觸發事件是要加
dragstart
- drop物件的觸發事件是要加
dragenter
, dragover
, dragleave
, drop
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| // 選取可以拖拉的物件 const draggableElements = document.querySelectorAll(".draggable");
// 所有拖拉物件加上dragstart事件 draggableElements.forEach(elem => { elem.addEventListener("dragstart", dragStart); // elem.addEventListener("drag", drag); // elem.addEventListener("dragend", dragEnd); });
// 選取可以置放的物件 droppableElements = document.querySelectorAll(".droppable");
// 所有可置放的物件加上drop相關的事件 droppableElements.forEach(elem => { elem.addEventListener("dragenter", dragEnter); elem.addEventListener("dragover", dragOver); elem.addEventListener("dragleave", dragLeave); elem.addEventListener("drop", drop); });
|
觸控板的touch事件
參考至 Drag and drop elements on touch devices
html
1 2 3 4 5
| <!-- drag的物件 --> <div class='draggable' ontouchstart="pickup(event)" ontouchmove="move(event)" ontouchend="drop(event)"></div>
<!-- drop的物件 --> <div class='droppable' ontouchend="drop(event)"></div>
|
js
自己定義 pickup
, move
兩個函式來做為觸控板的碰觸與移動事件,至於 drop
的函式則合併至Drag-Drop事件的 drop
函式
重點:
event.changedTouches[0]
: 所有從上一次觸摸事件到這次觸發事件,狀態發生改變過的Touch對象,[0]
代表找最新的
document.elementFromPoint(x,y)
: 從 x, y 座標去找該位置上最上方的物件 (因為此時有拖拉元素,所以最上方的物件其實是拖拉的元素,所以將這個拖拉元素先手動藏下去再浮上來)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
| let moving = null;
// 觸碰板觸碰 function pickup(event) { moving = event.target;
moving.style.height = moving.clientHeight; moving.style.width = moving.clientWidth; // 才不會一按下去就下滑 moving.style.left = event.changedTouches[0].clientX - moving.clientWidth/2 + "px" moving.style.top = event.changedTouches[0].clientY - moving.clientHeight/2 + "px"; }
// 觸碰板移動 function move(event) { event.preventDefault() if (moving) { // 找到觸發元素的位置 moving.style.left = event.changedTouches[0].clientX - moving.clientWidth/2 + "px"; moving.style.top = event.changedTouches[0].clientY + moving.clientWidth/2 + "px"; // drop的物件加上droppable-hover這個class才能觸發hover的動畫 event.target.style.zIndex = '-10'; // 先藏下去 var drag_element = document.elementFromPoint(event.changedTouches[0].clientX, event.changedTouches[0].clientY) // elementFromPoint從位置抓元素(每次移動位置最上方的元素) event.target.style.zIndex = '1'; // 再浮上來 $(".droppable-hover").removeClass("droppable-hover") // 每次移動都把有包含hover這個動畫的元素的class去除 if(drag_element!=undefined && drag_element.className == "droppable"){ drag_element.classList.add("droppable-hover"); // 當最上方的元素是droppable的就增加droppable-hover這個class } event.preventDefault(); } }
|
事件對象的位置,圖片節錄至 Touch & Drag 事件
畫布 SVG
參考至 SVG 研究之路 (23) - 理解 viewport 與 viewbox
遇到的問題:
輪盤背後要排燈,但輪盤是圓的
解決方法:
先畫一個svg,z-index=-1
代表下移一層,viewbox
設定SVG可視範圍,圖片標籤是<image>
非<img>
,座標高寬設定好之後,只需要改變 transform: rotate(旋轉deg)
即可
1 2 3 4 5 6 7
| <svg style="z-index:-1" viewbox="-50 -50 100 100" version="1.1"> <image href="./路徑/檔名1.gif" x="-7.5px" y="-49px" height="15px" width="15px"/> <image href="./路徑/檔名2.gif" style="transform:rotate(20deg)" x="-7.5" y="-49" height="15px" width="15px"/> <image href="./路徑/檔名1.gif" style="transform:rotate(40deg)" x="-7.5" y="-49" height="15px" width="15px"/> <image href="./路徑/檔名2.gif" style="transform:rotate(60deg)" x="-7.5" y="-49" height="15px" width="15px"/> ... </svg>
|
Chrome無法讀取local文件的問題
利用d3.js去讀取本地端的csv檔會出現以下錯誤訊息:
URL scheme must be “http” or “https” for CORS request.
簡單來說,是因為Google Chrome預設禁止這樣的行為
所以參考至網路 處理Chrome無法讀取本地端文件問題,內有三種解決方法
我選擇的解決方法是正規作法,利用npm下載http-server來架一個本地伺服器
1 2
| $ npm install http-server -g // 安裝 $ http-server ./YOUR_FOLDER // 執行
|
輸入 http://127.0.0.1:8080
就可以看到網站