HTML:Examples
HTML/JavaScript/CSS 코드 샘플들.
테이블 크기 고정
-
style="table-layout:fixed"
- 테이블의 크기를 고정. 가로세로 모두 고정
-
style="word-break:break-all;"
- 고정된 테이블에 긴 글을 넣을경우 가로가 고정되어 있으므로 글자가 잘려 보이는 경우가 발생.이것을 방지하고 글의 자동 줄바꿈 효과.
-
height="auto"
- 고정된 테이블의 세로때문에 글이 고정된 세로길이까지만 보인다. 이것을 방지하기위해 세로의 길이를 오토로 조정해줌.
Redirection URL html
아래와 같이 추가하면 8초후에 http://naver.com 으로 자동 이동된다.
Base64 이미지 출력
<div>
<p>Taken from wikpedia</p>
<img src="data:image/png;base64, iVBORw0KGgoAAAANSUhEUgAAAAUA
AAAFCAYAAACNbyblAAAAHElEQVQI12P4//8/w38GIAXDIBKE0DHxgljNBAAO
9TXL0Y4OHwAAAABJRU5ErkJggg==" alt="Red dot" />
</div>
스크롤 최하단 이동
var chatHistory = document.getElementById("messageBody");
chatHistory.scrollTop = chatHistory.scrollHeight;
놀라운 앱을 위한 새로운 패턴: 클립보드 패턴
A collection of common patterns for dealing with the clipboard.
이미지 복사 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to copy images</title>
</head>
<body>
<h1>How to copy images</h1>
<img src="assets/fugu.svg" alt="Fugu fish." width="128" height="128">
<button type="button">Copy</button>
</body>
</html>
const button = document.querySelector('button');
const img = document.querySelector('img');
button.addEventListener('click', async () => {
const responsePromise = fetch(img.src);
try {
if ('write' in navigator.clipboard) {
await navigator.clipboard.write([
new ClipboardItem({
'image/svg+xml': new Promise(async (resolve) => {
const blob = await responsePromise.then(response => response.blob());
resolve(blob);
}),
}),
]);
// Image copied as image.
} else {
const text = await responsePromise.then(response => response.text());
await navigator.clipboard.writeText(text);
// Image copied as source code.
}
} catch (err) {
console.error(err.name, err.message);
}
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
button {
display: block;
}
텍스트 복사 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to copy text</title>
</head>
<body>
<h1>How to copy text</h1>
<textarea rows="3">
Some sample text for you to copy. Feel free to edit this.</textarea
>
<button type="button">Copy</button>
</body>
</html>
const textarea = document.querySelector('textarea');
const button = document.querySelector('button');
button.addEventListener('click', async () => {
try {
await navigator.clipboard.writeText(textarea.value);
} catch (err) {
console.error(err.name, err.message);
}
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
button {
display: block;
}
파일 붙여넣기 방법
<!DOCTYPE html>
<html>
<head>
<title>How to paste files</title>
</head>
<body>
<h1>How to paste files</h1>
<p>Hit <kbd>⌘</kbd> + <kbd>v</kbd> (for macOS) or <kbd>ctrl</kbd> + <kbd>v</kbd>
(for other operating systems) to paste image or text file(s) anywhere in this page.
</p>
</body>
</html>
document.addEventListener('paste', async (e) => {
// Prevent the default behavior, so you can code your own logic.
e.preventDefault();
if (!e.clipboardData.files.length) {
return;
}
// Iterate over all pasted files.
Array.from(e.clipboardData.files).forEach(async (file) => {
// Add more checks here for MIME types you're interested in,
// such as `application/pdf`, `video/mp4`, etc.
if (file.type.startsWith('image/')) {
// For images, create an image and append it to the `body`.
const img = document.createElement('img');
const blob = URL.createObjectURL(file);
img.src = blob;
document.body.append(img);
} else if (file.type.startsWith('text/')) {
// For text files, read the contents and output it into a `textarea`.
const textarea = document.createElement('textarea');
textarea.value = await file.text();
document.body.append(textarea);
}
});
});
html {
box-sizing: border-box;
font-family: system-ui, sans-serif;
color-scheme: dark light;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 1rem;
}
img {
height: auto;
max-width: 100%;
display: block;
}
이미지 붙여넣기 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to paste images</title>
</head>
<body>
<h1>How to paste images</h1>
<p>Hit <kbd>⌘</kbd> + <kbd>v</kbd> (for macOS) or <kbd>ctrl</kbd> + <kbd>v</kbd>
(for other operating systems) to paste images anywhere in this page.
</p>
</body>
</html>
document.addEventListener('paste', async (e) => {
e.preventDefault();
const clipboardItems = typeof navigator?.clipboard?.read === 'function' ? await navigator.clipboard.read() : e.clipboardData.files;
for (const clipboardItem of clipboardItems) {
let blob;
if (clipboardItem.type?.startsWith('image/')) {
// For files from `e.clipboardData.files`.
blob = clipboardItem
// Do something with the blob.
appendImage(blob);
} else {
// For files from `navigator.clipboard.read()`.
const imageTypes = clipboardItem.types?.filter(type => type.startsWith('image/'))
for (const imageType of imageTypes) {
blob = await clipboardItem.getType(imageType);
// Do something with the blob.
appendImage(blob);
}
}
}
});
const appendImage = (blob) => {
const img = document.createElement('img');
img.src = URL.createObjectURL(blob);
document.body.append(img);
};
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
button {
display: block;
}
텍스트 붙여넣기 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to paste text</title>
</head>
<body>
<h1>How to paste text</h1>
<p>
<button type="button">Paste</button>
</p>
<textarea></textarea>
</body>
</html>
const pasteButton = document.querySelector('button');
pasteButton.addEventListener('click', async () => {
try {
const text = await navigator.clipboard.readText()
document.querySelector('textarea').value += text;
console.log('Text pasted.');
} catch (error) {
console.log('Failed to read clipboard. Using execCommand instead.');
document.querySelector('textarea').focus();
const result = document.execCommand('paste')
console.log('document.execCommand result: ', result);
}
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
button {
display: block;
}
놀라운 앱을 위한 새로운 패턴: 파일 및 디렉터리 패턴
A collection of common patterns for dealing with files and directories.
디렉토리 드래그 앤 드롭 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How to drag and drop directories</title>
</head>
<body>
<main>
<h1>How to drag and drop directories</h1>
<p>Drag and drop one or multiple files or directories onto the page.</p>
<pre></pre>
</main>
</body>
</html>
const supportsFileSystemAccessAPI =
"getAsFileSystemHandle" in DataTransferItem.prototype;
const supportsWebkitGetAsEntry =
"webkitGetAsEntry" in DataTransferItem.prototype;
const elem = document.querySelector("main");
const debug = document.querySelector("pre");
elem.addEventListener("dragover", (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener("dragenter", (e) => {
elem.style.outline = "solid red 1px";
});
elem.addEventListener("dragleave", (e) => {
elem.style.outline = "";
});
elem.addEventListener("drop", async (e) => {
e.preventDefault();
elem.style.outline = "";
const fileHandlesPromises = [...e.dataTransfer.items]
.filter((item) => item.kind === "file")
.map((item) =>
supportsFileSystemAccessAPI
? item.getAsFileSystemHandle()
: supportsWebkitGetAsEntry
? item.webkitGetAsEntry()
: item.getAsFile()
);
for await (const handle of fileHandlesPromises) {
if (handle.kind === "directory" || handle.isDirectory) {
console.log(`Directory: ${handle.name}`);
debug.textContent += `Directory: ${handle.name}\n`;
} else {
console.log(`File: ${handle.name}`);
debug.textContent += `File: ${handle.name}\n`;
}
}
});
:root {
color-scheme: dark light;
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 1rem;
font-family: system-ui, sans-serif;
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
img,
video {
height: auto;
max-width: 100%;
}
main {
flex-grow: 1;
}
footer {
margin-top: 1rem;
border-top: solid CanvasText 1px;
font-size: 0.8rem;
}
파일을 끌어다 놓는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How to drag and drop files</title>
</head>
<body>
<main>
<h1>How to drag and drop files</h1>
<p>Drag and drop one or multiple files onto the page.</p>
<pre></pre>
</main>
</body>
</html>
const supportsFileSystemAccessAPI =
"getAsFileSystemHandle" in DataTransferItem.prototype;
const supportsWebkitGetAsEntry =
"webkitGetAsEntry" in DataTransferItem.prototype;
const elem = document.querySelector("main");
const debug = document.querySelector("pre");
elem.addEventListener("dragover", (e) => {
// Prevent navigation.
e.preventDefault();
});
elem.addEventListener("dragenter", (e) => {
elem.style.outline = "solid red 1px";
});
elem.addEventListener("dragleave", (e) => {
elem.style.outline = "";
});
elem.addEventListener("drop", async (e) => {
e.preventDefault();
elem.style.outline = "";
const fileHandlesPromises = [...e.dataTransfer.items]
.filter((item) => item.kind === "file")
.map((item) =>
supportsFileSystemAccessAPI
? item.getAsFileSystemHandle()
: supportsWebkitGetAsEntry
? item.webkitGetAsEntry()
: item.getAsFile()
);
for await (const handle of fileHandlesPromises) {
if (handle.kind === "directory" || handle.isDirectory) {
console.log(`Directory: ${handle.name}`);
debug.textContent += `Directory: ${handle.name}\n`;
} else {
console.log(`File: ${handle.name}`);
debug.textContent += `File: ${handle.name}\n`;
}
}
});
:root {
color-scheme: dark light;
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 0;
padding: 1rem;
font-family: system-ui, sans-serif;
line-height: 1.5;
min-height: 100vh;
display: flex;
flex-direction: column;
}
img,
video {
height: auto;
max-width: 100%;
}
main {
flex-grow: 1;
}
footer {
margin-top: 1rem;
border-top: solid CanvasText 1px;
font-size: 0.8rem;
}
파일 탐색기에서 열린 파일 처리 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="manifest.json" />
<title>How to handle files opened from the file explorer</title>
<link rel="stylesheet" href="style.css" />
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register(
'sw.js',
);
console.log(
'Service worker registered for scope',
registration.scope,
);
});
}
</script>
<script src="script.js" type="module"></script>
</head>
<body>
<h1>How to handle files opened from the file explorer</h1>
<p>Install the app. After the installation, try opening an image file from the file explorer with the app.
</body>
</html>
{
"name": "How to handle files opened from the file explorer",
"short_name": "Handle files",
"start_url": "../demo.html",
"id": "../demo.html",
"icons": [
{
"src": "../assets/favicon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "../assets/favicon.svg",
"type": "image/svg+xml",
"sizes": "any"
}
],
"display": "standalone",
"file_handlers": [
{
"action": "../demo.html",
"accept": {
"image/*": [".jpg", ".jpeg", ".png", ".webp", ".svg"]
}
}
]
}{
"name": "How to handle files opened from the file explorer",
"short_name": "Handle files",
"start_url": "../demo.html",
"id": "../demo.html",
"icons": [
{
"src": "../assets/favicon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "../assets/favicon.svg",
"type": "image/svg+xml",
"sizes": "any"
}
],
"display": "standalone",
"file_handlers": [
{
"action": "../demo.html",
"accept": {
"image/*": [".jpg", ".jpeg", ".png", ".webp", ".svg"]
}
}
]
}
if ('launchQueue' in window && 'files' in LaunchParams.prototype) {
launchQueue.setConsumer((launchParams) => {
if (!launchParams.files.length) {
return;
}
for (const fileHandle of launchParams.files) {
document.body.innerHTML += `<p>${fileHandle.name}</p>`;
}
});
}
html {
box-sizing: border-box;
font-family: system-ui, sans-serif;
color-scheme: dark light;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 1rem;
}
img {
height: auto;
max-width: 100%;
display: block;
}
디렉토리를 여는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>"
/>
<title>How to open a directory</title>
</head>
<body>
<h1>How to open a directory</h1>
<button type="button">Open directory</button>
<pre></pre>
</body>
</html>
const button = document.querySelector('button');
const pre = document.querySelector('pre');
const openDirectory = async (mode = "read") => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showDirectoryPicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let directoryStructure = undefined;
const getFiles = async (dirHandle, path = dirHandle.name) => {
const dirs = [];
const files = [];
for await (const entry of dirHandle.values()) {
const nestedPath = `${path}/${entry.name}`;
if (entry.kind === "file") {
files.push(
entry.getFile().then((file) => {
file.directoryHandle = dirHandle;
file.handle = entry;
return Object.defineProperty(file, "webkitRelativePath", {
configurable: true,
enumerable: true,
get: () => nestedPath,
});
})
);
} else if (entry.kind === "directory") {
dirs.push(getFiles(entry, nestedPath));
}
}
return [
...(await Promise.all(dirs)).flat(),
...(await Promise.all(files)),
];
};
try {
const handle = await showDirectoryPicker({
mode,
});
directoryStructure = getFiles(handle, undefined);
} catch (err) {
if (err.name !== "AbortError") {
console.error(err.name, err.message);
}
}
return directoryStructure;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
const input = document.createElement('input');
input.type = 'file';
input.webkitdirectory = true;
input.addEventListener('change', () => {
let files = Array.from(input.files);
resolve(files);
});
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
};
button.addEventListener('click', async () => {
const filesInDirectory = await openDirectory();
if (!filesInDirectory) {
return;
}
Array.from(filesInDirectory).forEach((file) => (pre.textContent += `${file.name}\n`));
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
하나 또는 여러 파일을 여는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>📂</text></svg>"
/>
<title>How to open one or multiple files</title>
</head>
<body>
<h1>How to open one or multiple files</h1>
<button type="button">Open file</button>
<button class="multiple" type="button">Open files</button>
<pre></pre>
</body>
</html>
const button = document.querySelector('button');
const buttonMultiple = document.querySelector('button.multiple');
const pre = document.querySelector('pre');
const openFileOrFiles = async (multiple = false) => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
"showOpenFilePicker" in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
let fileOrFiles = undefined;
try {
// Show the file picker, optionally allowing multiple files.
fileOrFiles = await showOpenFilePicker({ multiple });
if (!multiple) {
// Only one file is requested.
fileOrFiles = fileOrFiles[0];
}
} catch (err) {
// Fail silently if the user has simply canceled the dialog.
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
}
}
return fileOrFiles;
}
// Fallback if the File System Access API is not supported.
return new Promise((resolve) => {
// Append a new `<input type="file" multiple? />` and hide it.
const input = document.createElement('input');
input.style.display = 'none';
input.type = 'file';
document.body.append(input);
if (multiple) {
input.multiple = true;
}
// The `change` event fires when the user interacts with the dialog.
input.addEventListener('change', () => {
// Remove the `<input type="file" multiple? />` again from the DOM.
input.remove();
// If no files were selected, return.
if (!input.files) {
return;
}
// Return all files or just one file.
resolve(multiple ? input.files : input.files[0]);
});
// Show the picker.
if ('showPicker' in HTMLInputElement.prototype) {
input.showPicker();
} else {
input.click();
}
});
};
button.addEventListener('click', async () => {
const file = await openFileOrFiles();
if (!file) {
return;
}
pre.textContent += `${file.name}\n`;
});
buttonMultiple.addEventListener('click', async () => {
const files = await openFileOrFiles(true);
if (!files) {
return;
}
Array.from(files).forEach((file) => (pre.textContent += `${file.name}\n`));
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
button {
margin: 1rem;
}
공유 파일 수신 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="manifest.json" />
<title>How to receive shared files</title>
<link rel="stylesheet" href="style.css" />
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register(
'sw.js',
);
console.log(
'Service worker registered for scope',
registration.scope,
);
});
}
</script>
<script src="script.js" type="module"></script>
</head>
<body>
<h1>How to receive shared files</h1>
<p>Install the app. After the installation, try sharing an image to it from another app.
</body>
</html>
{
"name": "How to receive shared files",
"short_name": "Shared files",
"start_url": "../demo.html",
"id": "../demo.html",
"icons": [
{
"src": "../assets/favicon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "../assets/favicon.svg",
"type": "image/svg+xml",
"sizes": "any"
}
],
"display": "standalone",
"share_target": {
"action": "../receive-files/",
"method": "POST",
"enctype": "multipart/form-data",
"params": {
"files": [
{
"name": "image",
"accept": ["image/jpeg", "image/png", "image/webp", "image/gif"]
}
]
}
}
}
window.addEventListener('load', async () => {
if (location.search.includes('share-target')) {
const keys = await caches.keys();
const mediaCache = await caches.open(
keys.filter((key) => key.startsWith('media'))[0],
);
const image = await mediaCache.match('shared-image');
if (image) {
const blob = await image.blob();
await mediaCache.delete('shared-image');
// Handle the shared file somehow.
}
}
});
html {
box-sizing: border-box;
font-family: system-ui, sans-serif;
color-scheme: dark light;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 1rem;
}
img {
height: auto;
max-width: 100%;
display: block;
}
파일 저장 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to save a file</title>
</head>
<body>
<h1>How to save a file</h1>
<label
>Text to save
<textarea rows="3">
Some sample text for you to save. Feel free to edit this.</textarea
>
</label>
<label>File name <input class="text" value="example.txt" /></label>
<button class="text" type="button">Save text</button>
<label
>Image to save
<img
src="https://cdn.glitch.global/75170424-3d76-41d7-ae77-72d0efb0401b/AVIF%20Test%20picture%20(JPEG%20converted%20to%20AVIF%20with%20Convertio).avif?v=1658240752363"
alt="Blue flower."
width="630"
height="420"
/></label>
<label>File name <input class="img" value="example.avif" /></label>
<button class="img" type="button">Save image</button>
</body>
</html>
const textarea = document.querySelector('textarea');
const textInput = document.querySelector('input.text');
const textButton = document.querySelector('button.text');
const img = document.querySelector('img');
const imgInput = document.querySelector('input.img');
const imgButton = document.querySelector('button.img');
const saveFile = async (blob, suggestedName) => {
// Feature detection. The API needs to be supported
// and the app not run in an iframe.
const supportsFileSystemAccess =
'showSaveFilePicker' in window &&
(() => {
try {
return window.self === window.top;
} catch {
return false;
}
})();
// If the File System Access API is supported…
if (supportsFileSystemAccess) {
try {
// Show the file save dialog.
const handle = await showSaveFilePicker({
suggestedName,
});
// Write the blob to the file.
const writable = await handle.createWritable();
await writable.write(blob);
await writable.close();
return;
} catch (err) {
// Fail silently if the user has simply canceled the dialog.
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
return;
}
}
}
// Fallback if the File System Access API is not supported…
// Create the blob URL.
const blobURL = URL.createObjectURL(blob);
// Create the `<a download>` element and append it invisibly.
const a = document.createElement('a');
a.href = blobURL;
a.download = suggestedName;
document.body.append(a);
a.style.display = 'none';
// Click the element.
a.click();
// Revoke the blob URL and remove the element.
setTimeout(() => {
URL.revokeObjectURL(blobURL);
a.remove();
}, 1000);
};
textButton.addEventListener('click', async () => {
const blob = new Blob([textarea.value], { type: 'text/plain' });
await saveFile(blob, textInput.value);
});
imgButton.addEventListener('click', async () => {
const blob = await fetch(img.src).then((response) => response.blob());
await saveFile(blob, imgInput.value);
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
img {
max-width: 320px;
height: auto;
}
label,
button,
textarea,
input,
img {
display: block;
margin-block: 1rem;
}
파일 공유 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title></title>
<link rel="stylesheet" href="style.css" />
<script src="script.js" type="module"></script>
</head>
<body>
<h1>Share a file</h1>
<img
width="200"
height="200"
alt="A cat walking in the snow."
src="cat.png"
/>
<button type=button></button>
</body>
</html>
const button = document.querySelector('button');
const img = document.querySelector('img');
const webShareSupported = 'canShare' in navigator;
button.textContent = webShareSupported ? 'Share' : 'Download';
const shareOrDownload = async (blob, fileName, title, text) => {
if (webShareSupported) {
const data = {
files: [
new File([blob], fileName, {
type: blob.type,
}),
],
title,
text,
};
if (navigator.canShare(data)) {
try {
await navigator.share(data);
} catch (err) {
if (err.name !== 'AbortError') {
console.error(err.name, err.message);
}
} finally {
return;
}
}
}
// Fallback
const a = document.createElement('a');
a.download = fileName;
a.style.display = 'none';
a.href = URL.createObjectURL(blob);
a.addEventListener('click', () => {
setTimeout(() => {
URL.revokeObjectURL(a.href);
a.remove();
}, 1000)
});
document.body.append(a);
a.click();
};
button.addEventListener('click', async () => {
const blob = await fetch(img.src).then(res => res.blob());
await shareOrDownload(blob, 'cat.png', 'Cat in the snow', 'Getting cold feet…');
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
img,
video {
height: auto;
max-width: 100%;
display: block;
}
button {
margin: 1rem;
}
놀라운 앱을 위한 새로운 패턴: 고급 앱 패턴
A collection of common patterns for building advanced apps.
앱 배지를 만드는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link rel="manifest" href="manifest.json" />
<title>How to create an app badge</title>
<link rel="stylesheet" href="style.css" />
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register(
'sw.js',
);
console.log(
'Service worker registered for scope',
registration.scope,
);
});
}
</script>
<script src="script.js" type="module"></script>
</head>
<body>
<h1>How to create an app badge</h1>
<ol>
<li>
Watch the favicon. You should see a counter that updates each second
integrated into the favicon.
<img
src="../favicon.png"
style="width: 250px; height: auto"
width="528"
height="74"
alt="Favicon with counter."
/>
</li>
<li>
Install the app by clicking the button below. After the installation,
the button is disabled.
<p>
<button disabled type="button">Install</button>
</p>
</li>
<li>
Watch the app icon in your operating system's task bar. You should see a
counter that updates each second as an app badge.
<img
src="../app-badge.png"
style="width: 80px; height: auto"
width="282"
height="388"
alt="App badge with counter."
/>
</li>
</ol>
<favicon-badge src="../favicon.svg" textColor="#fff" badge="" />
</body>
</html>
{
"name": "How to create an app badge",
"short_name": "App Badge",
"start_url": "../demo.html",
"id": "../demo.html",
"icons": [
{
"src": "../assets/favicon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "../assets/favicon.svg",
"type": "image/svg+xml",
"sizes": "any"
}
],
"display": "standalone"
}
import 'https://unpkg.com/[email protected]/dist/FavIconBadge.js';
// The `<favicon-badge>` custom element.
const favicon = document.querySelector('favicon-badge');
// The install button.
const installButton = document.querySelector('button');
// Feature detection.
const supportsAppBadge = 'setAppBadge' in navigator;
// This function will either set the favicon or the native
// app badge. The implementation is dynamically changed at runtime.
let setAppBadge;
// Variable for the counter.
let i = 0;
// Returns a value between 0 and 9.
const getAppBadgeValue = () => {
if (i > 9) {
i = 0;
}
return i++;
};
// Function to set a favicon badge.
const setAppBadgeFavicon = (value) => {
favicon.badge = value;
};
// Function to set a native app badge.
const setAppBadgeNative = (value) => {
navigator.setAppBadge(value);
}
// If the app is installed and native app badges are supported,
// use the native app badge.
if (
matchMedia('(display-mode: standalone)').matches &&
supportsAppBadge
) {
setAppBadge = setAppBadgeNative;
// In all other cases (i.e., if the app is not installed or native
// app badges are not supported), use the favicon badge.
} else {
setAppBadge = setAppBadgeFavicon;
}
// Update the badge every second.
setInterval(() => {
setAppBadge(getAppBadgeValue());
}, 1000);
// Only relevant for browsers that support installation.
if ('BeforeInstallPromptEvent' in window) {
// Variable to stash the `BeforeInstallPromptEvent`.
let installEvent = null;
// Function that will be run when the app is installed.
const onInstall = () => {
// Disable the install button.
installButton.disabled = true;
// No longer needed.
installEvent = null;
if (supportsAppBadge) {
// Remove the favicon badge.
favicon.badge = false;
// Switch the implementation so it uses the native
// app badge.
setAppBadge = setAppBadgeNative;
}
};
window.addEventListener('beforeinstallprompt', (event) => {
// Do not show the install prompt quite yet.
event.preventDefault();
// Stash the `BeforeInstallPromptEvent` for later.
installEvent = event;
// Enable the install button.
installButton.disabled = false;
});
installButton.addEventListener('click', async () => {
// If there is no stashed `BeforeInstallPromptEvent`, return.
if (!installEvent) {
return;
}
// Use the stashed `BeforeInstallPromptEvent` to prompt the user.
installEvent.prompt();
const result = await installEvent.userChoice;
// If the user installs the app, run `onInstall()`.
if (result.outcome === 'accepted') {
onInstall();
}
});
// The user can decide to ignore the install button
// and just use the browser prompt directly. In this case
// likewise run `onInstall()`.
window.addEventListener('appinstalled', (event) => {
onInstall();
});
}
html {
box-sizing: border-box;
font-family: system-ui, sans-serif;
color-scheme: dark light;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 1rem;
}
img {
height: auto;
max-width: 100%;
display: block;
}
주소록에서 연락처에 액세스하는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to access contacts from the address book</title>
</head>
<body>
<h1>How to access contacts from the address book</h1>
<p>Ship your order as a present to a friend.</p>
<button hidden type="button">Open address book</button>
<pre></pre>
<label> Name <input class="name" autocomplete="name"></label>
<label hidden>Address <input class="address" required></label>
<label>Street <input class="autofill" autocomplete="address-line1" required></label>
<label>City <input class="autofill" autocomplete="address-level2" required></label>
<label>State / Province / Region (optional) <input class="autofill" autocomplete="address-level1"></label>
<label>ZIP / Postal code (optional) <input class="autofill" autocomplete="postal-code"></label>
<label>Country <input class="autofill" autocomplete="country"></label>
<label>Email<input class="email" autocomplete="email"></label>
<label>Telephone<input class="tel" autocomplete="tel"></label>
</body>
</html>
const button = document.querySelector('button');
const name = document.querySelector('.name')
const address = document.querySelector('.address')
const email = document.querySelector('.email')
const tel = document.querySelector('.tel')
const pre = document.querySelector('pre')
const autofills = document.querySelectorAll('.autofill')
if ('contacts' in navigator) {
button.hidden = false;
for (const autofill of autofills) {
autofill.parentElement.style.display = 'none'
}
address.parentElement.style.display = 'block';
button.addEventListener('click', async () => {
const props = ['name', 'email', 'tel', 'address'];
const opts = {multiple: false};
try {
const [contact] = await navigator.contacts.select(props, opts);
name.value = contact.name;
address.value = contact.address;
tel.value = contact.tel
email.value = contact.email;
} catch (err) {
pre.textContent = `${err.name}: ${err.message}`
}
});
}
html {
box-sizing: border-box;
font-family: system-ui, sans-serif;
color-scheme: dark light;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 1rem;
}
input {
display: block;
margin-block-end: 1rem;
}
다중 화면 사용 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to use multiple screens</title>
<link rel="stylesheet" href="/style.css" />
<script src="/script.js" type="module"></script>
</head>
<body>
<h1>How to use multiple screens</h1>
<div>
<div>
Permission Status:
<span id="permissionStatus"></span>
</div>
<div>Screens Available: <span id="screensAvail"></span></div>
</div>
<button id="detectScreen">Detect Screens</button>
<button id="create" disabled>Create Page On Random Screen</button>
</body>
</html>
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link
rel="icon"
href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🎉</text></svg>"
/>
<title>How to use multiple screens</title>
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<script>
function randomBackgroundColor() {
const r = Math.floor(Math.random() * 256);
const g = Math.floor(Math.random() * 256);
const b = Math.floor(Math.random() * 256);
var bgColor = 'rgb(' + r + ',' + g + ',' + b + ')';
document.body.style.backgroundColor = bgColor;
}
randomBackgroundColor();
</script>
</body>
</html>
const detectButton = document.querySelector('#detectScreen');
const createButton = document.querySelector('#create');
const permissionLabel = document.querySelector('#permissionStatus');
const screensAvailLabel = document.querySelector('#screensAvail');
const popupUrl = 'popup.html';
let screenDetails = undefined;
let permission = undefined;
let currentScreenLength = undefined;
detectButton.addEventListener('click', async () => {
if ('getScreenDetails' in window) {
screenDetails = await window.getScreenDetails();
screenDetails.addEventListener('screenschange', (event) => {
if (screenDetails.screens.length !== currentScreenLength) {
currentScreenLength = screenDetails.screens.length;
updateScreenInfo();
}
});
try {
permission =
(await navigator.permissions.query({ name: 'window-placement' }))
.state === 'granted'
? 'Granted'
: 'No Permission';
} catch (err) {
console.error(err);
}
currentScreenLength = screenDetails.screens.length;
updateScreenInfo();
} else {
screenDetails = window.screen;
permission = 'Multi-Screen Window Placement API - NOT SUPPORTED';
currentScreenLength = 1;
updateScreenInfo();
}
});
createButton.addEventListener('click', () => {
const screen =
screenDetails.screens[Math.floor(Math.random() * currentScreenLength)];
const options = {
x: screen.availLeft,
y: screen.availTop,
width: screen.availWidth,
height: screen.availHeight,
};
window.open(popupUrl, '_blank', getFeaturesFromOptions(options));
});
const getFeaturesFromOptions = (options) => {
return (
'left=' +
options.x +
',top=' +
options.y +
',width=' +
options.width +
',height=' +
options.height
);
};
const updateScreenInfo = () => {
screensAvailLabel.innerHTML = currentScreenLength;
permissionLabel.innerHTML = permission;
if ('getScreenDetails' in window && screenDetails.screens.length > 1) {
createButton.disabled = false;
} else {
createButton.disabled = true;
}
};
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
백그라운드에서 주기적으로 데이터를 동기화하는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<link
rel="icon"
href=""
/>
<link rel="manifest" href="./manifest.json" />
<title>How to periodically synchronize data in the background</title>
<link rel="stylesheet" href="/style.css" />
<script src="/script.js" defer></script>
</head>
<body>
<h1>How to periodically synchronize data in the background</h1>
<p class="available">Periodic background sync can be used. Install the app first.</p>
<p class="not-available">Periodic background sync cannot be used.</p>
<h2>Last updated</h2>
<p class="last-updated">Never</p>
<h2>Registered tags</h2>
<ul>
<li>None yet.</li>
</ul>
</body>
</html>
{
"name": "How to periodically synchronize data in the background",
"short_name": "Periodic Background Sync",
"start_url": "./",
"id": "./",
"icons": [
{
"src": "../assets/favicon.png",
"type": "image/png",
"sizes": "512x512"
},
{
"src": "../assets/favicon.svg",
"type": "image/svg+xml",
"sizes": "any"
}
],
"display": "standalone"
}
const available = document.querySelector('.available');
const notAvailable = document.querySelector('.not-available');
const ul = document.querySelector('ul');
const lastUpdated = document.querySelector('.last-updated');
const updateContent = async () => {
const data = await fetch(
'https://worldtimeapi.org/api/timezone/Europe/London.json'
).then((response) => response.json());
return new Date(data.unixtime * 1000);
};
const registerPeriodicBackgroundSync = async (registration) => {
const status = await navigator.permissions.query({
name: 'periodic-background-sync',
});
if (status.state === 'granted' && 'periodicSync' in registration) {
try {
// Register the periodic background sync.
await registration.periodicSync.register('content-sync', {
// An interval of one day.
minInterval: 24 * 60 * 60 * 1000,
});
available.hidden = false;
notAvailable.hidden = true;
// List registered periodic background sync tags.
const tags = await registration.periodicSync.getTags();
if (tags.length) {
ul.innerHTML = '';
}
tags.forEach((tag) => {
const li = document.createElement('li');
li.textContent = tag;
ul.append(li);
});
// Update the user interface with the last periodic background sync data.
const backgroundSyncCache = await caches.open('periodic-background-sync');
if (backgroundSyncCache) {
const backgroundSyncResponse =
backgroundSyncCache.match('/last-updated');
if (backgroundSyncResponse) {
lastUpdated.textContent = `${await fetch('/last-updated').then(
(response) => response.text()
)} (periodic background-sync)`;
}
}
// Listen for incoming periodic background sync messages.
navigator.serviceWorker.addEventListener('message', async (event) => {
if (event.data.tag === 'content-sync') {
lastUpdated.textContent = `${await updateContent()} (periodic background sync)`;
}
});
} catch (err) {
console.error(err.name, err.message);
available.hidden = true;
notAvailable.hidden = false;
lastUpdated.textContent = 'Never';
}
} else {
available.hidden = true;
notAvailable.hidden = false;
lastUpdated.textContent = `${await updateContent()} (manual)`;
}
};
if ('serviceWorker' in navigator) {
window.addEventListener('load', async () => {
const registration = await navigator.serviceWorker.register('./sw.js');
console.log('Service worker registered for scope', registration.scope);
await registerPeriodicBackgroundSync(registration);
});
}
html {
box-sizing: border-box;
font-family: system-ui, sans-serif;
color-scheme: dark light;
}
*,
*:before,
*:after {
box-sizing: inherit;
}
body {
margin: 1rem;
}
사용자가 자신이 있는 웹사이트를 공유하도록 하는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>How to let the user share the website they are on</title>
<link rel="stylesheet" href="style.css" />
<script src="script.js" defer></script>
</head>
<body>
<h1>How to let the user share the website they are on</h1>
<p>
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Proin at libero
eget ante congue molestie. Integer varius enim leo. Duis est nisi,
ullamcorper et posuere eu, mattis sed lorem. Lorem ipsum dolor sit amet,
consectetur adipiscing elit. In at suscipit erat, et sollicitudin lorem.
</p>
<img src="https://placekitten.com/400/300" width=400 height=300>
<p>
In euismod ornare scelerisque. Nunc imperdiet augue ac porttitor
porttitor. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. Curabitur eget pretium elit, et
interdum quam.
</p>
<hr />
<button type="button"><span class="icon"></span>Share</button>
</body>
</html>
// DOM references
const button = document.querySelector('button');
const icon = button.querySelector('.icon');
const canonical = document.querySelector('link[rel="canonical"]');
// Find out if the user is on a device made by Apple.
const IS_MAC = /Mac|iPhone/.test(navigator.platform);
// Find out if the user is on a Windows device.
const IS_WINDOWS = /Win/.test(navigator.platform);
// For Apple devices or Windows, use the platform-specific share icon.
icon.classList.add(`share${IS_MAC? 'mac' : (IS_WINDOWS? 'windows' : '')}`);
button.addEventListener('click', async () => {
// Title and text are identical, since the title may actually be ignored.
const title = document.title;
const text = document.title;
// Use the canonical URL, if it exists, else, the current location.
const url = canonical?.href || location.href;
// Feature detection to see if the Web Share API is supported.
if ('share' in navigator) {
try {
await navigator.share({
url,
text,
title,
});
return;
} catch (err) {
// If the user cancels, an `AbortError` is thrown.
if (err.name !== "AbortError") {
console.error(err.name, err.message);
}
}
}
// Fallback to use Twitter's Web Intent URL.
// (https://developer.twitter.com/en/docs/twitter-for-websites/tweet-button/guides/web-intent)
const shareURL = new URL('https://twitter.com/intent/tweet');
const params = new URLSearchParams();
params.append('text', text);
params.append('url', url);
shareURL.search = params;
window.open(shareURL, '_blank', 'popup,noreferrer,noopener');
});
:root {
color-scheme: dark light;
}
html {
box-sizing: border-box;
}
*, *:before, *:after {
box-sizing: inherit;
}
body {
margin: 1rem;
font-family: system-ui, sans-serif;
}
img,
video {
height: auto;
max-width: 100%;
}
button {
display: flex;
}
button .icon {
display: inline-block;
width: 1em;
height: 1em;
background-size: 1em;
}
@media (prefers-color-scheme: dark) {
button .icon {
filter: invert();
}
}
.share {
background-image: url('share.svg');
}
.sharemac {
background-image: url('sharemac.svg');
}
앱 바로가기를 만드는 방법
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="color-scheme" content="dark light" />
<link rel="manifest" href="manifest.json" />
<title>How to create app shortcuts</title>
<script>
if ('serviceWorker' in navigator) {
window.addEventListener('load', () => {
navigator.serviceWorker.register('sw.js');
});
}
</script>
<script type="module" src="script.js"></script>
</head>
<body>
<h1>How to create app shortcuts</h1>
<ol>
<li>
You can drag these <a href="blue.html">blue page</a> or
<a href="red.html">red page</a> links to the bookmarks bar
and access them later.
</li>
<li>
Install the app by clicking the button below. After the installation,
the button is disabled.
<p>
<button disabled type="button">Install</button>
</p>
</li>
</ol>
</body>
</html>
{
"name": "How to create app shortcuts",
"short_name": "App shortcuts",
"start_url": "../demo.html",
"icons": [
{
"src": "../icons/favicon.png",
"type": "image/png",
"sizes": "512x512"
}
],
"shortcuts": [
{
"name": "Feel blue",
"short_name": "Blue",
"description": "Open a blue webpage",
"url": "../blue.html",
"icons": [{ "src": "../icons/blue.png", "sizes": "192x192" }]
},
{
"name": "Feel red",
"short_name": "Red",
"description": "Open a red webpage",
"url": "../red.html",
"icons": [{ "src": "../icons/red.png", "sizes": "192x192" }]
}
],
"display": "standalone"
}
// The install button.
const installButton = document.querySelector('button');
// Only relevant for browsers that support installation.
if ('BeforeInstallPromptEvent' in window) {
// Variable to stash the `BeforeInstallPromptEvent`.
let installEvent = null;
// Function that will be run when the app is installed.
const onInstall = () => {
// Disable the install button.
installButton.disabled = true;
// No longer needed.
installEvent = null;
};
window.addEventListener('beforeinstallprompt', (event) => {
// Do not show the install prompt quite yet.
event.preventDefault();
// Stash the `BeforeInstallPromptEvent` for later.
installEvent = event;
// Enable the install button.
installButton.disabled = false;
});
installButton.addEventListener('click', async () => {
// If there is no stashed `BeforeInstallPromptEvent`, return.
if (!installEvent) {
return;
}
// Use the stashed `BeforeInstallPromptEvent` to prompt the user.
installEvent.prompt();
const result = await installEvent.userChoice;
// If the user installs the app, run `onInstall()`.
if (result.outcome === 'accepted') {
onInstall();
}
});
// The user can decide to ignore the install button
// and just use the browser prompt directly. In this case
// likewise run `onInstall()`.
window.addEventListener('appinstalled', () => {
onInstall();
});
}