定義
ドラッグ用の取っ手(⣿ のような)アイコンや装飾文字を画面には見せたいが、ユーザーが本文をコピーするときにその装飾文字まで一緒にコピーされるのを防ぎたいときに使う知識だ。
背景となる概念が二つ: Selection.toString()(現在の選択範囲を文字列として取り出すブラウザ API)とコピー時の直列化(選択範囲をクリップボード用テキストに変換する過程)は、いずれも DOM のテキストノード(HTML 内の実際の文字データ)を基準に文字列を作る。そのため、これを防ぐのによく使う user-select: none(マウスのドラッグ選択を防ぐ CSS)は、ユーザーが新たにドラッグして選択することだけを難しくするだけで、すでに取られた選択のコピー結果からその文字を完全に除外すると保証するものではない。確実に除外するには、その文字をテキストノードではなく CSS ::before / ::after の content(generated content — HTML にはなく CSS だけで描き込む偽コンテンツ)に置く方が安全だ。
なぜ必要か
問題の状況: ブロックエディタのドラッグ取っ手、装飾アイコン、隠しラベルを DOM に本物のテキストとして入れておくと、ユーザーがそのブロックを選択してコピーするとき、取っ手のグリフ(⣿ のようなアイコン文字)が本文テキストと混ざってクリップボードに入る。貼り付けると「⣿ 実際の内容」のように汚くなる。
このとき user-select: none だけでは不十分で、draggable={false}(要素をドラッグできなくする属性)はドラッグ動作を防ぐだけでコピー文字列とは無関係だ。だから**「画面に見える装飾」と「コピーされる意味のあるテキスト」を分離する**アプローチが必要だ。
動作原理
- ユーザーが範囲をドラッグすると
Selectionが DOM テキストノードを指す。 - コピー時にブラウザは選択範囲のテキストを直列化する。経路・ブラウザごとにブロック境界の改行数(
\nvs\n\n)が異なりうる。 user-select: noneはポインターでそのノードを選択範囲に入れにくくするが、親選択・プログラム的な選択・一部の直列化経路では依然として含まれうる。::before { content: "…" }は生成コンテンツなので DOM テキストノードではなく、一般的なSelection.toString()/ クリップボード経路にはあまり含まれない。
| 項目 | 説明 | 注意点 |
|---|---|---|
user-select: none | ポインター選択を難しくする | Selection/コピー完全除外の保証ではない |
::before content | 生成コンテンツ、DOM テキストではない | a11y 代替テキストは別途設計 |
draggable=false | ネイティブ要素ドラッグを抑制 | img/a の既定動作を遮断する用 |
| クリップボード直列化 | ブラウザ・経路ごとに改行が異なる | テストは空でない行に正規化 |
実務適用
選択/コピーから除外すべきグリフは CSS generated content に移す。
メディア・リンクの意図しないドラッグは draggable={false} で防ぐ。クリップボードの E2E は、ブラウザごとの改行差を許容するよう、空でない行だけ比較して正規化する。
スクリーンリーダーが取っ手の意味を知る必要があるなら、aria-label などのアクセシビリティツリーで別途提供する。generated content だけでは a11y は解決しない。
トレードオフ
- pseudo content はコピー汚染を減らすが、一部のブラウザ/ツールで生成コンテンツを異なる扱いにすることがある。核心の経路は実機で確認する。
- 装飾を DOM から外すと、スタイル・ヒット領域の設計が CSS により依存する。
使ってはいけない場合
- 当該文字が意味のある本文のとき — コピーされるべきなのでテキストノードに置く。
- 装飾を選択可能にする必要がある UX(例: 取っ手自体を選択して操作) — 別のインタラクションモデルを使う。
よくある間違い
user-select: noneだけで「クリップボードから除外された」と仮定する。- 取っ手のグリフをテキストノードに置き、コピー結果に混ざったままにする。
- クリップボードテストを改行文字の数まで厳密に比較し、ブラウザごとに flake が出る。
- a11y を無視して視覚グリフだけ pseudo に移す。
関連概念
- html5-drag-and-drop — 取っ手・画像・リンクがドラッグ/選択を横取りする問題と併せて扱う。