log

日々の記録と、書くことを楽しむためのブログです

集中できるテキストエディタを作ろう

自分好みのテキストエディタっぽいものを、JSを使って自分で作ってみます。

作りたいもの

邪魔なメニューが極力表示されてない、エモを邪魔しないテキストエディタを作りたい。
テキストエディタとはいいますが、プログラム向けのあれのことではありません。日本語.txt用です。

今回は最小限の機能にします。

搭載する機能

  • 真ん中に文字を書き込めるスペースがある
  • メニューを開閉できる
  • 文字数を表示(スペースと改行は含めない)
  • フォントを変えられる

完成図

完成図(メニュー閉)
完成図(メニュー開)

コード

出来上がったものがこちらです。

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <link rel="stylesheet" href="style.css">
    <title>t e x t</title>
</head>
<body>
    <!-- メニュー -->
    <div id="menu">
        <nav>
            <div class="inner">
                <div id="settings">
                    <span>
                        <label>Font:</label>
                        <select id="font-select">
                            <option value="font-noto-sans" selected>Noto Sans JP</option>
                            <option value="font-zen-maru">Zen Maru Gothic</option>
                            <option value="font-shipori">Shippori Mincho</option>
                            <option value="font-klee-one">Klee One</option>
                            <option value="font-sawarabi-gothic">Sawarabi Gothic</option>
                            <option value="font-hina-mincho">Hina Mincho</option>
                            <option value="font-kiwi-maru">Kiwi Maru</option>
                            <option value="font-noto-serif">Noto Serif JP</option>
                            <option value="font-zen-old-mincho">Zen Old Mincho</option>
                        </select>
                    </span>
                </div>
            </div>
        </nav>

        <div class="menu_btn" onclick="open_menu()">
            <span></span>
            <span></span>
            <span></span>
        </div>

    </div>

    <!-- 書き込みスペース -->
    <main id="writing_space">
        <textarea id="textarea" oninput="count()" class="font-noto-sans" wrap="soft"></textarea>
        <p id="counter">0</p>
    </main>
    <script src="index.js"></script>
</body>
</html>

解説

  • .menu_btnの中にspanが並んでいるのは、そのエリアを黒塗りしてハンバーガーメニューの三本線を作るためです
  • #textareaにoninput属性があります。これは入力されるごとにcount()イベントが発動するという意味です。文字数カウントに使います。

CSS

@import url('https://fonts.googleapis.com/css2?family=Hina+Mincho&family=Kiwi+Maru&family=Klee+One&family=Noto+Sans+JP&family=Noto+Serif+JP&family=Sawarabi+Gothic&family=Shippori+Mincho&family=Zen+Maru+Gothic&family=Zen+Old+Mincho&display=swap');

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body{
  font-family: 'Noto Sans JP', sans-serif;
  color: #333;
}

/* テキストエリア */
#writing_space{
  display: grid;
  grid-template-rows: 1fr;
  grid-template-columns: 1fr;
  place-items: center;
  margin: 10rem 5rem 5rem 5rem;
}

#writing_space textarea{
  resize: none;
  padding: 10px;
  height: 600px;
  width: 900px;
  border-color: rgb(221, 221, 221);
  border-radius: 6px;
}
textarea:focus{
  outline: none;
}

/* 文字数カウンター */
#counter{
  color: rgb(156, 156, 156);
  font-size: small;
}

/* メニュー */
nav{
  display: block;
  position: fixed;
  background-color: rgb(221, 221, 221);
  top: 0;
  left: -300;
  bottom: 0;
  z-index: 3;
  opacity: 0;
  width: 400px;
}

#menu nav .inner{
  padding: 15px;
}

/* メニューを出す */
.open nav{
  left: 0;
  opacity: 1;
}

/* フォント選択 */
#font-select{
  border-color: rgb(221, 221, 221);
  border-radius: 6px;
}
#font-select:focus{
  outline: none;
}

/* メニューボタン */
.menu_btn{
  display: block;
  position: fixed;
  top: 30px;
  right: 30px;
  width: 30px;
  height: 30px;
  z-index: 3;
  cursor: pointer;
}

/* spanタグで作るハンバーガーメニュー */
.menu_btn span{
  position: absolute;
  display: block;
  left: 0;
  width: 30px;
  height: 3px;
  background-color: #333;
  border-radius: 4px;
}

/* ボタンのデザイン */
.menu_btn span:nth-child(1){
  top: 4px;
}
.menu_btn span:nth-child(2){
  top: 14px;
}
.menu_btn span:nth-child(3){
  bottom: 4px;
}

/* ×ボタン */
.open .menu_btn span:nth-child(1){
  transform: translateY(10px) rotate(-315deg);
}
.open .menu_btn span:nth-child(2){
  opacity: 0;
}
.open .menu_btn span:nth-child(3){
  transform: translateY(-10px) rotate(315deg);
}


/* フォント */
.font-noto-sans{
  font-family: 'Noto Sans JP', sans-serif;
}
.font-zen-maru{
  font-family: 'Zen Maru Gothic', sans-serif;
  font-size: 18px;
  font-weight: 300;
}
.font-shipori{
  font-family: 'Shippori Mincho', serif;
}
.font-klee-one{
  font-family: 'Klee One', cursive;
}
.font-sawarabi-gothic{
  font-family: 'Sawarabi Gothic', sans-serif;
}
.font-hina-mincho{
  font-family: 'Hina Mincho', serif;
}
.font-kiwi-maru{
  font-family: 'Kiwi Maru', serif;
}
.font-noto-serif{
  font-family: 'Noto Serif JP', serif;
}
.font-zen-old-mincho{
  font-family: 'Zen Old Mincho', serif;
}

解説

  • importでグーグルフォントから好みのフォントをインポートします
  • フォントクラスを事前に用意しておき、フォント変更に備えます
  • メニューボタンでメニューを透明→不透明にして表示ができます
    • ※メニュー関係の実装は借りてきたものなので解説は省きます

JS

// 文字数カウント
// 改行とスペースはカウントしない
function count(){
    let text = document.querySelector('#textarea').value;
    text = text.replace(/\n/g, '');
    text = text.replace(/\s/g, '');

    const counter = document.querySelector('#counter');
    counter.innerHTML = text.length
}

// メニュー表示
function open_menu(){
    let nav = document.querySelector('#menu');
    nav.classList.toggle('open');
}

// フォント変更
let font_selecter = document.querySelector('#font-select');
font_selecter.addEventListener('change', (e) => {
    let font_class = e.target.value;

    textarea.classList.remove(...textarea.classList); //テキストエリアのクラスをすべて削除
    textarea.classList.add(font_class);
});

解説

文字数カウント

// 文字数カウント
// 改行とスペースはカウントしない
function count(){
    let text = document.querySelector('#textarea').value;
    text = text.replace(/\n/g, '');
    text = text.replace(/\s/g, '');

    const counter = document.querySelector('#counter');
    counter.innerHTML = text.length
}

この関数はHTMLで指定したとおり、文字を入力するたびに動きます。

let text = document.querySelector('#textarea').value;
text = text.replace(/\n/g, '');
text = text.replace(/\s/g, '');

#textareaの文字を取得し、その中からまず改行と全角スペースを除きます。

  • 「\n」は改行、「\s」は全角スペースを意味します
  • 正規表現のうしろにある「g」はgオプションといいます。これがあることにより、文字列の中のすべての改行とスペースを空白に置き換える(=消す)ことができます。
const counter = document.querySelector('#counter');
counter.innerHTML = text.length

そして、その文字数を.lengthで取得して、文字数を表示する#counterエリアに表示させます


メニュー表示

openクラスを付与することで左側のメニューを可視化します。 toggleなので、クリックするたびにクラスのON/OFFが切り替わります。


フォント変更

#font-selectのセレクトボックスを選択すると関数が動きます。

// フォント変更
let font_selecter = document.querySelector('#font-select');
font_selecter.addEventListener('change', (e) => {
    let font_class = e.target.value;

    textarea.classList.remove(...textarea.classList); //テキストエリアのクラスをすべて削除
    textarea.classList.add(font_class);
});

はじめに、セレクトボックスで選択したもののvalueをとります。これはcssで前もって準備していたフォントクラス名になっています。

let font_class = e.target.value;
<select id="font-select">
    <option value="font-noto-sans" selected>Noto Sans JP</option>
    <option value="font-zen-maru">Zen Maru Gothic</option>
    ...
</select>
.font-noto-sans{
  font-family: 'Noto Sans JP', sans-serif;
}
.font-zen-maru{
  font-family: 'Zen Maru Gothic', sans-serif;
  font-size: 18px;
  font-weight: 300;
}
...

次に、テキストエリアに指定したフォントクラスをつけていくのですが、先に、テキストエリアのclassをすべて削除します。

textarea.classList.remove(...textarea.classList); //テキストエリアのクラスをすべて削除

これにはスプレッド構文を使います。
スプレッド構文

ざっくりいうと、配列などの連なったオブジェクトをいっぺんに順番に処理するときなどに使う構文のようです。
ここでは「textarea.classList」の中身すべてをremoveするということだと思っていただければOKです。

最後に、選択されたフォントクラス名をテキストエリアにくっつけます。

textarea.classList.add(font_class);

これでフォントが変わります。
使う際にはお好みのフォントでお試しください。

おわりに

こんな感じで、最低限書くことができるエディタができました。
F11で全画面にするとかなりいい感じです。

実はこういう風にシンプルな画面のエディタは持ってるのですが、せっかくだから作ってみました。
気分転換にはいいかも。