Yuya8888
@Yuya8888 (裕也)

Are you sure you want to delete the question?

If your question is resolved, you may close it.

Leaving a resolved question undeleted may help others!

We hope you find it useful!

まるばつゲームを作成したいが、JavaScriptが読み込めない。

解決したいこと

Javascriptでまるばつゲームを作成中です。
コードは一通り記述したのですが、肝心の Javascriptが読み込めずゲームが作動しません。
現状は下の画像の通りです。
2C313DD4-85D4-47E5-B4DA-6706BC488F3B.jpeg

発生している問題・エラー(検証ツールのコンソールにて)

Uncaught TypeError: Cannot read property 'addEventListener' of null
    at Object../app/javascript/app.js (app.js:95)
    at __webpack_require__ (bootstrap:19)
    at Object../app/javascript/packs/application.js (application.js:10)
    at __webpack_require__ (bootstrap:19)
    at bootstrap:83
    at bootstrap:83

GET http://localhost:3000/app.js net::ERR_ABORTED 404 (Not Found)   localhost/:71 

GET http://localhost:3000/app.css net::ERR_ABORTED 404 (Not Found)   localhost/:21

GET http://localhost:3000/app.js net::ERR_ABORTED 404 (Not Found)   localhost/:71 

app/assets/stylesheets/app.css

html {
  box-sizing: border-box;
  font-size: 16px;
}
*, *::before, *::after {
  box-sizing: border-box;
}
li {
  list-style: none;
}
body {
  margin: 0;
  line-height: normal;
}
h1 {
  margin: 20px 0;
  font-size: 2rem;
  font-weight: bold;
}
.wrapper {
  max-width: 1024px;
  margin: 0 auto;
  padding: 0 10px;
  text-align: center;
}
.game-container {
  padding: 60px 0;
}
.message-container {
  margin-bottom: 20px;
  font-size: 1.25rem;
  font-weight: bold;
}
.batsu {
  color: #00b0f0;
}
.maru {
  color: #ff0000;
}
.js-hidden {
  display: none;
}
.squares-container {
  margin: 0 auto;
  width: 202px;
}
.squares-box {
  width: 202px;
  height: 202px;
  display: flex;
  flex-wrap: wrap;
  border: solid 2px #333;
}
.square {
  position: relative;
  width: calc(198px / 3);
  height: calc(198px / 3);
  border: solid 2px #333;
}
.js-maru-checked::before {
  content: '';
  width: 50px;
  height: 50px;
  border: solid 8px #ff0000;
  border-radius: 50%;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}
.js-batsu-checked::before {
  content: '';
  width: 50px;
  height: 8px;
  margin: auto;
  background-color: #00b0f0;
  border-radius: 4px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(45deg);
}
.js-batsu-checked::after {
  content: '';
  width: 8px;
  height: 50px;
  margin: auto;
  background-color: #00b0f0;
  border-radius: 4px;
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%) rotate(45deg);
}
.btn-container {
  padding-top: 40px;
}
.btn {
  display: inline-block;
  width: 150px;
  padding: 10px 20px;
  border-radius: 5px;
  cursor: pointer;
}
.btn-reset {
  color: #fff;
  background-color: #ffc000;
  font-weight: bold;
}
.btn-reset:hover {
  background-color: #ffd347;
  transition-duration: 0.4s;
}

/* JS連動 */
.js-unclickable {
  pointer-events: none;
}
.js-highLight {
  background-color: #fff2cc;
}

app/views/layouts/index.html.erb

<!DOCTYPE html>
<html lang="ja">
  <head>
    <meta charset="UTF-8" />
    <title>○×ゲーム</title>
    <link href="app.css" rel="stylesheet" />
  </head>
  <body>
    <div class="wrapper">
      <div class="game-container">
        <div class="message-container">
          <ul class="message-list">
            <li id="maru-turn" class="js-hidden">
              <span class="maru">○</span>の番
            </li>
            <li id="batsu-turn">
              <span class="batsu">×</span>の番
            </li>
            <li id="maru-win" class="js-hidden">
              <span class="maru">○</span>の勝ち!
            </li>
            <li id="batsu-win" class="js-hidden">
              <span class="batsu">×</span>の勝ち!
            </li>
            <li id="draw" class="js-hidden">
              引き分け
            </li>
          </ul>
        </div>

        <div class="squares-container">
          <div class="squares-box">
            <!-- 1行め -->
            <div id="1-1" class="square"></div>
            <div id="1-2" class="square"></div>
            <div id="1-3" class="square"></div>
            <!-- 2行め -->
            <div id="2-1" class="square"></div>
            <div id="2-2" class="square"></div>
            <div id="2-3" class="square"></div>
            <!-- 3行め -->
            <div id="3-1" class="square"></div>
            <div id="3-2" class="square"></div>
            <div id="3-3" class="square"></div>
          </div>
        </div>

        <div class="btn-container">
          <div class="js-hidden" id="reset-btn">
            <span class="btn btn-reset">もう一回遊ぶ</span>
          </div>
        </div>

      </div>
    </div>
    <script src="app.js"></script>
  </body>
</html>

app/javascript/app.js

let flag = false;
let counter = 9;
let winningLine = null;
const squares = document.querySelectorAll('.square');
const squaresArray = [].slice.call(squares); // IE11対策
const messages = document.querySelectorAll('.message-list li');
const messagesArray = [].slice.call(messages); // IE11対策
const resetBtn = document.querySelector('#reset-btn');


// メッセージの切り替え関数
const setMessage = (id) => {
    messagesArray.forEach((message) => {
        if (message.id === id) {
            message.classList.remove('js-hidden');
        } else {
            message.classList.add('js-hidden');
        }
    });
}


// 勝利判定のパターン関数
const filterById = (targetArray, idArray) => {
    return targetArray.filter((e) => {
        return (e.id === idArray[0] || e.id === idArray[1] || e.id === idArray[2]);
    });
}
// 勝利判定パターン
const line1 = filterById(squaresArray, ['1-1', '1-2', '1-3']);
const line2 = filterById(squaresArray, ['2-1', '2-2', '2-3']);
const line3 = filterById(squaresArray, ['3-1', '3-2', '3-3']);
const line4 = filterById(squaresArray, ['1-1', '2-1', '3-1']);
const line5 = filterById(squaresArray, ['1-2', '2-2', '3-2']);
const line6 = filterById(squaresArray, ['1-3', '2-3', '3-3']);
const line7 = filterById(squaresArray, ['1-1', '2-2', '3-3']);
const line8 = filterById(squaresArray, ['1-3', '2-2', '3-1']);
const lineArray = [line1, line2, line3, line4, line5, line6, line7, line8];
// 勝利判定関数
const isWinner = (symbol) => {
    // some: 1つでも条件を満たしていればTrueを返す
    const result = lineArray.some((line) => {
        // every: 全て条件を満たしていればTrueを返す
        const subResult = line.every((square) => {
            if (symbol === 'maru') {
                return square.classList.contains('js-maru-checked');
            } else 
            if (symbol === 'batsu') {
                return square.classList.contains('js-batsu-checked');
            }
        });

        if (subResult) { winningLine = line }

        return subResult;
    });
    return result;
}


// ゲーム終了時の関数
const gameOver = () => {
    // 全てのマスをクリック不可にする
    squaresArray.forEach((square) => {
        square.classList.add('js-unclickable');
    });

    // 勝った時のマス目をハイライトする
    if (winningLine) {
        winningLine.forEach((square) => {
            square.classList.add('js-highLight');
        });
    }

    // リセットボタン表示
    resetBtn.classList.remove('js-hidden');
}


// ゲームの初期化の関数
const initGame = () => {
    flag = false;
    counter = 9;
    winningLine = null;
    squaresArray.forEach((square) => {
        square.classList.remove('js-maru-checked');
        square.classList.remove('js-batsu-checked');
        square.classList.remove('js-unclickable');
        square.classList.remove('js-highLight');
    });
    setMessage('batsu-turn');
    resetBtn.classList.add('js-hidden');
}
resetBtn.addEventListener('click', function() {
    initGame();
});


// マスをクリックした時のイベント発火
squaresArray.forEach((square) => {
    square.addEventListener('click', () => {
        if (flag === true) {
            square.classList.add('js-maru-checked');
            square.classList.add('js-unclickable');

            if (isWinner('maru')) {
                setMessage('maru-win');
                gameOver();
                return;
            }

            setMessage('batsu-turn');
            flag = false;

        } else {
            square.classList.add('js-batsu-checked');
            square.classList.add('js-unclickable');

            if (isWinner('batsu')) {
                setMessage('batsu-win');
                gameOver();
                return;
            }

            setMessage('maru-turn');
            flag = true;
        }

        counter--;
        // 引き分け判定
        if (counter === 0) {
            setMessage('draw');
            gameOver();
        }
    });
});

app/views/layouts/application.html.erb

<!DOCTYPE html>
<html>
  <head>
    <title>Marubatugame</title>
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

    <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
    <%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
  </head>

  <body>
    <%= yield %>
  </body>
</html>

app/javascript/packs/application.js

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so it'll be compiled.

require("@rails/ujs").start()
// require("turbolinks").start()
require("@rails/activestorage").start()
require("channels")
require("../app")

// Uncomment to copy all static images under ../images to the output folder and reference
// them with the image_pack_tag helper in views (e.g <%= image_pack_tag 'rails.png' %>)
// or the `imagePath` JavaScript helper below.
//
// const images = require.context('../images', true)
// const imagePath = (name) => images(name, true)

自分で試したこと

『Rails6でJavaScriptを使う方法』や、『JavaScript 読み込めない』等、検索してその記事通り記述を編集しても
解決にはいたりませんでした。
未熟者で手詰まりなので教えていただけると助かります。

0

1Answer

app/javascript/packs/application.jsで読み込んでいるrequire("../app")を消せば動くと思います。

htmlのほうでもapp.jsは読み込んでいるので2回読み込むことになっているし、app/javascript/packs/application.jsではドキュメントが読み込まれる前にスクリプトが実行されるので、html要素を取得できずエラーになっています。

訂正
app/javascript/packs/application.jsは自動生成ファイルっぽいのでいじっちゃダメでした。(ruby知らないのでごめんなさい)
app/javascript/app.jsを以下のように修正し、ドキュメントの読み込みを待つようにすれば動作すると思います。

document.addEventListener("DOMContentLoaded", ()=> {

 // ここにapp.jsにもともと書いてあったコードを貼り付ける

})
1Like

Comments

  1. @Yuya8888

    Questioner

    丁寧にありがとうございます!!
    そもそも読み込む記述していなかったですね・・・。
    初歩的な質問でお手数おかけいたしました。

Your answer might help someone💌