Clipboard

Clipboard API

๋ธŒ๋ผ์šฐ์ €์—์„œ ํด๋ฆฝ๋ณด๋“œ๋ฅผ ์กฐ์ž‘ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ๋˜๋Š” API ์ง‘ํ•ฉ

  • ํด๋ฆฝ๋ณด๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์–ด์„œ ๋ณต์‚ฌ, ๋ถ™์—ฌ๋„ฃ๊ธฐ ๋“ฑ์˜ ๊ธฐ๋Šฅ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ํ…์ŠคํŠธ, ์ด๋ฏธ์ง€ ๋ฐ ๊ธฐํƒ€ ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

  • API๋Š” ์‚ฌ์šฉ์ž์˜ ๊ฐœ์ธ์ •๋ณด๋ฅผ ์กด์ค‘ํ•˜๋„๋ก ์„ค๊ณ„๋˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์— ํด๋ฆฝ๋ณด๋“œ์—์„œ์˜ ์ฝ๋Š” ๋“ฑ์˜ ์ž‘์—…์—๋Š” ์‚ฌ์šฉ์ž์˜ ์Šน์ธ์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋งŽ๋‹ค.

const clipBoard = navigator.clipboard;

์ด์ „์—๋Š” ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ์—์„œ document.exeCommand ๋ฅผ ์‚ฌ์šฉํ•ด OS Clipboard์— ์•ก์„ธ์Šคํ–ˆ์ง€๋งŒ, ๋‹ค์–‘ํ•œ ๋ณด์•ˆ ๋ฐ ์„ฑ๋Šฅ ๋ฌธ์ œ๋กœ Clipboard API๋กœ ๋Œ€์ฒด๋˜์—ˆ๋‹ค.

Interface

ํด๋ฆฝ๋ณด๋“œ์˜ ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ์ธํ„ฐํŽ˜์ด์Šค

  • ํด๋ฆฝ๋ณด๋“œ ์ธํ„ฐํŽ˜์ด์Šค๋Š” ์šด์˜ ์ฒด์ œ(Window, macOS) ํด๋ฆฝ๋ณด๋“œ์— ์‚ฌ์šฉ๋จ

  • writeText(text: string): Promise<void> -> ํ…์ŠคํŠธ๋ฅผ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ (๋น„๋™๊ธฐ)

    • ํด๋ฆฝ๋ณด๋“œ์— ์“ฐ๋Š” ์ž‘์—…์˜ ๊ฒฝ์šฐ, ๋ฒ„ํŠผ ํด๋ฆญ๊ณผ ๊ฐ™์€ ์‚ฌ์šฉ์ž ์ œ์Šค์ฒ˜์˜ ๊ฒฐ๊ณผ์ธ ๊ฒฝ์šฐ ๋ธŒ๋ผ์šฐ์ €๋Š” ์ผ๋ฐ˜์ ์œผ๋กœ ๋ช…์‹œ์ ์ธ ๋ฉ”์‹œ์ง€ ์—†์ด ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•œ๋‹ค.

    • ํ•˜์ง€๋งŒ ์ž ์žฌ์ ์ธ ์˜ค๋ฅ˜๋‚˜ ๊ถŒํ•œ ๋ฌธ์ œ๋ฅผ ์ธ์‹ํ•˜๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ์ข‹๋‹ค.

  • readText(): Promise<T> -> ํด๋ฆฝ๋ณด๋“œ์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ

    • ํด๋ฆฝ๋ณด๋“œ๊ฐ€ ๋น„์–ด ์žˆ๊ฑฐ๋‚˜, ํ…์ŠคํŠธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š๊ฑฐ๋‚˜, ํด๋ฆฝ๋ณด๋“œ์˜ ๋‚ด์šฉ์„ ๋‚˜ํƒ€๋‚ด๋Š” ๊ฐ์ฒด ์ค‘ ํ…์ŠคํŠธ ํ‘œํ˜„์ด ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์€ ๊ฒฝ์šฐ ๋นˆ ๋ฌธ์ž์—ด์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค.

    • ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ๋ธŒ๋ผ์šฐ์ €๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์•ฑ์— ํด๋ฆฝ๋ณด๋“œ์—์„œ ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ–ˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.

    • ์ฝ์„ ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ์„ ๋ถ€์—ฌํ–ˆ๋Š”์ง€ ํ™•์ธ์ด ํ•„์š”ํ•˜๊ธฐ์— ๋ช…์‹œ์ ์œผ๋กœ ์—๋Ÿฌ์ฒ˜๋ฆฌ๋ฅผ ํ•ด์ฃผ์–ด์•ผ ํ•œ๋‹ค.

write() vs writeText(), read() vs readText()

  • write(data: ClipboardItem): ClipboardItem ๊ฐ์ฒด๋ฅผ ๋ฐ›์•„ ํด๋ฆฝ๋ณด๋“œ์— ์“ฐ์ด๋Š” ๋ฐ์‚ฌ์šฉ๋œ๋‹ค.

    • ๋‹ค์–‘ํ•œ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ ๊ฐ€๋Šฅ

  • writeText(data: string): ๋‹จ์ˆœ ํ…์ŠคํŠธ๋ฅผ ํด๋ฆฝ๋ณด๋“œ์— ์“ฐ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ์‚ฌ์šฉ

  • read(): Promise<ClipboardItem> : ClipboardItem ๊ฐ์ฒด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋ฉฐ, ClipboardItem ๊ฐ์ฒด๋Š” ํด๋ฆฝ๋ณด๋“œ์— ์žˆ๋Š” ๋ฐ์ดํ„ฐ์™€ ํ•ด๋‹น ํ˜•์‹์˜ ๋Œ€ํ•œ ์ •๋ณด๋ฅผ ์ œ๊ณต

  • readText() Promise<string>: ํด๋ฆฝ๋ณด๋“œ์—์„œ ํ…์ŠคํŠธ๋ฅผ ์ฝ์–ด์˜จ๋‹ค.

์žฅ์ 

  • ๋ธŒ๋ผ์šฐ์ €์—์„œ ํด๋ฆฝ๋ณด๋“œ ์กฐ์ž‘์„ ์ง€์›ํ•˜์—ฌ ๋ณต์‚ฌ, ์ž˜๋ผ๋‚ด๊ธฐ, ๋ถ™ํ˜€๋„ฃ๊ธฐ๋ฅผ ์‰ฝ๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๋‹ค์–‘ํ•œ ์œ ํ˜•์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๋‹ค์–‘ํ•œ ์œ ํ˜•์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ๊ธฐ ์ง€์›ํ•œ๋‹ค.

๋‹จ์ 

  • ํ˜ธํ™˜์„ฑ ๋ฌธ์ œ๋กœ ์ผ๋ถ€ ์‚ฌ์šฉ์ž๋Š” ๊ด€๋ จ ๊ธฐ๋Šฅ์„ ์‚ฌ์šฉํ•˜์ง€ ๋ชปํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ํด๋ฆฝ๋ณด๋“œ๋ฅผ ์ž‘๋™ํ•˜๊ธฐ ์œ„ํ•ด ์‚ฌ์šฉ์ž์˜ ์Šน์ธ์„ ์–ป์–ด์•ผ ํ•˜๋Š” ๊ฒฝ์šฐ ๋ถˆํ•„์š”ํ•œ ๊ฐ„์„ญ์ด ๋ฐœ์ƒ(ํŒ์—…)

  • ์•…์„ฑ ์›น์‚ฌ์ดํŠธ๊ฐ€ ์‚ฌ์šฉ์ž์˜ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌํ•œ ๋ฏผ๊ฐํ•œ ์ •๋ณด์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ๋“ฑ ๋ณด์•ˆ์˜ ๋ฌธ์ œ๊ฐ€ ๋˜๋Š” ๊ฒฝ์šฐ๋„ ์กด์žฌ

์ฃผ์˜์‚ฌํ•ญ

  • ํด๋ฆฝ๋ณด๋“œ ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ๋Š” ์‚ฌ์šฉ์ž์˜ ์Šน์ธ์„ ๋จผ์ € ๋ฐ›์•„์•ผ ํ•œ๋‹ค.

  • ํด๋ฆฝ๋ณด๋“œ ๋ฐ์ดํ„ฐ๋ฅผ ์ฒ˜๋ฆฌํ•  ๋•Œ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์ง€ ์•Š๋„๋ก ๋ฐ์ดํ„ฐ ์œ ํ˜•์˜ ์ฃผ์˜ ํ•„์š”

  • ์‚ฌ์šฉ์ž ๋ชจ๋ฅด๊ฒŒ ๋˜๋Š” ๋™์˜ ์—†์ด ํด๋ฆฝ๋ณด๋“œ์—์„œ ์ฝ๊ฑฐ๋‚˜ ์“ฐ๋Š” ๊ฒƒ์€ ์‹ฌ๊ฐํ•œ ๋ณด์•ˆ ์œ„ํ—˜์ด ๋  ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ ํ•ญ์ƒ ํด๋ฆฝ๋ณด๋“œ์— ์•ก์„ธ์Šคํ•  ์ˆ˜ ์žˆ๋Š” ๊ถŒํ•œ๊ณผ ์ •๋‹น์‚ฌ์œ ๊ฐ€ ์žˆ๋Š”์ง€ ํ™•์ธ์ด ํ•„์š”ํ•˜๋‹ค.

์ œํ•œ์‚ฌํ•ญ

  • ํด๋ฆญ ์ด๋ฒคํŠธ ์™ธ๋ถ€์—์„œ๋Š” ํ•ด๋‹น ๋ฉ”์„œ๋“œ๋ฅผ ํ˜ธ์ถœํ•  ์ˆ˜ ์—†๋‹ค.

  • ์•…์„ฑ ํ”„๋กœ๊ทธ๋žจ์ด ์‚ฌ์šฉ์ž ๋ชฐ๋ž˜ ์Šคํฌ๋ฆฝํŠธ๋‚˜ ๊ธฐํƒ€ ์•…์„ฑ ์ฝ”๋“œ๋ฅผ ์‚ฌ์šฉ์ž์˜ ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌํ•˜์ง€ ๋ชปํ•˜๋„๋ก ํ•˜๊ธฐ ์œ„ํ•œ ๋ณด์•ˆ ์กฐ์น˜

    • Failed to copy! DOMException: Document is not focused

ClipboardItem

Clipboard API์—์„œ ์‚ฌ์šฉ๋˜๋Š” ๊ฐ์ฒด๋กœ, ํด๋ฆฝ๋ณด๋“œ์— ์“ฐ๊ฑฐ๋‚˜ ์ฝ์„ ๋ฐ์ดํ„ฐ๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.

  • ํด๋ฆฝ๋ณด๋“œ์— ์ผ๋ฐ˜ ํ…์ŠคํŠธ๊ฐ€ ์•„๋‹Œ ๋‹ค๋ฅธ ๊ฒƒ์„ ๋„ฃ์œผ๋ ค๋ฉด ClipboardItem์„ ์‚ฌ์šฉํ•˜์—ฌ ์ผ์น˜ํ•˜๋Š” MIME ์œ ํ˜•์„ ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค

  • type: ํด๋ฆฝ๋ณด๋“œ ์•„์ดํ…œ์˜ ์œ ํ˜•์„ ์„ค๋ช…ํ•˜๋Š” ๋ฌธ์ž์—ด์˜ ๋ฐฐ์—ด

    • ํด๋ฆฝ๋ณด๋“œ์˜ ์•„์ดํ…œ์ด ์–ด๋–ค ์ข…๋ฅ˜์˜ ๋ฐ์ดํ„ฐ๋ฅผ ํฌํ•จํ•˜๊ณ  ์žˆ๋Š”์ง€๋ฅผ ๋‚˜ํƒ€๋‚ธ๋‹ค.

    • Text์˜ ๊ฒฝ์šฐ ["text/plain"] ๊ณผ ๊ฐ™์€ ๊ฐ’์„ ๊ฐ–์Œ

  • getType(): ํŠน์ • ๋ฐ์ดํ„ฐ ์œ ํ˜•์— ๋Œ€ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ๋น„๋™๊ธฐ์ ์œผ๋กœ ๊ฐ€์ ธ์˜ค๋Š” ๋ฉ”์„œ๋“œ

  • getTypeAsString(): getType๊ณผ ๋น„์Šทํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฌธ์ž์—ด๋กœ ๋ฐ›๋Š”๋‹ค.

const clipboardItem = new ClipboardItem({
  [MIME_TYPE]: new Blob([stream], { type: MIME_TYPE }),
})

  • Clipboard API ์ด์ „์—๋Š” ํด๋ฆฝ๋ณด๋“œ์— ๋ณต์‚ฌ ๋ถ™์—ฌ๋„ฃ์„ ์ˆ˜ ์žˆ๋Š” MIME ์œ ํ˜•์œผ๋กœ text/plain, text/html, image/png ๋งŒ ์ง€์›ํ–ˆ๋‹ค.

  • ๋ธŒ๋ผ์šฐ์ €์— ๋‚ด์žฅ๋œ ์Šคํฌ๋ฆฝํŠธ ๊ตฌ์„ฑ์š”์†Œ๋‚˜ ์ž๋ฐ”์Šคํฌ๋ฆฝํŠธ๋ฅผ ์ œ๊ฑฐํ•˜๊ฑฐ๋‚˜ PNG decompression bomb assaults์„ ๋ง‰๊ธฐ ์œ„ํ•ด ์ฒ˜๋ฆฌ ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์•—๋‹ค.

  • ํด๋ฆฝ๋ณด๋“œ API๋Š” ์ด์ œ ์›น ์‚ฌ์šฉ์ž ์ง€์ • ํ˜•์‹์„ ์ง€์›ํ•˜๋ฏ€๋กœ ๊ฐœ๋ฐœ์ž๊ฐ€ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ์›น ์‚ฌ์šฉ์ž ์ •์˜ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋ฉด JSON.stringify() ์™€ ํ•จ๊ป˜ ์‚ฌ์šฉํ•˜์—ฌ ์›น์—์„œ ์ƒˆ๋กœ์šด ๋ฐ์ดํ„ฐ ํ˜•์‹์„ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค.

  • ๊ธฐ๋ณธ์ ์œผ๋กœ ํ…์ŠคํŠธ ํ˜•์‹์œผ๋กœ ๋ณ€ํ™˜ํ•˜์ง€ ์•Š๊ณ  ๋‹ค๋ฅธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์œผ๋กœ ์ง์ ‘ ๋ณต์‚ฌํ•  ์ˆ˜ ์žˆ๋‹ค.

์ •์˜ํ•˜๊ธฐ

  • ํด๋ฆฝ๋ณด๋“œ์— ์›น ์‚ฌ์šฉ์ž ์ •์˜ ํ˜•์‹์„ ์‚ฌ์šฉํ•˜๋ ค๋ฉด Blob์˜ MIM ์œ ํ˜•์˜ ์ ‘๋‘์‚ฌ๋กœ 'web' ์„ ์ž‘์„ฑํ•ด์•ผ ํ•œ๋‹ค.

๋ฐ์ดํ„ฐ ์ฝ๊ธฐ

  • ์‚ฌ์šฉ์ž ์ •์˜ ํ˜•์‹์˜ ๋ฐ์ดํ„ฐ๋ฅผ ์ฝ์œผ๋ ค๋ฉด web ์œผ๋กœ ์‹œ์ž‘ํ•˜๋Š” ํด๋ฆฝ๋ณด๋“œ ๊ฐœ์ฒด๋ฅผ ๊ฒ€์ƒ‰ํ•ด์•ผ ํ•œ๋‹ค.

try {
  const clipboardItems = await navigator.clipboard.read();
  for (const clipboardItem of clipboardItems) {
    for (const type of clipboardItem.types) {
      if (!type.startsWith('web ')) {
        continue;
      }
      const blob = await clipboardItem.getType(type);
    }
  }
} catch (err) {
  console.error(err.name, err.message);
}

์›น ์‚ฌ์šฉ์ž ์ •์˜ ํ˜•์‹์€ ํ‘œ์ค€์ด๋‹ค.

ํด๋ฆฝ๋ณด๋“œ API์˜ ๋ชฉ์ ์€ ์‚ฌ์šฉ์ž๊ฐ€ ํŒŒ์ผ ํ˜•์‹์„ ๋งŒ๋“œ๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ํ•œ ์†Œ์Šค์˜ ์ฝ˜ํ…์ธ ๋ฅผ ๋‹ค๋ฅธ ์†Œ์Šค์˜ ์ž„์‹œ ์ €์žฅ ๊ณต๊ฐ„์— ์ง์ ‘ ๋ณต์‚ฌํ•˜๋Š” ๊ฒƒ, ์ฆ‰ ๋ถ™์—ฌ๋„ฃ๊ธฐ๋ผ๊ณ  ํ•˜๋Š” ํ”„๋กœ์„ธ์Šค๋ฅผ ์ง€์›ํ•˜๋Š” ๊ฒƒ

์ง€์›ํ•˜์ง€ ์•Š๋Š” ํ™˜๊ฒฝ์—์„œ ์ฒ˜๋ฆฌ

try {
  const permission = await navigator.permissions.query({ name: 'clipboard-read' });
  if (permission.state === 'denied') {
    throw new Error('Not allowed to read clipboard.');
  }
  const clipboardContents = await navigator.clipboard.read();
  for (const item of clipboardContents) {
    // do things with the clipboard entries
  }
} catch (error) {
  console.error(error.message);
}

Last updated