pencere – À̹ÌÁö·µ¿¿µ»ó·iframe Áö¿ø Á¢±Ù¼º °¶·¯¸® ¶óÀÌÆ®¹Ú½º
¡Ø "pencere"´Â ÅÍŰ¾î·Î "â¹®"À̶ó´Â ¶æÀÔ´Ï´Ù.

# ªÀº ¼³¸í
JavaScript/TypeScript ±â¹ÝÀÇ ¹Ìµð¾î ºä¾î ¶óÀ̺귯¸®·Î, À̹ÌÁö·µ¿¿µ»ó·iframe·Ä¿½ºÅÒ HTML ÄÜÅÙÃ÷¸¦ ¹ÝÀÀÇü °¶·¯¸® ¶óÀÌÆ®¹Ú½º·Î Ç¥½ÃÇÕ´Ï´Ù. View Transitions API¸¦ Ȱ¿ëÇØ ½æ³×ÀÏ¿¡¼ ¶óÀÌÆ®¹Ú½º·Î ºÎµå·´°Ô ÀüȯµÇ´Â ¸ðÇÎ È¿°ú¸¦ Á¦°øÇϸç, ÇÁ·¹ÀÓ¿öÅ©¿¡ Á¾¼ÓµÇÁö ¾ÊÀ¸¸é¼µµ React, Vue, Svelte, Solid, Web Components¿ë ¾î´ðÅ͸¦ ¸ðµÎ Áö¿øÇÕ´Ï´Ù.
# ¶óÀ̼¾½º
MIT License (»ó¾÷Àû ÀÌ¿ë °¡´É, ÀÚÀ¯·Î¿î ¼öÁ¤·¹èÆ÷ °¡´É)
# ¼³Ä¡¹æ¹ý
¨ç NPM ¼³Ä¡
npm install pencere
import { PencereViewer } from "pencere"
¨è CDN ¹æ½Ä
<script type="module" src="https://cdn.jsdelivr.net/npm/pencere/dist/index.mjs" crossorigin="anonymous" ></script>
¨é JavaScript·Î °¶·¯¸® »ý¼º
const viewer = new PencereViewer({ items: [ { type: "image", src: "/photos/coast.jpg", alt: "Coastal cliffs", caption: "Pacific Coast" }, { type: "image", src: "/photos/forest.jpg", alt: "Redwood forest" }, ], loop: true, viewTransition: true, // ½æ³×ÀÏ→¶óÀÌÆ®¹Ú½º ¸ðÇÎ È¿°ú routing: true, // URL ÇØ½Ã(#p1, #p2)¿¡ ½½¶óÀ̵å À§Ä¡ ±â·Ï }) document.querySelector('#gallery-trigger') ?.addEventListener('click', (e) => viewer.open(0, e.currentTarget))
¨ê HTML ¼±¾ðÀû »ç¿ë (bindPencere)
HTML ¸¶Å©¾÷¸¸À¸·Î ÀÚµ¿ °¶·¯¸® ±×·ìȰ¡ °¡´ÉÇÕ´Ï´Ù.
<a href="/photos/coast.jpg" data-pencere data-gallery="nature" data-caption="Pacific Coast"> <img src="/photos/coast-thumb.jpg" alt="Coastal cliffs" /> </a> <a href="/photos/forest.jpg" data-pencere data-gallery="nature" data-caption="Redwood Forest"> <img src="/photos/forest-thumb.jpg" alt="Redwood forest" /> </a> <script type="module"> import { bindPencere } from "pencere" const unbind = bindPencere("[data-pencere]") </script>
¨ë ÇÁ·¹ÀÓ¿öÅ©º° »ç¿ë
- React:
import { useLightbox } from "pencere/react"
- Vue:
import { usePencere } from "pencere/vue"
- Svelte:
import { pencere } from "pencere/svelte" (use: µð·ºÆ¼ºê)
- Web Component:
<pencere-lightbox> Ä¿½ºÅÒ ¿¤¸®¸ÕÆ® µî·Ï
# Áß¿ä ¿É¼Ç
| ¿É¼Ç¸í |
¼³¸í |
±âº»°ª |
items |
Ç¥½ÃÇÒ ÄÜÅÙÃ÷ ¹è¿ (typeÀº image/video/iframe/html) |
Çʼö |
startIndex |
óÀ½ ¿ ½½¶óÀ̵å À妽º (0ºÎÅÍ) |
0 |
loop |
¸¶Áö¸·→óÀ½ ¼øÈ¯ Ž»ö |
false |
container |
ºä¾î¸¦ ¸¶¿îÆ®ÇÒ Æ¯Á¤ DOM ¿ä¼Ò |
body |
strings / i18n |
UI ¹®ÀÚ¿ °³º° ¿À¹ö¶óÀ̵å / Àüü ¹ø¿ª ÇÔ¼ö |
- |
keyboard |
Ű ¹ÙÀεù Àç¸ÅÇÎ ¶Ç´Â ºñȰ¼ºÈ |
- |
image |
»ý¼ºµÇ´Â <img>ÀÇ crossOrigin, referrerPolicy ¼³Á¤ |
- |
reducedMotion |
¾Ö´Ï¸ÞÀÌ¼Ç Á¤Ã¥ (auto / always / never) |
auto |
useNativeDialog |
³×ÀÌÆ¼ºê <dialog> ¿ä¼Ò·Î ·»´õ¸µ |
true |
lockScroll |
ºä¾î ¿¸² Áß ÆäÀÌÁö ½ºÅ©·Ñ Àá±Ý |
- |
nonce |
CSP nonce (±¸Çü ºê¶ó¿ìÀú¿ë fallback <style>¿¡ Àû¿ë) |
- |
dir |
ÅØ½ºÆ® ¹æÇâ (ltr / rtl / auto) |
auto |
haptics |
ÅÍÄ¡ µð¹ÙÀ̽º Áøµ¿ Çǵå¹é Ȱ¼ºÈ |
false |
routing |
URL ÇØ½Ã¿¡ ½½¶óÀ̵å À§Ä¡ ±â·Ï (#p1, #p2…) |
false |
fullscreen |
Àüüȸé ÄÁÆ®·Ñ ³ëÃâ (iOS Safari´Â CSS fallback) |
false |
viewTransition |
View Transitions API·Î ½æ³×ÀÏ→ºä¾î ¸ðÇΠȰ¼ºÈ |
false |
renderers |
Ä¿½ºÅÒ ÄÜÅÙÃ÷ ·»´õ·¯ µî·Ï (3D ¸ðµ¨ µî) |
[ ] |
ÁÖ¿ä API ¸Þ¼µå
| ¸Þ¼µå |
¼³¸í |
viewer.open(index, thumbnailElement) |
ÁöÁ¤ À妽º·Î ¿±â (½æ³×ÀÏ ¿ä¼Ò¸¦ µÎ¹øÂ° ÀÎÀÚ·Î ³Ñ±â¸é ¸ðÇÎ È¿°ú Ȱ¼ºÈ) |
viewer.close() |
ºä¾î ÇÁ·Î±×·¡¹Ö ¹æ½Ä ´Ý±â |
viewer.next() / viewer.prev() |
´ÙÀ½/ÀÌÀü ½½¶óÀ̵å |
viewer.goTo(index) |
ƯÁ¤ À妽º·Î À̵¿ |
viewer.openFromLocation() |
URL ÇØ½Ã ±â¹ÝÀ¸·Î ÇØ´ç ½½¶óÀÌµå ¿±â |
viewer.enterFullscreen() / viewer.toggleFullscreen() |
Àüüȸé ÁøÀÔ / Åä±Û |
¶óÀÌÇÁ»çÀÌŬ À̺¥Æ®
willOpen, change, slideLoad, didRender, didNavigate, close (close ½Ã reason: escape / backdrop / user / api)
# Ư¡ ¹× ¼³¸í
ÄÜÅÙÃ÷ ŸÀÔ
- À̹ÌÁö, µ¿¿µ»ó, iframe, Ä¿½ºÅÒ HTMLÀ» ´ÜÀÏ ¶óÀÌÆ®¹Ú½º¿¡¼ ¸ðµÎ ó¸®
- Ä¿½ºÅÒ ·»´õ·¯ µî·Ï °¡´É (¿¹:
<model-viewer> °°Àº 3D ¸ðµ¨ À¥ ÄÄÆ÷³ÍÆ®)
<picture> ÅÂ±× ÀÚµ¿ »ý¼ºÀ¸·Î AVIF/WebP Çü½Ä ÀÚµ¿ Çù»ó (sources ¹è¿ »ç¿ë ½Ã)
srcset, sizes·Î ¹ÝÀÀÇü À̹ÌÁö Áö¿ø
- ThumbHash·BlurHash Ç÷¹À̽ºÈ¦´õ Å©·Î½ºÆäÀ̵å
¸ð¼Ç & ÀÎÅÍ·¢¼Ç
- View Transitions API ±â¹Ý ³×ÀÌÆ¼ºê ½æ³×ÀÏ→¶óÀÌÆ®¹Ú½º ¸ðÇÎ È¿°ú
- ÇÉÄ¡ ÁÜ, ´õºíÅÇ Åä±Û, ¸¶¿ì½º ÈÙ ÁÜ, È®´ë À̹ÌÁö µå·¡±× ÆÒ
- ¾Æ·¡·Î ½º¿ÍÀÌÇÁÇØ¼ ´Ý±â (¹è°æ ÆäÀ̵å È¿°ú)
- Àüüȸé API Áö¿ø (iOS Safari´Â CSS °íÁ¤ À§Ä¡ ¿À¹ö·¹ÀÌ·Î fallback)
Á¢±Ù¼º (WCAG 2.2 AA ¿ÏÀü Áؼö)
- Æ÷Ä¿½º °ü¸®, 44×44px ÅÍÄ¡ Ÿ°Ù, Űº¸µå »ç¿ëÀÚ¿ë µå·¡±× ´ëü ¼ö´Ü
- IME ¾ÈÀü Űº¸µå ´ÜÃàŰ (ÇÑ±Û µî Á¶ÇÕ ÀÔ·Â Áß¿¡´Â ´ÜÃàŰ ¹«½Ã)
prefers-reduced-motion ¹Ìµð¾î Äõ¸® ÀÚµ¿ ¹ÝÀÀ
- 14°³ ¾ð¾î ³»Àå ¹ø¿ª (Çѱ¹¾î, ÀϺ»¾î, Áß±¹¾î °£Ã¼, ¾Æ¶ø¾î µî Æ÷ÇÔ)
- RTL ÀÚµ¿ °¨Áö → È»ìÇ¥·½º¿ÍÀÌÇÁ··¹À̾ƿô ÀÚµ¿ ¹ÝÀü (¾Æ¶ø¾î·È÷ºê¸®¾î)
¶ó¿ìÆÃ & °øÀ¯
- ÇØ½Ã ±â¹Ý µö¸µÅ©: ½½¶óÀÌµå º¯°æ ½Ã URL fragment(#p1, #p2) ÀÚµ¿ ±â·Ï
- ÆäÀÌÁö ·Îµå ½Ã URL ÇØ½Ã ÀÐ¾î¼ ÇØ´ç ½½¶óÀ̵å ÀÚµ¿ ¿ÀÇÂ
- ºê¶ó¿ìÀú µÚ·Î°¡±â ¹öưÀÌ ºä¾î¸¦ ´ÝÀ½ (popstate °¨Áö)
- ÇØ½Ã ÆÐÅÏ Ä¿½ºÅ͸¶ÀÌ¡ °¡´É
º¸¾È & ¹èÆ÷ ¾ÈÁ¤¼º
- Strict CSP ȣȯ:
adoptedStyleSheets »ç¿ëÀ¸·Î style-src ¿ìȸ (Chrome 73+, Firefox 101+, Safari 16.4+)
- ±¸Çü ºê¶ó¿ìÀú´Â nonce ±â¹Ý fallback
<style> Áö¿ø
- HTML ĸ¼Ç¿ë Trusted Types Á¤Ã¥ ÇïÆÛ Á¦°ø
- SLSA °ËÁõµÈ npm ¸±¸®½º, CDN¿ë SRI ÇØ½Ã Á¦°ø
Å׸¶ & Ä¿½ºÅ͸¶ÀÌ¡
- ¸ðµç ½Ã°¢ ¼Ó¼ºÀ» CSS Ä¿½ºÅÒ ÇÁ·ÎÆÛƼ·Î ³ëÃâ (
--pc-bg, --pc-fg, --pc-font, --pc-focus µî)
- ¾îµð¼µç CSS cascade·Î ¿À¹ö¶óÀÌµå °¡´É
ÇÁ·¹ÀÓ¿öÅ© Áö¿ø
- ÇÁ·¹ÀÓ¿öÅ© ¹«°ü ÄÚ¾î + React, Vue, Svelte, Solid, Web Components¿ë ¾î´ðÅÍ º°µµ Á¦°ø
- ¸ð¹ÙÀÏ È¯°æ¿¡¼ ¿É¼Ç Ȱ¼ºÈ ½Ã ÇÝÆ½ Çǵå¹é (Vibration API)
Ãßõ Ȱ¿ëó
Æ÷Æ®Æú¸®¿À »çÀÌÆ®(½æ³×ÀÏ ±×¸®µå¿¡¼ Ç®ÇØ»óµµ ¸ðÇÎ ¿À¹ö·¹ÀÌ), ÀÌÄ¿¸Ó½º »óǰ ÆäÀÌÁö(À̹ÌÁö·µ¿¿µ»ó·3D ¸ðµ¨À» ÇϳªÀÇ ¶óÀÌÆ®¹Ú½º¿¡), ´º½º·¿¡µðÅ丮¾ó »çÀÌÆ®(¿ÜºÎ URL¿¡¼ ƯÁ¤ °¶·¯¸® ½½¶óÀ̵å·Î µö¸µÅ©).
Âü°í ¸µÅ©
|