1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

【ドラッグアンドドロップAPI】HTML+CSS+JavaScriptでペルソナ5風のクロスワードを作ってみた

Posted at

こんなものを作ってみたので簡単に解説です

記事を開くと画像の問題がありますが、そこからさらに私の個人サイトに飛んで
実際に文字をドラッグアンドドロップできるクロスワードが遊べます。

スクリーンショット 2025-09-27 214851.jpg

HTML(回答エリアと問題)※省略

p5_9th.html
  <table class="container">
    <tr>
      <td></td>
      <td class="box" label="A"></td>
    </tr>
  </table>
  <table class="container">
    <tr>
      <td class="box"><div class="card" draggable="true" id="s1" label="	げ	"></div></td>
    </tr>
  </table>

JavaScript

p5_9th.html
<script type="text/javascript"><!--
// ----- パソコン・スマートフォン共通 -----
let draggedCard = null; // ドラッグ中のカード
function isDroppableBox(element) {  // その要素はカードを置けるボックスか
  if (!element || !element.classList.contains("box")) return false;  // ボックスでないとだめ
  if (element.classList.contains("card")) return false;  // カードであったらだめ
  if (element.childElementCount > 0) return false;  // 先客のカードがいたらだめ
  return true;
}

// ----- パソコンでのドラッグドロップ用 -----
const handleDragCardStart = (e) => {
  // カードをドラッグ開始したときのイベント
  e.target.classList.add("dragging");
  draggedCard = e.target;
};
const handleDragCardEnd = (e) => {
  // カードをドラッグ終了したときのイベント
  e.target.classList.remove("dragging");
  draggedCard = null;
};
// カードたちにイベントを登録
document.querySelectorAll(".card").forEach(card => {
  card.addEventListener("dragstart", handleDragCardStart, false);
  card.addEventListener("dragend", handleDragCardEnd, false);
});

const handleDragEnter = (e) => {
  // 要素の上に何かがドラッグされてきたときのイベント
  if (draggedCard == null) return;  // ドラッグされてきたのがカードでないなら何もしない
  // ドラッグされてきたのがカードであってそこがカードを置けるボックスなら知らせるため目立たせる
  if (isDroppableBox(e.target)) e.target.classList.add("box-over");
};
const handleDragOver = (e) => {
  // 要素の上に何かがドラッグされているときのイベント
  e.preventDefault();  // ブラウザ既定の処理を禁止
  if (draggedCard == null) {  // ドラッグされてきたのがカードでないなら禁止カーソルにする
    e.dataTransfer.dropEffect = "none";
    return;
  }
  // そこがカードを置けるボックスでないなら禁止カーソルにする
  e.dataTransfer.dropEffect = isDroppableBox(e.target) ? "move" : "none";
  // 但しそこがドラッグ中のカード自身や元々いたボックス上空なら便宜上移動可能カーソルにする
  // ドラッグし始めていきなり禁止マークが出るとインタフェースとして微妙なため
  if (e.target == draggedCard) e.dataTransfer.dropEffect = "move";
  if (e.target == draggedCard.parentElement) e.dataTransfer.dropEffect = "move";
};
const handleDragLeave = (e) => {
  // 要素の上から何かが去っていったときのイベント
  e.target.classList.remove("box-over");  // もし目立たせていたら目立たなくする
};
const handleDrop = (e) => {
  // 要素の上でカードが手放されたときのイベント
  e.preventDefault();  // ブラウザ既定の処理を禁止
  e.target.classList.remove("box-over");  // もし目立たせていたら目立たなくする
  // ドラッグされてきたのがカードであってそこがカードを置けるボックスならそこにカードを追加
  if (draggedCard && isDroppableBox(e.target)) e.target.appendChild(draggedCard);
};
// ページ上に何かがドラッグされてきた/いる/去る/落とされるイベントを登録
document.addEventListener("dragenter", handleDragEnter, false);
document.addEventListener("dragover", handleDragOver, false);
document.addEventListener("dragleave", handleDragLeave, false);
document.addEventListener("drop", handleDrop, false);

// ----- スマートフォンでのドラッグドロップ用 -----
let dragPreview = null; // 指に追従させるカード
const handleTouchStart = (e) => {
  // 画面にタッチしたときのイベント
  const target = e.target;
  if (!target.classList.contains("card")) return;
  draggedCard = target;
  draggedCard.classList.add("dragging");
  // 指に追従させるカードを作成
  dragPreview = draggedCard.cloneNode(true);
  dragPreview.classList.add("drag-preview");
  document.body.appendChild(dragPreview);
};
const handleTouchMove = (e) => {
  // 画面にタッチしている指を動かしたときのイベント
  if (draggedCard == null) return;
  e.preventDefault();  // ブラウザ既定の処理を禁止
  const touch = e.touches[0];  // 最初のタッチポイント
  // 指に追従させるカードを指に追従させる
  dragPreview.style.left = `${touch.clientX}px`;
  dragPreview.style.top = `${touch.clientY}px`;
  dragPreview.style.display = "block";
  // 全ボックスの box-over クラスを除去し改めて指の下にあるボックスだけに box-over クラスを付与
  document.querySelectorAll(".box").forEach(box => {box.classList.remove("box-over");});
  const element = document.elementFromPoint(touch.clientX, touch.clientY);
  if (isDroppableBox(element)) element.classList.add("box-over");
};
const handleTouchEnd = (e) => {
  // 画面にタッチしている指を放したときのイベント
  e.preventDefault();  // ブラウザ既定の処理を禁止
  if (draggedCard == null) return;
  const touch = e.changedTouches[0];  // タッチ終了位置
  const element = document.elementFromPoint(touch.clientX, touch.clientY);
  if (isDroppableBox(element)) element.appendChild(draggedCard);
  // ドラッグ中のカードと指に追従させるカードをリセット
  draggedCard.classList.remove("dragging");
  document.querySelectorAll(".box").forEach(box => {box.classList.remove("box-over");});
  draggedCard = null;
  if (dragPreview) dragPreview.remove();
  dragPreview = null;
};
if ("ontouchstart" in window) {
  // スマートフォンであったらページにタッチイベントを登録
  document.addEventListener("touchstart", handleTouchStart, { passive: false });
  document.addEventListener("touchmove", handleTouchMove, { passive: false });
  document.addEventListener("touchend", handleTouchEnd, false);
}

let body_lock = false;
const lock = document.getElementById('lock');
const e_html = document.getElementsByTagName('html');
const e_body = document.getElementsByTagName('body');

function toggle_lock(){
  if(!body_lock){
    lock.innerHTML="画面ロック:ON";
    e_html[0].style.overflow = 'hidden';
    e_body[0].style.overflow = 'hidden';
    body_lock = true;
  }else{
    lock.innerHTML="画面ロック:OFF";
    e_html[0].style.overflow = 'auto';
    e_body[0].style.overflow = 'auto';
    body_lock = false;
  }
}

lock.addEventListener('touchstart', function(e){
  toggle_lock();
}, false);

lock.addEventListener('click', function(e){
  toggle_lock();
}, false);

const check_button = document.getElementById('check');

function check(){
  if((s5.parentNode.id == "b1"||s8.parentNode.id == "b1"||s13.parentNode.id == "b1"||s20.parentNode.id == "b1"||s21.parentNode.id == "b1"||s55.parentNode.id == "b1")&& //か
  s50.parentNode.id == "b2"&& //れ
  s3.parentNode.id == "b3"&& //ー
  (s37.parentNode.id == "b4"||s40.parentNode.id == "b4")&& //ら
  (s10.parentNode.id == "b5"||s15.parentNode.id == "b5"||s28.parentNode.id == "b5"||s31.parentNode.id == "b5"||s48.parentNode.id == "b5"||s49.parentNode.id == "b5")&& //い
  (s2.parentNode.id == "b6"||s26.parentNode.id == "b6")){ //す
    result.innerHTML="☆☆ 正解! ☆☆";
  }else{
    result.innerHTML="不正解";
  }
}

// スマホ対応: タッチイベントとクリックイベントを両方設定
check_button.addEventListener('touchstart', function(event) {
  check();
}, false);

check_button.addEventListener('click', function(event) {
  check();
}, false);

簡単に言うと

containerのセルに移動できる要素cardをドラッグアンドドロップAPIをスマホ・PC両方で動くようにして移動できるように設定

判定は決まったcontainerセルに決まったcardが入っているかだけ判定

という感じになっています。

コードは
1)index.html
2)sp.html
3)p5_9th.html
の順に簡単になっていますので1から読んでいけば理解しやすいかと。

スマホ対応については下記の記事に大変お世話になりました。
【HTML】要素を置き場から置き場にドラッグ&ドロップする (PC+スマートフォン両対応)
https://qiita.com/Cookieword_box26/items/15cf863ddba02701aa6f

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?