「日本語を縦書きにしたい」には、実は2つの全く違う要求が混ざっている。1つは「画面で縦書きに見えればいい」(Webページのデザイン)。もう1つは「縦書きの文字列そのものが欲しい」(SNSやメモ帳に貼ったときに縦に並んでほしい)。前者は CSS 一行で終わるが、後者はプレーンテキストで渡す限り CSS では達成できない。文字列を物理的に組み替える必要がある。
横書きテキストを縦書きプレビューしつつ、SNS 貼り付け用の縦書き文字列も生成できるツールをぱんだツールズの1機能として作った。
この記事では、この2種類の縦書きの違いと、後者を行列の転置で実装する方法を解説する。
見た目の縦書き:writing-mode 一行
まず簡単な方。Webページ上で縦書きに見せるだけなら、CSS の writing-mode でブラウザがネイティブに縦組みしてくれる。
const writingModeStyle: React.CSSProperties =
direction === 'rtl'
? { writingMode: 'vertical-rl', textOrientation: 'mixed' } // 右→左(日本語標準)
: { writingMode: 'vertical-lr', textOrientation: 'mixed' } // 左→右
-
vertical-rlは「文字は上→下、列は右→左」。日本語の書籍・新聞と同じ並び。vertical-lrは列が左→右 -
text-orientation: mixedがポイントで、これを付けると日本語の句読点(。、)や括弧(「」)が縦書き用の正しい向きに自動回転し、全角の仮名・漢字は正立する。mixedは名前のとおり「全角は正立・半角ラテン(英数字)は時計回りに90度横倒し」を混在させる指定
これでプレビューは完成。white-space: pre-wrap を併せれば改行も効く。
<div style={writingModeStyle} className="font-serif whitespace-pre-wrap">
{text}
</div>
ただしこれは DOM 上のレンダリングが縦になっているだけで、中身のテキストは元の横書き文字列のまま。この要素を選択してコピーしても、貼り付け先では普通の横書きに戻る。「見た目」と「データ」は別物、というのがこの記事の肝。
テキストの縦書き:CSS では不可能
X(旧Twitter)や LINE、メモ帳に「縦書きで」投稿したいとき、writing-mode は何の役にも立たない。それらは投稿欄のスタイルを上書きできないし、そもそもプレーンテキストしか受け取らない。
縦書きに見える文字列を渡すには、改行とスペースを使って文字を格子状に並べるしかない。たとえば「やうやう」「白く」の2行を縦書き風にするなら、こういう文字列を作る。
白や
くう
や
う
各行に1文字ずつ、右の列から順に詰めて、足りないところは全角スペースで埋める。これを等幅で表示すると、右列に「やうやう」、左列に「白く」が縦に並んで見える。やっていることは、**入力を行ごとの列に分け、行と列を入れ替える(転置する)**操作。
実装:右→左で読む行列転置
入力テキストを「改行ごとの列」とみなし、転置して縦書き文字列を組む。
const xPostText = useMemo(() => {
// 1. 改行で分割 → 空行を除外 → 各行を文字配列に(スペースは除去)
const columns = text
.split('\n')
.filter(p => p.trim() !== '')
.map(p => [...p].filter(ch => ch !== ' '))
if (columns.length === 0) return ''
// 2. 一番長い列の長さ = 出力の行数
const maxHeight = Math.max(...columns.map(col => col.length))
const rows: string[] = []
for (let r = 0; r < maxHeight; r++) {
let row = ''
// 3. 右列(index 0)を最右に。末尾の列から先頭へ向かって出力する
for (let c = columns.length - 1; c >= 0; c--) {
row += columns[c][r] ?? ' ' // 文字が無ければ全角スペースで桁を埋める
}
rows.push(row)
}
return rows.join('\n')
}, [text])
ポイントが3つある。
列の長さを揃える。 各列(=元の各行)は長さがバラバラなので、最長列に合わせて ?? ' '(全角スペース)でパディングする。これをしないと格子が崩れて縦の列がガタガタになる。半角スペースではなく全角スペースなのが重要で、等幅で日本語の全角文字と桁を合わせるには、埋め草も全角でなければズレる。
右から左へ出力する。 日本語の縦書きは右の列が先頭。入力の1行目(columns[0])を画面の最も右に置きたいので、出力ループは columns.length - 1 から 0 へ逆順に回す。これで「入力順=右から左」になる。
[...p] で文字配列にする。 p.split('') ではなくスプレッド構文で分解しているのは、サロゲートペア(絵文字や一部の漢字)を1文字として扱うため。split('') は UTF-16 のコード単位で割るので、サロゲートペアが2つに割れて化ける。スプレッド/Array.from はコードポイント単位で反復するので安全。
転置後の文字列は、X やメモ帳のような等幅・縦書き非対応の環境に貼ると、全角スペースのパディングのおかげで縦に揃って見える。CSS に一切頼らず、文字列だけで縦書きを「偽装」している。
2つのコピーを用意する理由
このツールにはコピーボタンが2つある。役割が違う。
- 「テキストをコピー」:入力をそのまま(横書きのまま)コピー。Word や一太郎のような縦書き対応アプリに貼る用。アプリ側で縦書き設定にすれば正しく縦組みされるので、文字列を組み替えてはいけない
- 「縦書きでコピー」:上記の転置済み文字列をコピー。SNS・メモ帳のような縦書き非対応の環境に貼る用
「縦書きにしたい」と一口に言っても、貼り付け先が縦書きを解釈できるかで必要なものが真逆になる。対応アプリには素のテキストを、非対応環境には転置済みを渡す。この出し分けが、最初に書いた「見た目の縦書き」と「データの縦書き」の違いそのものになっている。
まとめ
Web で「縦書き」を実現する手段は、目的によって完全に別物だった。
- 画面に縦書きで見せるだけなら
writing-mode: vertical-rl+text-orientation: mixedの CSS で済む。句読点・括弧も自動で正しい向きになる - ただし CSS の縦書きは DOM 表示専用。コピーすると横書きに戻る(データは変わらない)
- SNS など縦書き非対応の場所に貼るには、文字列自体を行列転置して全角スペースで桁揃えする
- 転置は「右列が先頭」なので逆順ループ。文字分解は
[...str]でサロゲートペア対応 - 貼り付け先が縦書き対応か否かで、渡すべきもの(素のテキスト/転置済み)は逆になる
「縦書き」という1つの言葉の裏に、CSS で解く問題と文字列操作で解く問題の2つが隠れていた、という気づきが面白い題材だった。
ぱんだツールズ では他にも PDF・画像・CSV・テキスト処理など、開発者向けのツールを多数公開している。全部無料・登録不要・ブラウザ完結で使える。
https://sakutto-panda.com
この記事は Zenn にも同じ内容を投稿しています。