オンラインで動く縦書きのテキストエディタを実装する

朝日新聞社のメディア研究開発センターで、サービスのUIUXを考えたり、フロントエンドの開発をしたりしている松山と申します。

今回、Javascriptで縦書きのテキストエディタを作ってみました。主な実装方法と、デザインする上で重要だと思った点を紹介します。
![Screenshot 2024-04-30 at 3.03.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/8656c0d1-619b-9c7f-9466-ad59413f11a8.png)

こちらが完成したエディタになります。
まだバグなど不完全な部分が多いですが、縦書きエディタの触り心地を確かめてみてください。

https://vertical-japanese-input-editor.vercel.app/

# 実装方法

縦書きで記入でき、見出しなども追加できる、シンプルな見た目のエディタを実装しました。

後で解説しますが、大まかな実装方法です。

- 縦書きエディタの実装は、まずdivのテキストが縦書きになるようスタイリングし、次に、`contentEditable`属性をONにすることで、縦書きのdivの中身を編集できるようにすることで実装しました。
- 細かいデザインに関しては、エディタのdivにあてるCSSを細かく編集することで、綺麗に整えました。
- 見出しなどのリッチテキストの機能は、エディタのライブラリ「`lexical.js`」を用いることで、比較的スムーズに実装できました。

## 縦書きのテキストエリアを作成する

`contentEditable` 属性を`div`に付与すると、その中身がテキストエリアになります。

この機能を用いて、以下のように縦書きのテキストエリアを作ります。

```jsx
<div
	// 1. 縦書きのdivを作る
  style={{
    writingMode: "vertical-rl", // 縦書きにする
    textOrientation: "upright", // アルファベットも縦書きにする
    textAlign: "start", // 上揃え
  }}
  // 2. divをテキストエリアにする
  contentEditable={true}
>
	ああああああ
</div>
```

基本的には、これで縦書きのテキストエディタが完成します。
![Screen-Recording-2024-04-30-at-1.39.31.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/1fd8b039-97e0-3f94-6587-f7e68a0d4dd8.gif)


## 見た目を整える

縦書きレイアウトを美しく見せるよう、CSSを調整します。

ノウハウは以下のサイトに詳しいです。

[縦書きWeb普及委員会](https://tategaki.github.io/explan6.html)

色々と試してみて、個人的に調整の効果が出やすいと思ったのは以下の３つです。

- 行間隔（line-height）
- テキストの色（font-color）
- フォント（font）

どれも、余白や質感を演出するものです。

### 行間隔

行間隔（line-height）を広げることで、圧迫感を軽減できます。これでもか！というくらい空けるのがちょうどいいのではと思います。
広めに取ることで、将来的にルビや強調の点（・）を付け足した際にも、ゆとりを維持できます。

- デフォルト値
![Screenshot 2024-04-30 at 1.49.54.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/3f87e058-19ff-7d70-e057-02e220273f3b.png)

- 文字間隔を広めに調整
![Screenshot 2024-04-30 at 1.53.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/7d27c612-ebd8-3ce5-4241-7995a868425e.png)

### テキストの色

テキストの色（font-color）を少し明るめにすることで、優しい雰囲気にできます。真っ黒だと、教科書のような無骨な圧迫感を感じますが、明るくすると、日光が当たっているようなやんわりとしたやさしい雰囲気になる気がします。

- 真っ黒
![Screenshot 2024-04-30 at 2.06.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/3c18279b-8f37-834a-93f2-a3a8d6018aba.png)

- 少し明るくする
![Screenshot 2024-04-30 at 2.06.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/d662382e-5177-a744-5738-93bd29a75111.png)

### フォント

フォントを変更することで、文章の雰囲気を変えられます。san-selif体にすると、ビジネス書っぽくなって、serif体にすると小説っぽくなる気がします。

- san-serif体
![Screenshot 2024-04-30 at 4.13.28.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/70aaa21b-7e9d-d911-7721-07d5635a04e6.png)

- serif体
![Screenshot 2024-04-30 at 4.13.44.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/731a126e-17ba-5057-e741-bf2b0d308277.png)

### １行あたりの文字数
他には、１行あたりの文字数も重要です。

例えば文庫本の場合、１行あたりの文字数は４０文字前後です。そこに合わせると本を直接書いているような体験を作れるので、演出に活用できます。

おもに、次の3つを調整することで、文字数をコントロールできます。

- 文字同士の間隔（letter-spacing）
- フォントサイズ（font-size）
- １行あたりの高さ（height）

これらを調整して、うまい具合に行の文字数をコントロールすると良いかと思います。

## テキストエディタに太字やH1といったリッチテキストの機能つける

現状ですと、縦書きのテキストエリアとしては十分ですが、用途によっては不十分かもしれません。

そこで、`lexical.js`の力を借りて、太字などの機能をつけます。`lexical.js` とは、リッチなテキスト編集を可能にするjavascriptライブラリです。

lexical.jsでは、今回構築した縦書きのテキストエディタを使用することができます。[Getting Startedの例](https://lexical.dev/docs/getting-started/react)にある`ContentEditable`コンポーネントに、先ほど作成したCSSプロパティをコピペすることで、縦書きのエディタで`lexical.js`を使用できます。

```jsx
<RichTextPlugin
  contentEditable={
    <ContentEditable
      style={{
		    writingMode: "vertical-rl",
		    textOrientation: "upright",
		    textAlign: "start",
		  }}
    />
  }
  //...省略
/>
```

後は、lexical.jsの汎用的な機能を使うことで、太字ボタンをつけたり、H1H2などをつけます。この部分については、本題とは異なるので割愛します。

ただ、縦書き特有で一点だけ追加で実装すべきポイントがあります。

### 改行でスクロールが動かない問題を解消する

現状のままでは、動画のように改行でスクロールが移らず、新しい行が隠れて見えない不具合があります。

- 不具合：改行しても画面が動かない
![Screen Recording 2024-04-30 at 10.37.37.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/299e0544-6ec9-9034-37f3-73758284d1f2.gif)

- 意図する挙動：開業した際に画面が動く
![Screen Recording 2024-04-30 at 10.37.57.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2984288/8e094e4c-862b-3c47-1ce0-2add8559e3f0.gif)

これを修正するため、エンターの入力を検知してスクロールを移すカスタムロジックが必要になります。これをLexicalのプラグインの形で実装しました。このプラグイン（コンポーネント）を読み込むことでバグを回避できます。

```jsx
// デフォルトでは、縦書きてエディタの場合、改行してもviewに収まるように改行されない。
// そのため、カスタムの改行ロジックを書いた

import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
import { $isRangeSelection } from "lexical";

const NewLineVisibilityPlugin = () => {
  const [editor] = useLexicalComposerContext();
	useEffect(()=> {
		return editor.registerUpdateListener((state) => {
	    const currentEditorState = state.editorState;
	    const selection = currentEditorState._selection;
	    if (!selection || !selection.isCollapsed || !$isRangeSelection(selection))
	      return;
	    const anchorKey = selection.anchor.key;
	    const elem = editor.getElementByKey(anchorKey);
	    // elem.textContentは空である場合、改行されたとみなす
	    if (elem && !elem.textContent) {
		    // 画面からはみ出している時だけ、映るようにスクロール
	      // @ts-ignore
	      elem.scrollIntoViewIfNeeded(false);
	    }
	  });
	}, [editor])
  return null;
};

export default NewLineVisibilityPlugin;
```

## おわりに

縦書きのテキストエディタをデザインし、実装してみました。

個人的には、CSSをいじるだけで、きちんと動くものが作ることができたことが驚きでした。

ネットでは横書きの文章がほとんどですが、縦書きは日本人に馴染みやすいものなのではないでしょうか。

これを機に、縦書きのインターネットについて深めてみるというのも、面白いかもしれないと感じました。

今回のエディタはこちらです。バグなどありますが、縦書きの雰囲気を感じていただければと思います。

https://vertical-japanese-input-editor.vercel.app/

（メディア研究開発センター　松山莞太）