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?

ブラウザで動く簡易スクラッチパッドを作ってみた

1
Posted at

概要

ちょっとしたメモや下書き用途として、ブラウザで動く簡易スクラッチパッドを作ってみました。
単一のHTMLファイルで動作も軽く、ローカルで利用できます。

周りに共有したところ意外と反応が良かったので、簡単に紹介してみます。

経緯

以前から、ブラウザのアドレスバーに次の文字列を入力して簡易メモとして使っていました。

data:text/html,<html contenteditable>

これだけでブラウザ上で編集可能なページが開くので、どんな環境でも使えるローカルメモとして結構便利です。

ただし、この方法には一つ大きな不満がありました。

Tabキーがインデントではなく要素フォーカス移動になってしまう

ちょっとしたメモを書くときでも、Tabが使えないのは意外とストレスだったので、

  • Tab / Shift+Tab のインデント操作
  • 自由なキー割り当て(カーソル移動・削除に対して)
  • 単一HTMLファイル
  • ブラウザだけで動く

という条件で、簡易的なスクラッチパッドを試作してみました。


実装

以下のHTMLファイルで動作します。

<!doctype html>
<html lang="ja">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>ScratchPad</title>
<style>
  :root{color-scheme:light dark}
  html,body{height:100%;margin:0;overflow:hidden}
  #editor{height:100%;padding:12px;box-sizing:border-box;outline:none;font:14px/1.5 monospace;white-space:pre-wrap;tab-size:4}
</style>
</head>
<body>
<div id="editor" contenteditable spellcheck="false"><div><br></div></div>
<script>
(() => {
    const e = document.getElementById("editor"), TAB = "\t";
    const blk = n => (n && n !== e) ? (n.parentNode === e ? n : blk(n.parentNode)) : null;

    e.addEventListener("keydown", ev => {
        if (ev.isComposing) return;
        const s = getSelection(), r = s.getRangeAt(0), k = ev.key.toLowerCase();
        const mod = (ev.ctrlKey && ev.altKey), shift = ev.shiftKey;

        // カーソル移動 (Ctrl+Alt+H/J/K/L)
        if (mod && "hjkl".includes(k)) {
            ev.preventDefault();
            s.modify(shift ? "extend" : "move", "hk".includes(k) ? "backward" : "forward", "hl".includes(k) ? "character" : "line");
        }
        // 削除 (Alt+Shift+D:前 / Ctrl+Alt+D:次)
        else if (k === "d" && ev.altKey && (shift || ev.ctrlKey)) {
            ev.preventDefault();
            s.modify("extend", shift ? "backward" : "forward", "character");
            document.execCommand("delete");
        }
        // Tab / Shift+Tab // 複数行選択にも対応
        else if (k === "tab") {
            ev.preventDefault();
            if (s.isCollapsed) {
                if (!shift) document.execCommand("insertText", false, TAB);
                else {
                    const n = r.startContainer, o = r.startOffset;
                    if (n.nodeType === 3 && o > 0 && n.nodeValue[o-1] === TAB) {
                        n.nodeValue = n.nodeValue.slice(0, o-1) + n.nodeValue.slice(o);
                        r.setStart(n, o-1); s.removeAllRanges(); s.addRange(r);
                    }
                }
            } else {
                let b = [], start = blk(r.startContainer), end = blk(r.endContainer);
                for (let c = start; c; c = c.nextElementSibling) { b.push(c); if (c === end) break; }
                b.forEach(x => {
                    const f = x.firstChild, isTxt = f?.nodeType === 3;
                    if (!shift) isTxt ? f.nodeValue = TAB + f.nodeValue : x.insertBefore(document.createTextNode(TAB), f);
                    else if (isTxt && f.nodeValue.startsWith(TAB)) {
                        f.nodeValue = f.nodeValue.slice(1);
                        if (!f.nodeValue) x.removeChild(f);
                    }
                });
                const nr = document.createRange();
                nr.setStart(b[0], 0); nr.setEnd(b.at(-1), b.at(-1).childNodes.length);
                s.removeAllRanges(); s.addRange(nr);
            }
        }
    });
    e.focus();
})();
</script>
</body>
</html>

基本は contenteditable をベースにして、必要なキー操作だけ上書きしています。

現在のキー設定はこんな感じですが、HTMLの記述を変更すれば自由に割り当てを変更できます。

操作 キー
カーソル移動 Ctrl+Alt+H / J / K / L
インデント Tab / Shift+Tab
削除 Alt+Shift+D / Ctrl+Alt+D

data URLとして使う

実用を考えて、このHTMLを data URL にして、ブラウザのブックマークから直接起動することにしました。
data URL とは、ファイルの内容をそのままURLとして埋め込む仕組みです。

手順はこんな感じです。

  1. HTMLファイルの内容をdata URLに変換
  2. そのURLをブックマーク
  3. クリックするとスクラッチパッドが開く

変換はオンラインツールでもできますし、ブラウザの開発者ツールのConsoleでも簡単に作れます。

const html = `HTMLの内容コピペ`;
const dataUrl = "data:text/html;charset=utf-8," + encodeURIComponent(html);
console.log(dataUrl);

感想

現在、私はこんな用途で使っています。

・ブラウザの新規タブ1枚でテキストメモ
・書き終わったらタブを閉じるだけ
・プレーンテキスト化したいときの一時置き場
・画像をそのまま貼り付け
・エディタやビューアを起動するほどでもないとき

ブラウザの標準機能も基本そのまま使えます。

・Undo / Redo
・拡大縮小
・ページ内検索
・HTML保存
・スクリーンショット

手元のChromeやEdgeでは問題なく動作していますが、環境によっては動かない可能性があります。
execCommandなど非推奨なAPIの利用は避けるべきだと思います。

また、contenteditable ベースで必要な編集挙動のみを上書きしているため、

  • DOM構造に依存した挙動
  • 貼り付け時の構造崩れ

などの不安定さは多少残っています。

とはいえ、

「すぐ起動して書き捨てるメモ」

としては結構気に入っています。
まだ改善の余地はあると思うので、今後も試していこうと思います。

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?