1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

クリップボードに text/html の書き込むときにどういうスタイルを入れるか

Last updated at Posted at 2024-12-04

上記の例だと css nesting とか入れると うまくいかないのに気が付いたので

実際の動作確認する場合のメモ

確認方法

上記のサンプルのスタイルを

& {
  .tomato {
    color: red;
    font-size: 20px;
    font-weight: bold;
  }
}

にする

上記の適用を行ったサンプルとして以下に playground を用意しておきます。

確認する分には paste イベントの DataTransfer 読めば良さそうですね。

Pasteイベントからクリップボード の 確認する用のサンプルとして下記を用意

See the Pen use DataTranscfer example ver.20240907 by juner clarinet (@juner) on CodePen.

確認結果としては以下

リッチテキストコピー

<html>
<body>
<!--StartFragment--><html><head><style>& {
  & .tomato { color: red; font-size: 20px; font-weight: bold; }
}
</style></head><body><div id="message">
  <div class="tomato">トマト</div>
  <div style="color: green; font-size: 24px; font-weight: bold">ピーマン</div>
</div></body></html><!--EndFragment-->
</body>
</html>

直接選択してコピーした場合

<html>
<body>
<!--StartFragment--><div class="tomato" style="color: red; font-size: 20px; font-weight: bold; font-family: Meiryo; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">トマト</div><div style="font-family: Meiryo; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; white-space: normal; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial; color: green; font-size: 24px; font-weight: bold;">ピーマン</div><!--EndFragment-->
</body>
</html>

スタイルシートが CSS nesting を考慮していなくて壊れていますね……。
具体的にはこの適用部分か。

// スタイルを抽出して適用
const styleSheets = Array.from(document.styleSheets);
let styleContent = "";
styleSheets.forEach(sheet => {
    const rules = Array.from(sheet.cssRules || []);
    rules.forEach(rule => {
        styleContent += rule.cssText + "\n";
    });
});

この感じから言うと getComputedStyle でスタイルを取得して style 属性として設定している感じですね。

なので下記の様な修正が必要と思われます。

// スタイルを抽出して適用
{
  const a1 = richContent.querySelectorAll("*");
  const a2 = clone.querySelectorAll("*");
  for (let i = 0, max = a1.length; i < max; i++) {
    const e1 = a1[i];
    const e2 = a2[i];
    const styles = globalThis.getComputedStyle(e1);
    for (const key of styles) {
      if ((styles[key] ?? "") === "") continue;
      e2.style[key] = styles[key];
    }
  }
}

対応した playground は下記の様な感じです

以上。

確認環境

Microsoft Edge for Business
バージョン 131.0.2903.70 (64ビット)

補足

上記の方法だと下記の様な text/html がコピーされるのでまぁ、更に絞り込む必要がある気はします。
多分 一般的にコピーに必要のないスタイルは除外とかしているのではないでしょうか……?

最終的に出力されたtext/html
<html>
<body>
<!--StartFragment--><html><head></head><body><div id="message">
  <div class="tomato" style="accent-color: auto; place-content: normal; place-items: normal; place-self: auto; alignment-baseline: auto; anchor-name: none; anchor-scope: none; animation-composition: replace; animation: 0s ease 0s 1 normal none running none; app-region: none; appearance: none; backdrop-filter: none; backface-visibility: visible; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0); background-blend-mode: normal; baseline-shift: 0px; baseline-source: auto; block-size: 30px; border-block-end: 0px none rgb(255, 0, 0); border-block-start: 0px none rgb(255, 0, 0); border-color: rgb(255, 0, 0); border-radius: 0px; border-style: none; border-width: 0px; border-collapse: separate; border-end-end-radius: 0px; border-end-start-radius: 0px; border-image: none 100% / 1 / 0 stretch; border-inline-end: 0px none rgb(255, 0, 0); border-inline-start: 0px none rgb(255, 0, 0); border-start-end-radius: 0px; border-start-start-radius: 0px; inset: auto; box-decoration-break: slice; box-shadow: none; box-sizing: content-box; break-after: auto; break-before: auto; break-inside: auto; buffered-rendering: auto; caption-side: top; caret-color: rgb(255, 0, 0); clear: none; clip: auto; clip-path: none; clip-rule: nonzero; color: rgb(255, 0, 0); color-interpolation: srgb; color-interpolation-filters: linearrgb; color-rendering: auto; columns: auto; gap: normal; column-rule: 0px rgb(255, 0, 0); column-span: none; contain-intrinsic-block-size: none; contain-intrinsic-size: none; contain-intrinsic-inline-size: none; container: none; content: normal; cursor: auto; cx: 0px; cy: 0px; d: none; direction: ltr; display: block; dominant-baseline: auto; empty-cells: show; field-sizing: fixed; fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; filter: none; flex: 0 1 auto; flex-flow: row; float: none; flood-color: rgb(0, 0, 0); flood-opacity: 1; font-family: Meiryo; font-kerning: auto; font-optical-sizing: auto; font-palette: normal; font-size: 20px; font-size-adjust: none; font-stretch: 100%; font-style: normal; font-synthesis: weight style small-caps; font-variant: normal; font-weight: 700; grid: none; grid-area: auto; height: 30px; hyphenate-character: auto; hyphenate-limit-chars: auto; hyphens: manual; image-orientation: from-image; image-rendering: auto; initial-letter: normal; inline-size: 882px; inset-block: auto; inset-inline: auto; interpolate-size: numeric-only; isolation: auto; letter-spacing: normal; lighting-color: rgb(255, 255, 255); line-break: auto; line-height: normal; list-style: outside none disc; margin-block: 0px; margin: 0px; margin-inline: 0px; marker: none; mask: none; mask-type: luminance; math-depth: 0; math-shift: normal; math-style: normal; max-block-size: none; max-height: none; max-inline-size: none; max-width: none; min-block-size: 0px; min-height: 0px; min-inline-size: 0px; min-width: 0px; mix-blend-mode: normal; object-fit: fill; object-position: 50% 50%; object-view-box: none; offset: normal; opacity: 1; order: 0; orphans: 2; outline: rgb(255, 0, 0) none 0px; outline-offset: 0px; overflow-anchor: auto; overflow-clip-margin: 0px; overflow-wrap: normal; overflow: visible; overlay: none; overscroll-behavior-block: auto; overscroll-behavior-inline: auto; padding-block: 0px; padding: 0px; padding-inline: 0px; paint-order: normal; perspective: none; perspective-origin: 441px 15px; pointer-events: auto; position: static; position-anchor: auto; position-area: none; position-try: none; position-visibility: always; r: 0px; resize: none; rotate: none; ruby-align: space-around; ruby-position: over; rx: auto; ry: auto; scale: none; scroll-behavior: auto; scroll-margin-block: 0px; scroll-margin-inline: 0px; scroll-padding-block: auto; scroll-padding-inline: auto; scroll-timeline: none; scrollbar-color: auto; scrollbar-gutter: auto; scrollbar-width: auto; shape-image-threshold: 0; shape-margin: 0px; shape-outside: none; shape-rendering: auto; speak: normal; stop-color: rgb(0, 0, 0); stop-opacity: 1; stroke: none; stroke-dasharray: none; stroke-dashoffset: 0px; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-width: 1px; tab-size: 8; table-layout: auto; text-align: start; text-align-last: auto; text-anchor: start; text-decoration: none solid rgb(255, 0, 0); text-decoration-skip-ink: auto; text-emphasis: none rgb(255, 0, 0); text-emphasis-position: over; text-indent: 0px; text-overflow: clip; text-rendering: auto; text-shadow: none; text-size-adjust: auto; text-spacing-trim: normal; text-transform: none; text-underline-position: auto; text-wrap: wrap; timeline-scope: none; touch-action: auto; transform: none; transform-origin: 441px 15px; transform-style: flat; transition: all; translate: none; unicode-bidi: isolate; user-select: auto; vector-effect: none; vertical-align: baseline; view-timeline: none; view-transition-class: none; view-transition-name: none; visibility: visible; white-space-collapse: collapse; widows: 2; width: 882px; will-change: auto; word-break: normal; word-spacing: 0px; writing-mode: horizontal-tb; x: 0px; y: 0px; z-index: auto; zoom: 1; border-spacing: 0px; -webkit-border-image: none; -webkit-box-align: stretch; -webkit-box-decoration-break: slice; -webkit-box-direction: normal; -webkit-box-flex: 0; -webkit-box-ordinal-group: 1; -webkit-box-orient: horizontal; -webkit-box-pack: start; -webkit-font-smoothing: auto; -webkit-line-break: auto; -webkit-line-clamp: none; -webkit-locale: &quot;en&quot;; -webkit-mask-box-image-source: none; -webkit-mask-box-image-slice: 0 fill; -webkit-mask-box-image-width: auto; -webkit-mask-box-image-outset: 0; -webkit-mask-box-image-repeat: stretch; -webkit-print-color-adjust: economy; -webkit-rtl-ordering: logical; -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18); -webkit-text-combine: none; -webkit-text-decorations-in-effect: none; -webkit-text-fill-color: rgb(255, 0, 0); -webkit-text-orientation: vertical-right; -webkit-text-security: none; -webkit-text-stroke: 0px rgb(255, 0, 0); -webkit-user-drag: auto; -webkit-user-modify: read-only; -webkit-writing-mode: horizontal-tb;">トマト</div>
  <div style="color: rgb(0, 128, 0); font-size: 24px; font-weight: 700; accent-color: auto; place-content: normal; place-items: normal; place-self: auto; alignment-baseline: auto; anchor-name: none; anchor-scope: none; animation-composition: replace; animation: 0s ease 0s 1 normal none running none; app-region: none; appearance: none; backdrop-filter: none; backface-visibility: visible; background: none 0% 0% / auto repeat scroll padding-box border-box rgba(0, 0, 0, 0); background-blend-mode: normal; baseline-shift: 0px; baseline-source: auto; block-size: 36px; border-block-end: 0px none rgb(0, 128, 0); border-block-start: 0px none rgb(0, 128, 0); border-color: rgb(0, 128, 0); border-radius: 0px; border-style: none; border-width: 0px; border-collapse: separate; border-end-end-radius: 0px; border-end-start-radius: 0px; border-image: none 100% / 1 / 0 stretch; border-inline-end: 0px none rgb(0, 128, 0); border-inline-start: 0px none rgb(0, 128, 0); border-start-end-radius: 0px; border-start-start-radius: 0px; inset: auto; box-decoration-break: slice; box-shadow: none; box-sizing: content-box; break-after: auto; break-before: auto; break-inside: auto; buffered-rendering: auto; caption-side: top; caret-color: rgb(0, 128, 0); clear: none; clip: auto; clip-path: none; clip-rule: nonzero; color-interpolation: srgb; color-interpolation-filters: linearrgb; color-rendering: auto; columns: auto; gap: normal; column-rule: 0px rgb(0, 128, 0); column-span: none; contain-intrinsic-block-size: none; contain-intrinsic-size: none; contain-intrinsic-inline-size: none; container: none; content: normal; cursor: auto; cx: 0px; cy: 0px; d: none; direction: ltr; display: block; dominant-baseline: auto; empty-cells: show; field-sizing: fixed; fill: rgb(0, 0, 0); fill-opacity: 1; fill-rule: nonzero; filter: none; flex: 0 1 auto; flex-flow: row; float: none; flood-color: rgb(0, 0, 0); flood-opacity: 1; font-family: Meiryo; font-kerning: auto; font-optical-sizing: auto; font-palette: normal; font-size-adjust: none; font-stretch: 100%; font-style: normal; font-synthesis: weight style small-caps; font-variant: normal; grid: none; grid-area: auto; height: 36px; hyphenate-character: auto; hyphenate-limit-chars: auto; hyphens: manual; image-orientation: from-image; image-rendering: auto; initial-letter: normal; inline-size: 882px; inset-block: auto; inset-inline: auto; interpolate-size: numeric-only; isolation: auto; letter-spacing: normal; lighting-color: rgb(255, 255, 255); line-break: auto; line-height: normal; list-style: outside none disc; margin-block: 0px; margin: 0px; margin-inline: 0px; marker: none; mask: none; mask-type: luminance; math-depth: 0; math-shift: normal; math-style: normal; max-block-size: none; max-height: none; max-inline-size: none; max-width: none; min-block-size: 0px; min-height: 0px; min-inline-size: 0px; min-width: 0px; mix-blend-mode: normal; object-fit: fill; object-position: 50% 50%; object-view-box: none; offset: normal; opacity: 1; order: 0; orphans: 2; outline: rgb(0, 128, 0) none 0px; outline-offset: 0px; overflow-anchor: auto; overflow-clip-margin: 0px; overflow-wrap: normal; overflow: visible; overlay: none; overscroll-behavior-block: auto; overscroll-behavior-inline: auto; padding-block: 0px; padding: 0px; padding-inline: 0px; paint-order: normal; perspective: none; perspective-origin: 441px 18px; pointer-events: auto; position: static; position-anchor: auto; position-area: none; position-try: none; position-visibility: always; r: 0px; resize: none; rotate: none; ruby-align: space-around; ruby-position: over; rx: auto; ry: auto; scale: none; scroll-behavior: auto; scroll-margin-block: 0px; scroll-margin-inline: 0px; scroll-padding-block: auto; scroll-padding-inline: auto; scroll-timeline: none; scrollbar-color: auto; scrollbar-gutter: auto; scrollbar-width: auto; shape-image-threshold: 0; shape-margin: 0px; shape-outside: none; shape-rendering: auto; speak: normal; stop-color: rgb(0, 0, 0); stop-opacity: 1; stroke: none; stroke-dasharray: none; stroke-dashoffset: 0px; stroke-linecap: butt; stroke-linejoin: miter; stroke-miterlimit: 4; stroke-opacity: 1; stroke-width: 1px; tab-size: 8; table-layout: auto; text-align: start; text-align-last: auto; text-anchor: start; text-decoration: none solid rgb(0, 128, 0); text-decoration-skip-ink: auto; text-emphasis: none rgb(0, 128, 0); text-emphasis-position: over; text-indent: 0px; text-overflow: clip; text-rendering: auto; text-shadow: none; text-size-adjust: auto; text-spacing-trim: normal; text-transform: none; text-underline-position: auto; text-wrap: wrap; timeline-scope: none; touch-action: auto; transform: none; transform-origin: 441px 18px; transform-style: flat; transition: all; translate: none; unicode-bidi: isolate; user-select: auto; vector-effect: none; vertical-align: baseline; view-timeline: none; view-transition-class: none; view-transition-name: none; visibility: visible; white-space-collapse: collapse; widows: 2; width: 882px; will-change: auto; word-break: normal; word-spacing: 0px; writing-mode: horizontal-tb; x: 0px; y: 0px; z-index: auto; zoom: 1; border-spacing: 0px; -webkit-border-image: none; -webkit-box-align: stretch; -webkit-box-decoration-break: slice; -webkit-box-direction: normal; -webkit-box-flex: 0; -webkit-box-ordinal-group: 1; -webkit-box-orient: horizontal; -webkit-box-pack: start; -webkit-font-smoothing: auto; -webkit-line-break: auto; -webkit-line-clamp: none; -webkit-locale: &quot;en&quot;; -webkit-mask-box-image-source: none; -webkit-mask-box-image-slice: 0 fill; -webkit-mask-box-image-width: auto; -webkit-mask-box-image-outset: 0; -webkit-mask-box-image-repeat: stretch; -webkit-print-color-adjust: economy; -webkit-rtl-ordering: logical; -webkit-tap-highlight-color: rgba(0, 0, 0, 0.18); -webkit-text-combine: none; -webkit-text-decorations-in-effect: none; -webkit-text-fill-color: rgb(0, 128, 0); -webkit-text-orientation: vertical-right; -webkit-text-security: none; -webkit-text-stroke: 0px rgb(0, 128, 0); -webkit-user-drag: auto; -webkit-user-modify: read-only; -webkit-writing-mode: horizontal-tb;">ピーマン</div>
</div></body></html><!--EndFragment-->
</body>
</html>
1
1
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
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?