Bindings
1. Text inputs
bind:value
지시어를 사용할 수 있다. name 값을 변경하면 입력 값이 업데이트될 뿐만 아니라 입력 값이 변경되면 name도 업데이트된다.
<script>
let name = 'world';
</script>
<input bind:value={name}>
<h1>Hello {name}!</h1>
2. Numberic inputs
<script>
let a = 1;
let b = 2;
</script>
<label>
<input type=number bind:value={a} min=0 max=10>
<input type=range bind:value={a} min=0 max=10>
</label>
<label>
<input type=number bind:value={b} min=0 max=10>
<input type=range bind:value={b} min=0 max=10>
</label>
<p>{a} + {b} = {a + b}</p>
<style>
label { display: flex }
input, p { margin: 6px }
</style>
3. Checkbos inputs
<script>
let yes = false;
</script>
<label>
<input type=checkbox bind:checked={yes}>
Yes! Send me regular email spam
</label>
{#if yes}
<p>Thank you. We will bombard your inbox and sell your personal details.</p>
{:else}
<p>You must opt-in to continue. If you're not paying, you're the product.</p>
{/if}
<button disabled={!yes}>
Subscribe
</button>
4. group inputs
<script>
let scoops = 1;
let flavours = ['Mint choc chip'];
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>
<h2>Size</h2>
<label>
<input type=radio bind:group={scoops} name="scoops" value={1}>
One scoop
</label>
<label>
<input type=radio bind:group={scoops} name="scoops" value={2}>
Two scoops
</label>
<label>
<input type=radio bind:group={scoops} name="scoops" value={3}>
Three scoops
</label>
<h2>Flavours</h2>
{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} name="flavours" value={flavour}>
{flavour}
</label>
{/each}
{#if flavours.length === 0}
<p>Please select at least one flavour</p>
{:else if flavours.length > scoops}
<p>Can't order more flavours than scoops!</p>
{:else}
<p>
You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
of {join(flavours)}
</p>
{/if}
5. Textarea inputs
<script>
import { marked } from 'marked';
let value = `Some words are *italic*, some are **bold**`;
</script>
{@html marked(value)}
<textarea bind:value></textarea>
<style>
textarea { width: 100%; height: 200px; }
</style>
6. select bindings
<script>
let questions = [
{ id: 1, text: `Where did you go to school?` },
{ id: 2, text: `What is your mother's name?` },
{ id: 3, text: `What is another personal fact that an attacker could easily find with Google?` }
];
let selected;
let answer = '';
function handleSubmit() {
alert(`answered question ${selected.id} (${selected.text}) with "${answer}"`);
}
</script>
<h2>Insecurity questions</h2>
<form on:submit|preventDefault={handleSubmit}>
<select bind:value={selected} on:change="{() => answer = ''}">
{#each questions as question}
<option value={question}>
{question.text}
</option>
{/each}
</select>
<input bind:value={answer}>
<button disabled={!answer} type=submit>
Submit
</button>
</form>
<p>selected question {selected ? selected.id : '[waiting...]'}</p>
<style>
input {
display: block;
width: 500px;
max-width: 100%;
}
</style>
7. select multiple
- version1 (복수선택 시 컨트롤 키 제어 필요)
<script>
let scoops = 1;
let flavours = ['Mint choc chip'];
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>
<h2>Size</h2>
<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>
<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>
<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>
<h2>Flavours</h2>
{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
{flavour}
</label>
{/each}
{#if flavours.length === 0}
<p>Please select at least one flavour</p>
{:else if flavours.length > scoops}
<p>Can't order more flavours than scoops!</p>
{:else}
<p>
You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
of {join(flavours)}
</p>
{/if}
- version2
<script>
let scoops = 1;
let flavours = ['Mint choc chip'];
let menu = [
'Cookies and cream',
'Mint choc chip',
'Raspberry ripple'
];
function join(flavours) {
if (flavours.length === 1) return flavours[0];
return `${flavours.slice(0, -1).join(', ')} and ${flavours[flavours.length - 1]}`;
}
</script>
<h2>Size</h2>
<label>
<input type=radio bind:group={scoops} value={1}>
One scoop
</label>
<label>
<input type=radio bind:group={scoops} value={2}>
Two scoops
</label>
<label>
<input type=radio bind:group={scoops} value={3}>
Three scoops
</label>
<h2>Flavours</h2>
{#each menu as flavour}
<label>
<input type=checkbox bind:group={flavours} value={flavour}>
{flavour}
</label>
{/each}
{#if flavours.length === 0}
<p>Please select at least one flavour</p>
{:else if flavours.length > scoops}
<p>Can't order more flavours than scoops!</p>
{:else}
<p>
You ordered {scoops} {scoops === 1 ? 'scoop' : 'scoops'}
of {join(flavours)}
</p>
{/if}
8. contenteditable bindings
<script>
let html = '<p>Write some text!</p>';
</script>
<div
contenteditable="true"
bind:innerHTML={html}
></div>
<pre>{html}</pre>
<style>
[contenteditable] {
padding: 0.5em;
border: 1px solid #eee;
border-radius: 4px;
}
</style>
9. each block bindings
<script>
let todos = [
{ done: false, text: 'finish Svelte tutorial' },
{ done: false, text: 'build an app' },
{ done: false, text: 'world domination' }
];
function add() {
todos = todos.concat({ done: false, text: '' });
}
function clear() {
todos = todos.filter(t => !t.done);
}
$: remaining = todos.filter(t => !t.done).length;
</script>
<h1>Todos</h1>
{#each todos as todo}
<div class:done={todo.done}>
<input
type=checkbox
bind:checked={todo.done}
>
<input
placeholder="What needs to be done?"
bind:value={todo.text}
>
</div>
{/each}
<p>{remaining} remaining</p>
<button on:click={add}>
Add new
</button>
<button on:click={clear}>
Clear completed
</button>
<style>
.done {
opacity: 0.4;
}
</style>
10. media elements
<script>
// These values are bound to properties of the video
let time = 0;
let duration;
let paused = true;
let showControls = true;
let showControlsTimeout;
// Used to track time of last mouse down event
let lastMouseDown;
function handleMove(e) {
// Make the controls visible, but fade out after
// 2.5 seconds of inactivity
clearTimeout(showControlsTimeout);
showControlsTimeout = setTimeout(() => showControls = false, 2500);
showControls = true;
if (!duration) return; // video not loaded yet
if (e.type !== 'touchmove' && !(e.buttons & 1)) return; // mouse not down
const clientX = e.type === 'touchmove' ? e.touches[0].clientX : e.clientX;
const { left, right } = this.getBoundingClientRect();
time = duration * (clientX - left) / (right - left);
}
// we can't rely on the built-in click event, because it fires
// after a drag — we have to listen for clicks ourselves
function handleMousedown(e) {
lastMouseDown = new Date();
}
function handleMouseup(e) {
if (new Date() - lastMouseDown < 300) {
if (paused) e.target.play();
else e.target.pause();
}
}
function format(seconds) {
if (isNaN(seconds)) return '...';
const minutes = Math.floor(seconds / 60);
seconds = Math.floor(seconds % 60);
if (seconds < 10) seconds = '0' + seconds;
return `${minutes}:${seconds}`;
}
</script>
<h1>Caminandes: Llamigos</h1>
<p>From <a href="https://studio.blender.org/films">Blender Studio</a>. CC-BY</p>
<div>
<video
poster="https://sveltejs.github.io/assets/caminandes-llamigos.jpg"
src="https://sveltejs.github.io/assets/caminandes-llamigos.mp4"
on:mousemove={handleMove}
on:touchmove|preventDefault={handleMove}
on:mousedown={handleMousedown}
on:mouseup={handleMouseup}
bind:currentTime={time}
bind:duration
bind:paused>
<track kind="captions">
</video>
<div class="controls" style="opacity: {duration && showControls ? 1 : 0}">
<progress value="{(time / duration) || 0}"/>
<div class="info">
<span class="time">{format(time)}</span>
<span>click anywhere to {paused ? 'play' : 'pause'} / drag to seek</span>
<span class="time">{format(duration)}</span>
</div>
</div>
</div>
<style>
div {
position: relative;
}
.controls {
position: absolute;
top: 0;
width: 100%;
transition: opacity 1s;
}
.info {
display: flex;
width: 100%;
justify-content: space-between;
}
span {
padding: 0.2em 0.5em;
color: white;
text-shadow: 0 0 8px black;
font-size: 1.4em;
opacity: 0.7;
}
.time {
width: 3em;
}
.time:last-child { text-align: right }
progress {
display: block;
width: 100%;
height: 10px;
-webkit-appearance: none;
appearance: none;
}
progress::-webkit-progress-bar {
background-color: rgba(0,0,0,0.2);
}
progress::-webkit-progress-value {
background-color: rgba(255,255,255,0.6);
}
video {
width: 100%;
}
</style>
11. demensions
<script>
let w;
let h;
let size = 42;
let text = 'edit me';
</script>
<input type=range bind:value={size}>
<input bind:value={text}>
<p>size: {w}px x {h}px</p>
<div bind:clientWidth={w} bind:clientHeight={h}>
<span style="font-size: {size}px">{text}</span>
</div>
<style>
input { display: block; }
div { display: inline-block; }
span { word-break: break-all; }
</style>
12. this
<script>
import { onMount } from 'svelte';
let canvas;
onMount(() => {
const ctx = canvas.getContext('2d');
let frame = requestAnimationFrame(loop);
function loop(t) {
frame = requestAnimationFrame(loop);
const imageData = ctx.getImageData(0, 0, canvas.width, canvas.height);
for (let p = 0; p < imageData.data.length; p += 4) {
const i = p / 4;
const x = i % canvas.width;
const y = i / canvas.width >>> 0;
const r = 64 + (128 * x / canvas.width) + (64 * Math.sin(t / 1000));
const g = 64 + (128 * y / canvas.height) + (64 * Math.cos(t / 1000));
const b = 128;
imageData.data[p + 0] = r;
imageData.data[p + 1] = g;
imageData.data[p + 2] = b;
imageData.data[p + 3] = 255;
}
ctx.putImageData(imageData, 0, 0);
}
return () => {
cancelAnimationFrame(frame);
};
});
</script>
<canvas
bind:this={canvas}
width={32}
height={32}
></canvas>
<style>
canvas {
width: 100%;
height: 100%;
background-color: #666;
-webkit-mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat;
mask: url(/svelte-logo-mask.svg) 50% 50% no-repeat;
}
</style>
13. component bindings
<!-- App.svelte -->
<script>
import Keypad from './Keypad.svelte';
let pin;
$: view = pin ? pin.replace(/\d(?!$)/g, '•') : 'enter your pin';
function handleSubmit() {
alert(`submitted ${pin}`);
}
</script>
<h1 style="color: {pin ? '#333' : '#ccc'}">{view}</h1>
<Keypad bind:value={pin} on:submit={handleSubmit}/>
<!-- Keypad.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
export let value = '';
const dispatch = createEventDispatcher();
const select = num => () => value += num;
const clear = () => value = '';
const submit = () => dispatch('submit');
</script>
<div class="keypad">
<button on:click={select(1)}>1</button>
<button on:click={select(2)}>2</button>
<button on:click={select(3)}>3</button>
<button on:click={select(4)}>4</button>
<button on:click={select(5)}>5</button>
<button on:click={select(6)}>6</button>
<button on:click={select(7)}>7</button>
<button on:click={select(8)}>8</button>
<button on:click={select(9)}>9</button>
<button disabled={!value} on:click={clear}>clear</button>
<button on:click={select(0)}>0</button>
<button disabled={!value} on:click={submit}>submit</button>
</div>
<style>
.keypad {
display: grid;
grid-template-columns: repeat(3, 5em);
grid-template-rows: repeat(4, 3em);
grid-gap: 0.5em
}
button {
margin: 0
}
</style>
14. binding to component instances
<!-- App.svelte -->
<script>
import InputField from './InputField.svelte';
let field;
</script>
<InputField bind:this={field}/>
<button on:click={() => field.focus()}>Focus field</button>
<!-- InputField.svelte -->
<script>
let input;
export function focus() {
input.focus();
}
</script>
<input bind:this={input} />