![](https://img.haomeiwen.com/i21032798/07599069c4487672.png)
Contenteditable bindings
Elements with a contenteditable="true"
attribute support textContent
and innerHTML
bindings:
<div contenteditable="true" bind:innerHTML={html} ></div>
<div contenteditable="true" bind:textContent={html} ></div>
Each block bindings
You can even bind to properties inside an each
block.
<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>
<div class="centered">
<h1>todos</h1>
<ul class="todos">
{#each todos as todo}
<li class:done={todo.done}>
<input
type="checkbox"
bind:checked={todo.done}
/>
<input
type="text"
placeholder="What needs to be done?"
bind:value={todo.text}
/>
</li>
{/each}
</ul>
<p>{remaining} remaining</p>
<button on:click={add}>
Add new
</button>
<button on:click={clear}>
Clear completed
</button>
</div>
<style>
.centered {
max-width: 20em;
margin: 0 auto;
}
.done {
opacity: 0.4;
}
li {
display: flex;
}
input[type="text"] {
flex: 1;
padding: 0.5em;
margin: -0.2em 0;
border: none;
}
</style>
Note that interacting with these <input> elements will mutate the array. If you prefer to work with immutable data, you should avoid these bindings and use event handlers instead.
Media elements
You can bind to properties of <audio> and <video> elements, making it easy to (for example) build custom player UI.
<script>
export let src;
export let title;
export let artist;
let time = 0;
let duration = 0;
let paused = true;
function format(time) {
if (isNaN(time)) return '...';
const minutes = Math.floor(time / 60);
const seconds = Math.floor(time % 60);
return `${minutes}:${seconds < 10 ? `0${seconds}` : seconds}`;
}
</script>
<div class="player" class:paused>
<audio
{src}
bind:currentTime={time}
bind:duration
bind:paused
preload="metadata"
on:ended={() => {
time = 0;
}}
/>
<button
class="play"
aria-label={paused ? 'play' : 'pause'}
on:click={() => paused = !paused}
/>
<div class="info">
<div class="description">
<strong>{title}</strong> /
<span>{artist}</span>
</div>
<div class="time">
<span>{format(time)}</span>
<div
class="slider"
on:pointerdown={e => {
const div = e.currentTarget;
function seek(e) {
const { left, width } = div.getBoundingClientRect();
let p = (e.clientX - left) / width;
if (p < 0) p = 0;
if (p > 1) p = 1;
time = p * duration;
}
seek(e);
window.addEventListener('pointermove', seek);
window.addEventListener('pointerup', () => {
window.removeEventListener('pointermove', seek);
}, {
once: true
});
}}
>
<div class="progress" style="--progress: {time / duration}%" />
</div>
<span>{duration ? format(duration) : '--:--'}</span>
</div>
</div>
</div>
<style>
.player {
display: grid;
grid-template-columns: 2.5em 1fr;
align-items: center;
gap: 1em;
padding: 0.5em 1em 0.5em 0.5em;
border-radius: 2em;
background: var(--bg-1);
transition: filter 0.2s;
color: var(--fg-3);
user-select: none;
}
.player:not(.paused) {
color: var(--fg-1);
filter: drop-shadow(0.5em 0.5em 1em rgba(0,0,0,0.1));
}
button {
width: 100%;
aspect-ratio: 1;
background-repeat: no-repeat;
background-position: 50% 50%;
border-radius: 50%;
}
[aria-label="pause"] {
background-image: url(./pause.svg);
}
[aria-label="play"] {
background-image: url(./play.svg);
}
.info {
overflow: hidden;
}
.description {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
line-height: 1.2;
}
.time {
display: flex;
align-items: center;
gap: 0.5em;
}
.time span {
font-size: 0.7em;
}
.slider {
flex: 1;
height: 0.5em;
background: var(--bg-2);
border-radius: 0.5em;
overflow: hidden;
}
.progress {
width: calc(100 * var(--progress));
height: 100%;
background: var(--bg-3);
}
</style>
The complete set of bindings for <audio>
and <video>
is as follows — seven readonly bindings...
- duration (readonly) — the total duration of the video, in seconds
- buffered (readonly) — an array of {start, end} objects
- seekable (readonly) — ditto
- played (readonly) — ditto
- seeking (readonly) — boolean
- ended (readonly) — boolean
- readyState (readonly) — number between (and including) 0 and 4
...and five two-way bindings:
- currentTime — the current point in the video, in seconds
- playbackRate — how fast to play the video, where 1 is 'normal'
- paused — this one should be self-explanatory
- volume — a value between 0 and 1
- muted — a boolean value where true is muted
Videos additionally have readonly videoWidth
and videoHeight
bindings.
Dimensions
Every block-level element has clientWidth
, clientHeight
, offsetWidth
and offsetHeight
bindings. These bindings are readonly.
let w;
let h;
let size = 42;
let text = 'edit this text';
</script>
<label>
<input type="range" bind:value={size} min="10" max="100" />
font size ({size}px)
</label>
<div bind:clientWidth={w} bind:clientHeight={h}>
<span style="font-size: {size}px" contenteditable>{text}</span>
<span class="size">{w} x {h}px</span>
</div>
<style>
div {
position: relative;
display: inline-block;
padding: 0.5rem;
background: hsla(15, 100%, 50%, 0.1);
border: 1px solid hsl(15, 100%, 50%);
}
.size {
position: absolute;
right: -1px;
bottom: -1.4em;
line-height: 1;
background: hsl(15, 100%, 50%);
color: white;
padding: 0.2em 0.5em;
white-space: pre;
}
</style>
There is some overhead involved, so it's not recommended to use this for large numbers of elements.
display: inline
elements cannot be measured with this approach; nor can elements that can't contain other elements (such as <canvas>
). In these cases you will need to measure a wrapper element instead.
This
To get a reference to a DOM node, use bind:this
:
<script>
import { onMount } from 'svelte';
function paint(context, t) {
const { width, height } = context.canvas;
const imageData = context.getImageData(0, 0, width, height);
for (let p = 0; p < imageData.data.length; p += 4) {
const i = p / 4;
const x = i % width;
const y = (i / width) >>> 0;
const red = 64 + (128 * x) / width + 64 * Math.sin(t / 1000);
const green = 64 + (128 * y) / height + 64 * Math.cos(t / 1000);
const blue = 128;
imageData.data[p + 0] = red;
imageData.data[p + 1] = green;
imageData.data[p + 2] = blue;
imageData.data[p + 3] = 255;
}
context.putImageData(imageData, 0, 0);
}
let canvas;
onMount(() => {
const context = canvas.getContext('2d');
let frame = requestAnimationFrame(function loop(t) {
frame = requestAnimationFrame(loop);
paint(context, t);
});
return () => {
cancelAnimationFrame(frame);
};
});
</script>
<canvas
bind:this={canvas}
width={32}
height={32}
/>
<style>
canvas {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: #666;
mask: url(./svelte-logo-mask.svg) 50% 50% no-repeat;
mask-size: 60vmin;
-webkit-mask: url(./svelte-logo-mask.svg) 50% 50% no-repeat;
-webkit-mask-size: 60vmin;
}
</style>
Component bindings
Just as you can bind to properties of DOM elements, you can bind to component props. For example, we can bind to the value
prop of this <Keypad>
component as though it were a form element:
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>
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="opacity: {pin ? 1 : 0.4}">
{view}
</h1>
<Keypad
bind:value={pin}
on:submit={handleSubmit}
/>
![](https://img.haomeiwen.com/i21032798/ebafecc723cabd2d.png)
Use component bindings sparingly. It can be difficult to track the flow of data around your application if you have too many of them.
Binding to component instances
Just as you can bind to DOM elements, you can bind to component instances themselves with bind:this
.
This is useful in the rare cases that you need to interact with a component programmatically (rather than by providing it with updated props).
网友评论