JavaScriptで作ったジグソーパズルをスマホにも対応させます。基本的にPC版に対してコードを追加していきます。
フリックによるスクロールの許可と禁止
フリックすることでピースを移動させるのですが、スクロールもフリックでおこないます。ピースを移動させたいのにスクロールも同時におこなわれては困るのでスクロールを許可したり禁止する処理が必要になります。
1 2 3 4 5 6 7 8 9 |
function disableScroll(e) { e.preventDefault(); } // これでフリック時のスクロールを禁止できる window.addEventListener('touchmove', disableScroll, { passive: false }); // これでフリック時のスクロールを許可できる window.removeEventListener('touchmove', disableScroll); |
そこで以下のような関数を定義します。第二引数でフリック時のスクロールを許可したり禁止します。
1 2 3 4 5 6 |
function enableScrollOnTouchMove(e, enable){ if(!enable) window.addEventListener('touchmove', disableScroll, { passive: false }); else window.removeEventListener('touchmove', disableScroll); } |
フリック開始時の処理
ディスプレイにタッチしたら指が触れている部分の座標を取得します。そしてその座標にピースがある場合はそのなかでも一番手前にあるものをmovingPieceに格納します。後述しますが、pieces.filter関数が返す配列の一番最後のものがそれに該当します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
window.addEventListener('touchstart', (ev) => { ev.preventDefault(); // デフォルトイベントをキャンセル if(ev.touches.length != 1){ // 触れている指が1でない場合 enableScrollOnTouchMove(ev, true); // フリックでスクロールできるようにする return; } let x = ev.touches[0].pageX; // 触れている指に関する情報を取得 let y = ev.touches[0].pageY; // 触れている指に関する情報を取得 const rect = can.getBoundingClientRect(); let ps = pieces.filter(piece => piece.IsClick(x - rect.left, y - rect.top) ); if(ps.length == 0){ console.log('どれもクリックされていない'); enableScrollOnTouchMove(ev, true); // フリックでスクロールできるようにする return; } movingPiece = ps[ps.length - 1]; // 一番手前に描画されているものを取得 }); |
ピースを移動させる処理
フリックしているときの処理を示します。movingPieceがnullでないなら、指が触れている部分の座標を調べて、その部分に移動中のピースの中央が描画されるようにします。またピースが見えない位置に移動してしまわないように注意します。
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 |
window.addEventListener('touchmove', (e) => { if(e.touches.length != 1) return; if(movingPiece != null){ const rect = can.getBoundingClientRect(); let x = e.touches[0].pageX; // 触れている指に関する情報を取得 let y = e.touches[0].pageY; // 触れている指に関する情報を取得 let newX = x - rect.left - pieceSize * 0.75; let newY = y - rect.top - pieceSize * 0.75; // 見えない位置に移動してしまうのを防ぐ if(newX < - pieceSize / 2) newX = - pieceSize / 2; if(newY < - pieceSize / 2) newY = - pieceSize / 2; if(newX > can.width - pieceSize / 2) newX = can.width - pieceSize / 2; if(newY > can.height - pieceSize * 0.75) newY = can.height - pieceSize * 0.75; movingPiece.X = newX; movingPiece.Y = newY; drawAll(); } }); |
フリック終了時の処理
フリックが終了したときの処理を示します。PC版とほとんどやっていることは同じなのですが、ディスプレイが狭いのでピースを仮置きできる場所を増やすために、そこにすでにピースがある場合、移動をキャンセルする処理をなくしました。
完成したときにピースが存在する場所に固定されているピースがなく、その位置からズレが小さいときだけフィットさせることにします。
またフリックが終了したタイミングで、フリックによるスクロールを禁止する処理をおこないます。
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 |
window.addEventListener('touchend', (e) => { if(movingPiece != null){ let col = Math.round(movingPiece.X / pieceSize); let row = Math.round(movingPiece.Y / pieceSize); if(col < 0) col = 0; if(row < 0) row = 0; if(row < rowMax && col < colMax){ let ps = pieces.filter(_ => _.X == col * pieceSize && _.Y == row * pieceSize); if(ps.length == 0){ // そこにピースがなくズレが小さいときだけフィットさせる if(Math.abs(col * pieceSize - movingPiece.X) < 20 && Math.abs(row * pieceSize - movingPiece.Y) < 20){ movingPiece.X = col * pieceSize; movingPiece.Y = row * pieceSize; } } } // 移動したピースを最前面に描画する pieces = pieces.filter(piece => piece != movingPiece); pieces.push(movingPiece); movingPiece = null; drawAll(); check(); } enableScrollOnTouchMove(e, false); // フリックでスクロールできないようにする }); |
リロードの抑止
通常、手前にスワイプしたときにリロードが発生しますが、パズルを解いている最中にリロードされては困るのでリロードを抑止したいのですが完全に抑止することはできないので、リロードが発生するまえに確認のダイアログを表示させます。
1 2 3 4 5 |
// リロードの抑止(完璧ではない) window.addEventListener('beforeunload', (ev) => { ev.preventDefault(); ev.returnValue = ''; }); |