JavaScript メモ

2020年12月27日

はじめに

JavaScript についてのメモ。

基本

ここでは HTML5 を想定する。次のように書く。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>タイトル</title>
  </head>
  <body>
    ...
    <script>
      ...
    </script>
  </body>
</html>

<script> と <script> の間にコードを書く。外部ファイルを指定する場合は、<script src="file.js"><script> などとする。

要素の取得

要素の取得には、id で指定して取る方法と class で指定して取る方法がある。

id

<input type="submit" value="Submit value" id="valueSubmit">
...

const valueSubmit = document.querySelector("#valueSubmit");

あるいは

const valueSubmit = document.getElementsById("valueSubmit");

class

<input type="submit" value="Submit value" class="valueSubmit">
...

const valueSubmit = document.querySelector(".valueSubmit");

あるいは

const valueSubmit = document.getElementsByClassName("valueSubmit")[0];

ページ内に名前が 1 つしか出てこない場合はどちらを使ってもよいが、本来 class は複数指定可能なので (実際、getElementByClassName() は配列を返す)、1 つしか出てこないなら id のほうがよいかもしれない。

タグの名前で取得することもできる。

const footer = document.getElementsByTagName("footer")[0];

要素の属性

たとえば、次のような要素があるとする。

<div id="elem" value="123">...</div>

この要素の value 属性の値を取得したい場合は、次のようにする。

const elem = document.getElementById("elem");
const value = elem.getAttribute("value");

スタイル

たとえば、スタイルで要素を非表示にするには、次のようにすればよい。

elem.style.display = "none";

要素の内容

<div id="elem">...</div>

要素の内容 (上の "...") にアクセスするには、以下のようにする。

const elem = document.getElementById("elem");
elem.textContent = "xxx";
elem.innerHTML = "<b>xxx</b>";

textContent ではテキストを設定できる。innerHTML であれば HTML タグも使える。

innerHTML にタグを追加していくには、リストを使うと便利である。

const temp = [];
temp.push("<h1>aaa</h1>");
temp.push("<p>bbb</p>");
elem.innerHTML = temp.join("\n");

子要素の取得

const children = elem.children;

ウインドウの大きさ

const width = window.innerWidth;
const height = window.innerHeight;

ボタンとイベント

ボタンを作るには、input 要素の submit タイプを使う方法と、button 要素を使う方法がある。クリックを捕捉するには、addEventListener() を使う方法と、onclick を使う方法がある。

button.addEventListener("click", () => {
  console.log("onclick");
});
button.addEventListener("click", () => {
  console.log("onclick2");
});
button.onclick = () => {
  console.log("onclick");
}
button.onclick = () => {
  console.log("onclick2");
}

addEventListener() の場合、上の例では "onclick"、"onclick2" が表示される。onclick の場合、上の例では "onclick2" しか表示されない。設定が上書きされるためである。

URL

URL は location で得られる。ページを移動したい場合は location.href に移動先の URL を入れればよい。

URL の後ろに "?" から始まる形でくっついたパラメータ部分 ("http://xxxxx/index.html?param1=1&param2=2" のような URL の "?" 以降) は location.search で得られる。パラメータの値を個別で得るには次のようにする。

const params = new URLSearchParams(location.search);
const param1 = params.get("param1");

"?" の代わりに "#" で始まっている場合は、次のようにすればよい。

const params = new URLSearchParams(location.hash.slice(1));

"#" を除いて渡している。

スクロールバー

スクロールバーの位置は、次のように得られる。

const leftPos = elem.scrollLeft;
const leftTop = elem.scrollTop;

スクロールバーを動かしたければ、上の変数に直接代入すればよい。

スクロールバーのスクロールを検出したければ、onscroll を用いる。

elem.onscroll = () => {
  console.log(elem.scrollTop);
}

クッキー

クッキーを読み書きするには document.cookie を用いる。

cookie.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>クッキー</title>
  </head>
  <body>
    <script>
      console.log(document.cookie);
      
      let date = new Date(Date.now());
      date.setMonth(date.getMonth() + 1);
      date = date.toUTCString();

      document.cookie = `a=1; samesite=lax; expires=${date}`;
      document.cookie = `b=2; samesite=lax; expires=${date}`;

      console.log(document.cookie);
      
      const cookieValue = document.cookie
        .split("; ")
        .find(row => row.startsWith("a"))
        .split("=")[1];

      console.log(`a = ${cookieValue}`);

      date = new Date(Date.now());
      date.setMonth(date.getMonth() - 1);
      date = date.toUTCString();

      document.cookie = `a=; expires=${date}`;
      document.cookie = `b=; expires=${date}`;
    </script>
  </body>
</html>

クッキーを書き込むには、document.cookie に以下のように値を入れる。

let date = new Date(Date.now());
date.setMonth(date.getMonth() + 1);
date = date.toUTCString();
document.cookie = `a=1; samesite=lax; expires=${date}`;
document.cookie = `b=2; samesite=lax; expires=${date}`;

ここでは "a" と "b" に値を設定している。一見、2 行目で 1 行目の値を上書きされているように見えるが、"a" の設定はちゃんと生きている。expires で有効期限を設定している。ここでは 1 ヶ月後に設定している。

値を取り出すには、document.cookie を参照すればよいが、すべての値が得られるので、必要なものを取り出す必要がある。"a" の値を取り出すには、次のようにする。

const cookieValue = document.cookie
  .split("; ")
  .find(row => row.startsWith("a"))
  .split("=")[1];

クッキーを削除したい場合は、expires で過去の日付を指定する。

date = new Date(Date.now());
date.setMonth(date.getMonth() - 1);
date = date.toUTCString();
document.cookie = `a=; samesite=lax; expires=${date}`;
document.cookie = `b=; samesite=lax; expires=${date}`;

以下のような関数を用意しておくと便利かもしれない。

function convertCookieToJSON(cookie) {
  if (cookie.trim() === "") {
    return {};
  }

  const cookies = cookie.split("; ");

  let json = {};

  for (const s of cookies) {
    const item = s.split("=");
    json[item[0]] = item[1];
  }

  return json;
}

function setCookie(label, value) {
  let date = new Date(Date.now());
  date.setMonth(date.getMonth() + 1);
  date = date.toUTCString();
  document.cookie = `${label}=${value}; samesite=lax; expires=${date}`;
}

function getCookie(label) {
  return document.cookie
    .split("; ")
    .find(row => row.startsWith(label))
    .split("=")[1];
}

function resetCookie() {
  const json = convertCookieToJSON(document.cookie);

  let date = new Date(Date.now());
  date.setMonth(date.getMonth() - 1);
  date = date.toUTCString();

  for (label in json) {
    document.cookie = `${label}=; expires=${date}`;
  }
}

sleep

function sleep(ms) {
  return new Promise(resolve => setTimeout(resolve, ms));
}

以下のように使う。

async function f() {
  ...
  await sleep(1000);
  ...
}

参考

JSON とオブジェクトの変換

オブジェクトを JSON に変換するには、次のようにする。

const str = JSON.stringify(obj);

ファイルに出力するときなどインデントが必要な場合は、次のようにする。

const str = JSON.stringify(obj, null, "  ");

逆に、JSON をオブジェクトに変換するには、次のようにする。

const obj2 = JSON.parse(str);

文字列のエンコード/デコード

たとえば、文字列 str を受け取って、以下のような文字列を作ろうとしたとする。

const obj = `{"str": "${str}"}`;

もし str の中に二重引用符が含まれると、問題が起こる。それに対処するには、エンコードする。

const obj = `{"str": "${encodeURI(str)}"}`;

もとに戻すにはデコードする。

const obj2 = JSON.parse(obj);
const str = decodeURI(obj2.str);

ひらがなカタカナ変換

function convertHiraToKata(str) {
    return str.replace(/[ぁ-ん]/g, function(s) {
       return String.fromCharCode(s.charCodeAt(0) + 0x60);
    });
}

空のオブジェクトかどうか調べる

Object.keys(obj).length === 0

静的メンバ変数の実装

クラスの静的メンバ変数を静的メンバ関数と set/get で実装する。

class Test {
  static get variable1() {
    return this._variable1;
  }
  
  static set variable1(v) {
    this._variable1 = v;
  }

  static get variable2() {
    return this._variable2 || {};
  }
  
  static set variable2(v) {
    this._variable2 = v;
  }
  
  static get variable3() {
    return this._variable3 || null;
  }
  
  static set variable3(v) {
    this._variable3 = v;
  }
  ...

get の || で初期値を設定している。

数字を 0 で埋める

数字を "00001"、"00002" などと表示する。

const filename = `${String(number).padStart(5, "0")}.json`;

乱数の表示

乱数を表示する。ページを更新すると値も更新される。

random_number.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>乱数</title>
  </head>
  <body>
    <p id="number"></p>

    <script>
      const randomNumber = Math.floor(Math.random()*100) + 1;
      const number = document.getElementById("number");
      number.textContent = randomNumber;
    </script>
  </body>
</html>

ボタンで乱数を更新するようにすると、次のようになる。

random_number2.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>乱数</title>
  </head>
  <body>
    <input type="submit" value="Get a random number" id="updateSubmit">
    <p id="number"></p>

    <script>
      const updateSubmit = document.getElementById("updateSubmit");
      const number = document.getElementById("number");

      function updateNumber() {
        const randomNumber = Math.floor(Math.random()*100) + 1;
        number.textContent = randomNumber;
      }

      updateSubmit.addEventListener("click", updateNumber);
    </script>
  </body>
</html>

ダイアログ

ボタンを押したら入力ダイアログを表示し、入力された文字列をダイアログで表示する。

dialog.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ダイアログ</title>
  </head>
  <body>
    <button>Press me</button>

    <script>
      const button = document.querySelector("button");

      button.onclick = function() {
        const msg = prompt("Input a message");
        alert(msg);
      }
    </script>
  </body>
</html>

値の入力

入力フォームに文字列を入力して、ボタンを押したら入力したものを表示する。

input_value.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>値の入力</title>
  </head>
  <body>
    <label for="valueInput">Enter a value: </label>
    <input type="text" id="valueInput">
    <input type="submit" value="Submit value" id="valueSubmit">

    <p id="value"></p>

    <script>
      const valueSubmit = document.getElementById("valueSubmit");
      const valueInput = document.getElementById("valueInput");
      const value = document.getElementById("value");

      function getValue() {
        value.textContent = valueInput.value;
      }

      valueSubmit.addEventListener("click", getValue);
    </script>
  </body>
</html>

Enter で値を入力するようにするには、次のコードを追加する。

input_value2.html

      function onKeyPress(e) {
        if (e.keyCode === 13) {
          getValue();
        }
      }

      valueInput.addEventListener("keypress", onKeyPress);

ボタンの追加

ボタンを押したら新しいボタンを追加する。

add_button.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ボタンの追加</title>
  </head>
  <body>
    <button id="addButtonButton">Add a button</button>

    <script>
      const addButtonButton = document.getElementById("addButtonButton");

      addButtonButton.addEventListener("click", () => {
        addButtonButton.disabled = true;

        const button = document.createElement("button");
        button.textContent = "Remove the button";
        document.body.appendChild(button);

        button.addEventListener("click", () => {
          button.parentNode.removeChild(button);
          addButtonButton.disabled = false;
        });
      });
    </script>
  </body>
</html>

選択

select.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>選択</title>
  </head>
  <body>
    <label for="valueSelect">種類</label>
    <select id="valueSelect"></select><br>
    <input type="submit" value="Submit value" id="valueSubmit">

    <p id="value"></p>

    <script>
      const valueSelect = document.getElementById("valueSelect");
      const valueSubmit = document.getElementById("valueSubmit");
      const value = document.getElementById("value");

      const options = ["", "メインクーン", "マンチカン", "スコティッシュフォールド"];

      for (const opt of options) {
        const option = document.createElement("option");
        option.value = opt;
        option.text = opt;
        valueSelect.appendChild(option);
      }

      function getValue() {
        value.textContent = `${valueSelect.selectedIndex}, ${valueSelect.value}`;
      }

      valueSubmit.addEventListener("click", getValue);
    </script>
  </body>
</html>

スクリプトで項目を選択する場合は、次のようにすればよい。

valueSelect.options[1].selected = true;

テーブルの要素の追加

ボタンを押したらテーブルの要素を追加する。

add_table_item.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>テーブルの項目の追加</title>
    <style>
table {
    border-collapse: collapse;
}
    </style>
  </head>
  <body>
    <input type="submit" value="Add a cell" id="addButtonSubmit">
    <table id="table" border="1">
        <thead>
            <tr><th>a</th><th>b</th></tr>
        </thead>
        <tbody>
        </tbody>
    </table>

    <script>
      const addButtonSubmit = document.getElementById("addButtonSubmit");

      function addButton() {
        const table = document.getElementById("table").getElementsByTagName('tbody')[0];
        const newRow = table.insertRow();
        const newCell1 = newRow.insertCell();
        newCell1.textContent = "1";
        const newCell2 = newRow.insertCell();
        newCell2.textContent = "2";
      }

      addButtonSubmit.addEventListener("click", addButton);
    </script>
  </body>
</html>

Canvas

Canvas で図形を描画する。

canvas.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>Canvas</title>
  </head>
  <body>
    <canvas id="canvas" width="640" height="480" style="background-color: #eeeeee;"></canvas>

    <script>
      const c = document.getElementById("canvas");
      const ctx = c.getContext("2d");
      ctx.moveTo(0, 0);
      ctx.lineTo(300, 200);
      ctx.stroke();

      ctx.beginPath();
      ctx.arc(300, 200, 20, 20, 0, 2*Math.PI);
      ctx.stroke();

      ctx.font = "26px Arial";
      ctx.fillText("test", 350, 250);
    </script>
  </body>
</html>

図を切り出して貼り直す

const c = document.createElement("canvas"); 
const w = canvas.width; 
const h = canvas.height*0.5; 
const imageData = canvas.getContext("2d").getImageData(0, 0, w, h); 
 
c.width = w; 
c.height = h; 
c.getContext("2d").putImageData(imageData, 0, 0); 

three.js の例

three.js を用いて立方体を描画する。

cube.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>cube</title>
    <style>
      body { margin: 0; }
      canvas { width: 100%; height: 100% }
    </style>
  </head>
  <body>
    <script src="https://threejs.org/build/three.js"></script>

    <script>
      const w = window.innerWidth;
      const h = window.innerHeight;

      const scene = new THREE.Scene();
      scene.background = new THREE.Color(0xdddddd);
      scene.add(new THREE.AmbientLight( 0x555555 ));

      const camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 1000);
      camera.position.z = 2;

      const geometry = new THREE.BoxBufferGeometry(1, 1, 1);
      const material = new THREE.MeshBasicMaterial({ color: 0xff0000 });
      const mesh = new THREE.Mesh(geometry, material);
      const edges = new THREE.EdgesGeometry(geometry);
      const line = new THREE.LineSegments(edges, new THREE.LineBasicMaterial( { color: 0x000000 }));
      const cube = new THREE.Group();
      cube.add(mesh);
      cube.add(line);
      scene.add(cube);

      const renderer = new THREE.WebGLRenderer({ antialias: true });
      renderer.setSize(w, h);
      document.body.appendChild(renderer.domElement);

      function animate() {
        requestAnimationFrame(animate);

        cube.rotation.x += 0.01;
        cube.rotation.y += 0.01;

        renderer.render(scene, camera);
      }

      animate();
    </script>
  </body>
</html>

参考

要素の表示/非表示の切り替え

たとえば、次のようなものを用意する。

<div id="debugSwitch">▽</div>

<div id="debug">
...
</div>

スクリプトで次のようにすれば、"▽" のクリックで <div id="debug">...</div> の表示/非表示を切り替えられる。

let debugDisplay = true;

function switchDebugDisplay() {
  if (!debugDisplay) {
    document.getElementById("debug").style.display = "block";
  } else {
    document.getElementById("debug").style.display = "none";
  }
  debugDisplay = !debugDisplay;
}

const debugSwitch = document.getElementById("debugSwitch");
debugSwitch.addEventListener("click", switchDebugDisplay);
switchDebugDisplay();

画面下にデバッグ表示領域を作る

上の方法でデバッグ表示領域を作ると、以下のようになる。

debug_area.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>デバッグ領域</title>
    <style>
footer {
  background: rgba(128, 192, 255, 0.3);
  position: fixed;
  bottom: 0;
  left: 0;
  width: 100%;
  height: 30%;
  overflow: auto;
}

div#debugSwitch {
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  text-align: left;
}
    </style>
  </head>
  <body>
    <footer>
      <div id="debugSwitch">▽</div>

      <div id="debug">
        <p>デバッグ領域</p>
      </div>
    </footer>
    <script>
      let debugDisplay = true;

      function switchDebugDisplay() {
        const footer = document.getElementsByTagName("footer")[0];
        if (!debugDisplay) {
          document.getElementById("debug").style.display = "block";
          footer.style.height = "40%";
        } else {
          document.getElementById("debug").style.display = "none";
          footer.style.height = "3%";
        }
        debugDisplay = !debugDisplay;
      }

      const debugSwitch = document.getElementById("debugSwitch");
      debugSwitch.addEventListener("click", switchDebugDisplay);
      switchDebugDisplay();
    </script>
  </body>
</html>

星ボタン

星ボタンを押してマークをつけたり外したりする。

star_butotn.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>星ボタン</title>
    <style>
div.starButton {
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  color: #696969;
  display: block;
  font-family: sans-serif;
  font-size: xx-large;
  font-weight: bold;
  text-decoration: none;
}
    </style>
  </head>
  <body>
    <h1>星ボタン</h1>
    <div class="starButton"> ☆ </div>
    <script>
      let marked = false;
      const starButton = document.querySelector(".starButton");
      starButton.addEventListener("click", switchButton);

      function setDisabledStarButton(b) {
        b.innerHTML = ' ☆ ';
        b.style.color = "#696969";
      }

      function setEnabledStarButton(b) {
        b.innerHTML = ' ★ ';
        b.style.color = "#ff8c00";
      }

      function switchButton() {
        console.log(marked);
        if (marked) {
          setDisabledStarButton(this);
          marked = false;
        } else {
          setEnabledStarButton(this);
          marked = true;
        }
      }
    </script>
  </body>
</html>

休日の取得

今日が休日 (祝日) かどうかを判定して返す。

get_holiday.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>休日の取得</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.24.0/moment.min.js"></script>
  </head>
  <body>
    <div id="view"></div>
    <script>
function getHoliday() {
  return new Promise((resolve, reject) => {
    const request = new XMLHttpRequest();

    // Holidays JP API https://holidays-jp.github.io/
    const url = "https://holidays-jp.github.io/api/v1/date.json";

    request.open("GET", url, true);
    request.responseType = 'json';

    request.onload = () => {
      const data = request.response;
      resolve(data);
    };

    request.send();
  });
}

function isHoliday(date) {
  return getHoliday().then((data) => data.hasOwnProperty(date));
}

const today = new Date();
const date = moment(today).format('YYYY-MM-DD');
isHoliday(date).then((data) => {
  const view = document.getElementById("view");
  view.textContent = `${date} ${data}`;
});
    </script>
  </body>
</html>

今日の日付を得て、isHoliday() で休日かどうかを得てコンソールに出力している。日付の処理に moment を用いている。休日のデータは Holidays JP API で得ている。

参考

HLS 配信

hls.js を用いる。

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>HLS 配信</title>
  </head>
  <body>
    <script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
    <video id="video" controls></video>
    <script>
      const video = document.getElementById("video");
      hls.loadSource("(.m3u8 ファイルの URL)");
      hls.attachMedia(video);
    </script>
  </body>
</html>

テーブルのソート

list.js を用いる。

list.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>テーブルのソート</title>
    <script src="http://cdnjs.cloudflare.com/ajax/libs/list.js/1.5.0/list.min.js"></script>
    <style>
.sort {
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  padding-right: 10px;
}

.sort:hover {
  background-color: #eee;
}

.sort:after {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 7px solid transparent;
  content: "";
  position: relative;
  top: -13px;
  right: -5px;
}

.sort.asc:after {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-top: 7px solid #000;
  content: "";
  position: relative;
  top: 13px;
  right: -5px;
}

.sort.desc:after {
  width: 0;
  height: 0;
  border-left: 5px solid transparent;
  border-right: 5px solid transparent;
  border-bottom: 7px solid #000;
  content: "";
  position: relative;
  top: -13px;
  right: -5px;
}

table {
  border-collapse: collapse;
}
    </style>
  </head>
  <body>
    <div id="table">
      <table border="1">
      <thead>
        <tr>
          <th class="sort" data-sort="num">num</th>
          <th class="sort" data-sort="abc">abc</th>
          <th class="sort" data-sort="aiu">あいう</th>
        </tr>
      </thead>
      <tbody class="list">
        <tr>
          <td class="num">1</td>
          <td class="abc">a</td>
          <td class="aiu">い</td>
        </tr>
        <tr>
          <td class="num">2</td>
          <td class="abc">c</td>
          <td class="aiu">あ</td>
        </tr>
        <tr>
          <td class="num">3</td>
          <td class="abc">b</td>
          <td class="aiu">う</td>
        </tr>
      </tbody>
      </table>
    </div>
    <script>
const options = {
  valueNames: ["num", "abc", "aiu"]
};
const list = new List("table", options);
list.sort("aiu", {order: "asc"});
//list.sort("aiu", {order: "desc"});
    </script>
  </body>
</html>

ハンバーバーメニュー

いわゆるハンバーガーメニュー (navigation drawer というらしい) の実装。

navigation_drawer.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ハンバーガーメニュー</title>
    <style>
/* button */
div.button {
  cursor: pointer;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
  width: 100px;
  border-radius: 5px;
  background-color: #0066ff;
  color: #ffffff;
  display: block;
  font-family: sans-serif;
  font-size: large;
  font-weight: bold;
  text-decoration: none;
  margin: 5px auto;
  padding: 5px 0;
  text-align: center;
}

/* navigation drawer */
#nav-drawer {
  position: absolute;
  top: 0;
  left: 0.5%;
}

.nav-unshown {
  display:none;
}

/* icon space */
#nav-open {
  display: inline-block;
  width: 40px;
  height: 30px;
  vertical-align: middle;
}

/* icon */
#nav-open span, #nav-open span:before, #nav-open span:after {
  position: absolute;
  height: 5px;
  width: 40px;
  border-radius: 5px;
  background: #06f;
  display: block;
  content: "";
  cursor: pointer;
  bottom: 20px;
}

#nav-open span:before {
  bottom: -10px;
}

#nav-open span:after {
  bottom: -20px;
}

#nav-close {
  display: none;
  position: fixed;
  z-index: 99;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: black;
  opacity: 0;
  transition: .1s ease-in-out;
}

#nav-content {
  overflow: auto;
  position: fixed;
  top: 0;
  left: 0;
  z-index: 9999;
  width: 90%;
  max-width: 145px;
  height: 100%;
  background: #fff;
  transition: .1s ease-in-out;
  -webkit-transform: translateX(-105%);
  transform: translateX(-105%);
}

#nav-input:checked ~ #nav-close {
  display: block;
  opacity: .0;
}

#nav-input:checked ~ #nav-content {
  -webkit-transform: translateX(0%);
  transform: translateX(0%);
  box-shadow: 6px 0 25px rgba(0,0,0,.15);
}
    </style>
    <div id="nav-drawer">
      <input id="nav-input" type="checkbox" class="nav-unshown">
      <label id="nav-open" for="nav-input"><span></span></label>
      <label class="nav-unshown" id="nav-close" for="nav-input"></label>
      <div id="nav-content">
        <div id="buttonBox">
          <div class="button" id="homeButton">ホーム</div>
          <div class="button" id="backButton">もどる</div>
        </div> 
      </div>
    </div>
  </head>
  <body>
  </body>
</html>

最初にメニューを出しておくには、nav-input を次のようにすればよい。

      <input id="nav-input" type="checkbox" class="nav-unshown" checked="checked">

出しっぱなしにするには、#nav-close の幅を #nav-content と同じにすればよい。

参考

ローディング

ローディング表示。ボタンを押したらローディング表示し、一定時間操作を受け付けなくする。

loading.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>ローディング</title>
    <style>
.loading {
  opacity: 0;
}

.balls {
  position: absolute;
  left: 50%;
  top: 50%;
  transform: translate(-50%, -50%);
  width: 100px;
  height: 100px;
  background-color: rgba(1, 1, 1, 0);
}

.ball {
  width: 50%;
  height: 20px;
  position: absolute;
  top: calc(50% - 10px);
  transform-origin: 100% 50%;
  left: 0;
}

.ball::before {
  content: "";
  display: block;
  width: 20px;
  height: 20px;
  border-radius: 50%;
  background-color: #06f;
  position: absolute;
  left: 0;
  top: 50%;
  transform: translateY(-50%);
}

.ball-1::before {
  animation: balls 1s linear 0s infinite;
}

.ball-2 {
  transform: rotate(45deg);
}

.ball-2::before {
  animation: balls 1s linear -0.125s infinite;
}

.ball-3 {
  transform: rotate(90deg);
}

.ball-3::before {
  animation: balls 1s linear -0.25s infinite;
}

.ball-4 {
  transform: rotate(135deg);
}
.ball-4::before {
  animation: balls 1s linear -0.375s infinite;
}

.ball-5 {
  transform: rotate(180deg);
}

.ball-5::before {
  animation: balls 1s linear -0.5s infinite;
}

.ball-6 {
  transform: rotate(225deg);
}

.ball-6::before {
  animation: balls 1s linear -0.625s infinite;
}

.ball-7 {
  transform: rotate(270deg);
}

.ball-7::before {
  animation: balls 1s linear -0.75s infinite;
}

.ball-8 {
  transform: rotate(315deg);
}

.ball-8::before {
  animation: balls 1s linear -0.875s infinite;
}

@keyframes balls {
  0% {
    width: 20px;
    height: 20px;
    opacity: 1;
  }
  100% {
    width: 6px;
    height: 6px;
    opacity: .2;
    margin-left: 7px;
  }
}
    </style>
    <div class="loading">
      <div class="balls">
      <span class="ball ball-1"></span>
      <span class="ball ball-2"></span>
      <span class="ball ball-3"></span>
      <span class="ball ball-4"></span>
      <span class="ball ball-5"></span>
      <span class="ball ball-6"></span>
      <span class="ball ball-7"></span>
      <span class="ball ball-8"></span>
      </div>
    </div>
  </head>
  <body>
    <button id="loadingButton">loading</button>

    <script>
      function screenLock(){
          const div = document.createElement('div'); 
          div.id = "screenLock"; 
          div.style.height = "100%"; 
          div.style.left = "0px"; 
          div.style.position = "fixed";
          div.style.top = "0px";
          div.style.width = "100%";
          div.style.zIndex = "9999";
          div.style.opacity = "0.1";
          div.style["background-color"] = "#000";
       
          const body = document.getElementsByTagName("body").item(0); 
          body.appendChild(div);
      }

      const loadingButton = document.getElementById("loadingButton");
      const loading = document.getElementsByClassName("loading")[0];

      loadingButton.addEventListener("click", () => {
        loading.style.opacity = "1";
        screenLock();

        setTimeout(() => {
          const div = document.getElementById("screenLock");
          div.parentNode.removeChild(div);
          loading.style.opacity = "0";
        }, 3000);

      });
    </script>
  </body>
</html>

参考

入れ替え可能なリスト

マウスのドラッグで入れ替え可能なリストを作成するには、jQuery UI の Sortable を用いる。下の例では table 要素を用いているが、ul 要素のリストでも可能である。

sortable_list.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>入れ替え可能なリスト</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js"></script>
    <style>
table {
  border-collapse: collapse;
  width: 100px;
}
    </style>
  </head>
  <body>
     <table border="1">
      <tbody id="sortable">
      <tr><td>1</td><td>りんご</td></tr>
      <tr><td>2</td><td>ごりら</td></tr>
      <tr><td>3</td><td>らっぱ</td></tr>
      <tr><td>4</td><td>ぱせり</td></tr>
    </table>
    <button id="button">出力</button>
    <div id="view"></div>
    <script>
      $("#sortable").sortable();
      $("#button").on("click", () => {
        const temp = [];
        for (const elem of $("#sortable").children("tr")) {
          temp.push(elem.children[1].textContent);
        }
        $("#view").html(temp.join(" "));
      });
    </script>
  </body>
</html>

参考

編集可能なテーブル

テーブルを編集可能にする jQuery プラグイン。

editable_table.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>編集可能なテーブル</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.5.1/jquery.min.js"></script>
    <script src="mindmup-editabletable.js"></script>
    <style>
table {
  border-collapse: collapse;
  width: 100px;
}
    </style>
  </head>
  <body>
    <table border="1">
      <tbody id="editable">
      <tr><td>1</td><td>りんご</td></tr>
      <tr><td>2</td><td>ごりら</td></tr>
      <tr><td>3</td><td>らっぱ</td></tr>
      <tr><td>4</td><td>ぱせり</td></tr>
    </table>
    <div id="view"></div>
    <script>
      $("#editable").editableTableWidget();
      $("#editable").sortable();
      $("#editable td").on("change", () => {
        const temp = [];
        for (const elem of $("#editable").children("tr")) {
          temp.push(elem.children[1].textContent);
        }
        $("#view").html(temp.join(" "));
      });
    </script>
  </body>
</html>

参考

PDF の表示と画像の保存

PDF.js を用いると、PDF の表示を行うことができる。Canvas に表示するので、Canvas から画像として保存することもできる。

ブラウザによっては (Firefox 以外は?) 日本語フォントの表示のために CMap のパスが必要である。これは配布パッケージの中に含まれている。ここでは npm の pdfjs-dist パッケージのものを用いる。

$ npm install pdfjs-dist

node_modules/pdfjs-dist/cmaps がそれである。この中の Adobe-Japan1 系と UniJIS 系、Hiragana、Katakana、Hankaku くらいがあればよい? getDocument() のパラメータ cMapUrl に CMap ディレクトリのパスを設定する。ブラウザではローカルのファイルにアクセスできないので、サーバー上に上げてテストする必要がある。

pdf.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>PDF</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.6.347/pdf.worker.min.js"></script>
    <style>
#pdfCanvas {
  border: 1px solid black;
}
    </style>
  </head>
  <body>
    <canvas id="pdfCanvas" width="400"></canvas>
    <input type="file" id="uploadFile" accept="application/pdf"></input>
    <a id="downloadImage" href="#">画像のダウンロード</a>
    <script>
      const canvas = document.getElementById("pdfCanvas");
      const uploadFile = document.getElementById("uploadFile");
      const downloadImage = document.getElementById("downloadImage");
      
      canvas.style.display = "none";
      downloadImage.style.display = "none";
      
      uploadFile.onchange = () => {
        const file = uploadFile.files[0];
        if(["application/pdf"].indexOf(file.type) == -1) {
            alert("エラー: PDF ファイルではありません。");
            return;
        }
        
        showPDF(URL.createObjectURL(file));
      }
      
      function showPDF(pdfURL) {
        const params = {
          url: pdfURL,
          cMapUrl: "./pdfjs-dist/cmaps/",
          cMapPacked: true
        };

        pdfjsLib.getDocument(params).promise.then((pdf) => {
          pdf.getPage(1).then((page) => {
            const scale = canvas.width/page.getViewport({scale: 1}).width;
            const viewport = page.getViewport({scale: scale});
            
            const context = canvas.getContext("2d");
            canvas.height = viewport.height;
            
            const renderContext = {
              canvasContext: context,
              viewport: viewport
            }
            
            page.render(renderContext);
            
            canvas.style.display = "block";
            downloadImage.style.display = "block";
          });
        }).catch((err) => {
          alert(err.message);
        });;
      }
      
      downloadImage.onclick = () => {
        downloadImage.setAttribute("href", canvas.toDataURL());
        downloadImage.setAttribute("download", "pdf.png");
      }
    </script>
  </body>
</html>

参考

行番号付きテキストエリア

テキストエリアを 2 つ使って、一方に行番号を書いておいて、もう一方のスクロールに連動させるようにすれば、行番号付きテキストエリアを作れる。

numbered_textarea.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>テキストエリア</title>
    <style>
#textArea table {
  border-collapse: collapse;
}

#textAreaNumber {
  width: 50px;
  height: 500px;
  text-align: right;
  resize: none;
  border: 1px solid #000;
  color: #999;
  background-color: #eee;
  overflow: hidden;
}

#textAreaEdit {
  width: 500px;
  height: 500px;
  resize: none;
  border: 1px solid #000;
}
    </style>
  </head>
  <body>
    <div id="view"></div>
    <script>
      const view = document.getElementById("view");
      const temp = [];
      temp.push('<div id="textArea">');
      temp.push('<table>');
      temp.push('<tbody>');
      temp.push('<tr>');
      temp.push('<td><textarea id="textAreaNumber" readonly="true"></textarea></td>');
      temp.push('<td><textarea id="textAreaEdit" spellcheck="false" wrap="off"></textarea></td>');
      temp.push('</tr>');
      temp.push('</tbody>');
      temp.push('</table>');
      temp.push('</div>');
      view.innerHTML = temp.join("\n");
 
      let maxLine = 1000;
      const addLine = 100;

      const textAreaNumber = document.getElementById("textAreaNumber");
      
      for (let i = 1; i <= maxLine; i++) {
        textAreaNumber.textContent += `${i}\n`;
      }
      
      const textAreaEdit = document.getElementById("textAreaEdit");
      
      textAreaNumber.scrollTop = 0;
      textAreaEdit.scrollTop = 0;
      
      textAreaEdit.onscroll = () => {
        textAreaNumber.scrollTop = textAreaEdit.scrollTop;
      }
      
      textAreaEdit.onkeydown = (e) => {
        if (e.keyCode === 9) {
          const tab = "  ";
          const ss = textAreaEdit.selectionStart;
          const se = textAreaEdit.selectionEnd;
 
          textAreaEdit.value = textAreaEdit.value.substr(0, ss)
              + tab + textAreaEdit.value.substr(se);
          
          textAreaEdit.setSelectionRange(ss + tab.length, ss + tab.length);
          textAreaEdit.focus();
          e.preventDefault();
        }
      }

      textAreaEdit.oninput = () => {
        let count = 0;
        const r = textAreaEdit.value.match(/\n/g);
        if (r === null) {
          count = 0;
        } else {
          count = r.length;
        }

        while (count > maxLine - addLine) {
          for (let i = 1; i <= addLine; i++) {
            textAreaNumber.textContent += `${maxLine+i}\n`;
          }
          maxLine += addLine;
        }
      }
    </script>
  </body>
</html>

UUID の取得

get_uuid.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>UUID の取得</title>
  </head>
  <body>
    <div id="view"></div>

    <script src="https://cdnjs.cloudflare.com/ajax/libs/uuid/8.1.0/uuidv4.min.js"></script>
    <script>
      const view = document.getElementById("view");
      view.textContent = uuidv4();
    </script>
  </body>
</html>

タイムアウト

時間が経ったら関数を実行するような処理は、次のように実現できる。

let timer = null;

if (timer !== null) {
    window.clearTimeout(timer);
}

timer = window.setTimeout(func,  milliseconds);

この例では、milliseconds だけこの部分の処理が行わなければ関数 func が呼び出される。タイムアウト前にこの部分に到達すれば、clearTimeout() でタイマーが無効になり、改めてタイマーが設定される。たとえば、一定時間画面操作をしなかったらログアウトするといった処理が可能である。

テーブルを CSV としてダウンロードする

リンクを用意する。

<a id="csv" href="" download="download.csv">ダウンロード</a>

以下のようにする。

const csv = document.getElementById("csv");
const csvData = [];

// header
csvData.push([
    "number",
    "item",
].join(","));

const table = document.getElementById("table").getElementsByTagName('tbody')[0];

for (const tr of table.children) {
  const row = [];
  for (const td of tr.children) {
    const text = td.textContent;
    const text2 = text.replace(/,/g, " ").replace(/\n/g, "");
    row.push(text2);
  }
  csvData.push(row.join(","));
}

const csvFile =
      new Blob([csvData.join("\n")], {type: "text/plain"});
csv.href = window.URL.createObjectURL(csvFile);

download_csv.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>CSV のダウンロード</title>
    <style>
table {
  border-collapse: collapse;
  width: 100px;
}
    </style>
  </head>
  <body>
    <table id="table" border="1">
      <tbody>
      <tr><td>1</td><td>りんご</td></tr>
      <tr><td>2</td><td>ごりら</td></tr>
      <tr><td>3</td><td>らっぱ</td></tr>
      <tr><td>4</td><td>ぱせり</td></tr>
    </table>
    <a id="csv" href="" download="download.csv">ダウンロード</a>
    <script>
      const csv = document.getElementById("csv");
      const csvData = [];

      // header
      csvData.push([
          "number",
          "item",
      ].join(","));

      const table
          = document.getElementById("table").getElementsByTagName('tbody')[0];

      for (const tr of table.children) {
        const row = [];
        for (const td of tr.children) {
          const text = td.textContent;
          const text2 = text.replace(/,/g, " ").replace(/\n/g, "");
          row.push(text2);
        }
        csvData.push(row.join(","));
      }

      const csvFile =
            new Blob([csvData.join("\n")], {type: "text/plain"});
      csv.href = window.URL.createObjectURL(csvFile);
    </script>
  </body>
</html>

文字コードを Unicode から Shift-JIS に変換する必要がある場合には、encoding.js を使う。

<script src="https://cdnjs.cloudflare.com/ajax/libs/encoding-japanese/1.0.30/encoding.min.js"></script>

以下のようにする。

const array = Encoding.stringToCode(csvData.join("\n"));
const array2 = Encoding.convert(array, "SJIS", "UNICODE");
const array3 = new Uint8Array(array2);
const csvFile =
      new Blob([array3], {type: "text/csv"});
csv.href = window.URL.createObjectURL(csvFile);

日本時間を得る

get_date.html

<!DOCTYPE html>
<html>
  <head>
    <meta charset="UTF-8">
    <title>日本時間の取得</title>
  </head>
  <body>
    <div id="view"></div>

    <script>
      const view = document.getElementById("view");

      const now = new Date();
      const datetime = now.toLocaleString("ja-JP", {timeZone: "Asia/Tokyo"});
      const dateString
                  = now.toLocaleDateString("ja-JP", {timeZone: "Asia/Tokyo"});

      const dateStrings = dateString.split("/");
      const year = dateStrings[0];
      const month = dateStrings[1].padStart(2, "0");
      const date = dateStrings[2].padStart(2, "0");
      const filename = `${year}${month}${date}`;


      const temp = [];
      temp.push(`<p>${datetime}</p>`);
      temp.push(`<p>${dateString}</p>`);
      temp.push(`<p>${filename}</p>`);

      view.innerHTML = temp.join("\n");
    </script>
  </body>
</html>

参考