0
0
mirror of https://github.com/sveltejs/svelte.git synced 2024-11-30 08:56:14 +01:00
svelte/documentation/content/docs/02-template-syntax/05-element-directives.md

802 lines
21 KiB
Markdown
Raw Normal View History

---
title: Element directives
---
As well as attributes, elements can have _directives_, which control the element's behaviour in some way.
## on:_eventname_
```svelte
on:eventname={handler}
```
```svelte
on:eventname|modifiers={handler}
```
Use the `on:` directive to listen to DOM events.
```svelte
<!--- file: App.svelte --->
<script>
let count = 0;
/** @param {MouseEvent} event */
function handleClick(event) {
count += 1;
}
</script>
<button on:click={handleClick}>
count: {count}
</button>
```
Handlers can be declared inline with no performance penalty. As with attributes, directive values may be quoted for the sake of syntax highlighters.
```svelte
<button on:click={() => (count += 1)}>
count: {count}
</button>
```
Add _modifiers_ to DOM events with the `|` character.
```svelte
<form on:submit|preventDefault={handleSubmit}>
<!-- the `submit` event's default is prevented,
so the page won't reload -->
</form>
```
The following modifiers are available:
- `preventDefault` — calls `event.preventDefault()` before running the handler
- `stopPropagation` — calls `event.stopPropagation()`, preventing the event reaching the next element
- `stopImmediatePropagation` - calls `event.stopImmediatePropagation()`, preventing other listeners of the same event from being fired.
- `passive` — improves scrolling performance on touch/wheel events (Svelte will add it automatically where it's safe to do so)
- `nonpassive` — explicitly set `passive: false`
- `capture` — fires the handler during the _capture_ phase instead of the _bubbling_ phase
- `once` — remove the handler after the first time it runs
- `self` — only trigger handler if `event.target` is the element itself
- `trusted` — only trigger handler if `event.isTrusted` is `true`. I.e. if the event is triggered by a user action.
Modifiers can be chained together, e.g. `on:click|once|capture={...}`.
If the `on:` directive is used without a value, the component will _forward_ the event, meaning that a consumer of the component can listen for it.
```svelte
<button on:click> The component itself will emit the click event </button>
```
It's possible to have multiple event listeners for the same event:
```svelte
<!--- file: App.svelte --->
<script>
let counter = 0;
function increment() {
counter = counter + 1;
}
/** @param {MouseEvent} event */
function track(event) {
trackEvent(event);
}
</script>
<button on:click={increment} on:click={track}>Click me!</button>
```
## bind:_property_
```svelte
bind:property={variable}
```
Data ordinarily flows down, from parent to child. The `bind:` directive allows data to flow the other way, from child to parent. Most bindings are specific to particular elements.
The simplest bindings reflect the value of a property, such as `input.value`.
```svelte
<input bind:value={name} />
<textarea bind:value={text} />
<input type="checkbox" bind:checked={yes} />
```
If the name matches the value, you can use shorthand.
```svelte
<!-- These are equivalent -->
<input bind:value />
<input bind:value />
```
Numeric input values are coerced; even though `input.value` is a string as far as the DOM is concerned, Svelte will treat it as a number. If the input is empty or invalid (in the case of `type="number"`), the value is `undefined`.
```svelte
<input type="number" bind:value={num} />
<input type="range" bind:value={num} />
```
On `<input>` elements with `type="file"`, you can use `bind:files` to get the [`FileList` of selected files](https://developer.mozilla.org/en-US/docs/Web/API/FileList). It is readonly.
```svelte
<label for="avatar">Upload a picture:</label>
<input accept="image/png, image/jpeg" bind:files id="avatar" name="avatar" type="file" />
```
If you're using `bind:` directives together with `on:` directives, the order that they're defined in affects the value of the bound variable when the event handler is called.
```svelte
<script>
let value = 'Hello World';
</script>
<input
on:input={() => console.log('Old value:', value)}
bind:value
on:input={() => console.log('New value:', value)}
/>
```
Here we were binding to the value of a text input, which uses the `input` event. Bindings on other elements may use different events such as `change`.
## Binding `<select>` value
A `<select>` value binding corresponds to the `value` property on the selected `<option>`, which can be any value (not just strings, as is normally the case in the DOM).
```svelte
<select bind:value={selected}>
<option value={a}>a</option>
<option value={b}>b</option>
<option value={c}>c</option>
</select>
```
A `<select multiple>` element behaves similarly to a checkbox group. The bound variable is an array with an entry corresponding to the `value` property of each selected `<option>`.
```svelte
<select multiple bind:value={fillings}>
<option value="Rice">Rice</option>
<option value="Beans">Beans</option>
<option value="Cheese">Cheese</option>
<option value="Guac (extra)">Guac (extra)</option>
</select>
```
When the value of an `<option>` matches its text content, the attribute can be omitted.
```svelte
<select multiple bind:value={fillings}>
<option>Rice</option>
<option>Beans</option>
<option>Cheese</option>
<option>Guac (extra)</option>
</select>
```
Elements with the `contenteditable` attribute support the following bindings:
- [`innerHTML`](https://developer.mozilla.org/en-US/docs/Web/API/Element/innerHTML)
- [`innerText`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/innerText)
- [`textContent`](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent)
There are slight differences between each of these, read more about them [here](https://developer.mozilla.org/en-US/docs/Web/API/Node/textContent#Differences_from_innerText).
```svelte
<div contenteditable="true" bind:innerHTML={html} />
```
`<details>` elements support binding to the `open` property.
```svelte
<details bind:open={isOpen}>
<summary>Details</summary>
<p>Something small enough to escape casual notice.</p>
</details>
```
## Media element bindings
Media elements (`<audio>` and `<video>`) have their own set of bindings — seven _readonly_ ones...
- `duration` (readonly) — the total duration of the video, in seconds
- `buffered` (readonly) — an array of `{start, end}` objects
- `played` (readonly) — ditto
- `seekable` (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 playback time in the video, in seconds
- `playbackRate` — how fast or slow 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 indicating whether the player is muted
Videos additionally have readonly `videoWidth` and `videoHeight` bindings.
```svelte
<video
src={clip}
bind:duration
bind:buffered
bind:played
bind:seekable
bind:seeking
bind:ended
bind:readyState
bind:currentTime
bind:playbackRate
bind:paused
bind:volume
bind:muted
bind:videoWidth
bind:videoHeight
/>
```
## Image element bindings
Image elements (`<img>`) have two readonly bindings:
- `naturalWidth` (readonly) — the original width of the image, available after the image has loaded
- `naturalHeight` (readonly) — the original height of the image, available after the image has loaded
```svelte
<img
bind:naturalWidth
bind:naturalHeight
></img>
```
## Block-level element bindings
Block-level elements have 4 read-only bindings, measured using a technique similar to [this one](http://www.backalleycoder.com/2013/03/18/cross-browser-event-based-element-resize-detection/):
- `clientWidth`
- `clientHeight`
- `offsetWidth`
- `offsetHeight`
```svelte
<div bind:offsetWidth={width} bind:offsetHeight={height}>
<Chart {width} {height} />
</div>
```
## bind:group
```svelte
bind:group={variable}
```
Inputs that work together can use `bind:group`.
```svelte
<!--- file: App.svelte --->
<script>
let tortilla = 'Plain';
/** @type {Array<string>} */
let fillings = [];
</script>
<!-- grouped radio inputs are mutually exclusive -->
<input type="radio" bind:group={tortilla} value="Plain" />
<input type="radio" bind:group={tortilla} value="Whole wheat" />
<input type="radio" bind:group={tortilla} value="Spinach" />
<!-- grouped checkbox inputs populate an array -->
<input type="checkbox" bind:group={fillings} value="Rice" />
<input type="checkbox" bind:group={fillings} value="Beans" />
<input type="checkbox" bind:group={fillings} value="Cheese" />
<input type="checkbox" bind:group={fillings} value="Guac (extra)" />
```
> `bind:group` only works if the inputs are in the same Svelte component.
## bind:this
```svelte
bind:this={dom_node}
```
To get a reference to a DOM node, use `bind:this`.
```svelte
<!--- file: App.svelte --->
<script>
import { onMount } from 'svelte';
/** @type {HTMLCanvasElement} */
let canvasElement;
onMount(() => {
const ctx = canvasElement.getContext('2d');
drawStuff(ctx);
});
</script>
<canvas bind:this={canvasElement} />
```
## class:_name_
```svelte
class:name={value}
```
```svelte
class:name
```
A `class:` directive provides a shorter way of toggling a class on an element.
```svelte
<!-- These are equivalent -->
<div class={active ? 'active' : ''}>...</div>
<div class:active>...</div>
<!-- Shorthand, for when name and value match -->
<div class:active>...</div>
<!-- Multiple class toggles can be included -->
<div class:active class:inactive={!active} class:isAdmin>...</div>
```
## style:_property_
```svelte
style:property={value}
```
```svelte
style:property="value"
```
```svelte
style:property
```
The `style:` directive provides a shorthand for setting multiple styles on an element.
```svelte
<!-- These are equivalent -->
<div style:color="red">...</div>
<div style="color: red;">...</div>
<!-- Variables can be used -->
<div style:color={myColor}>...</div>
<!-- Shorthand, for when property and variable name match -->
<div style:color>...</div>
<!-- Multiple styles can be included -->
<div style:color style:width="12rem" style:background-color={darkMode ? 'black' : 'white'}>...</div>
<!-- Styles can be marked as important -->
<div style:color="red">...</div>
```
When `style:` directives are combined with `style` attributes, the directives will take precedence:
```svelte
<div style="color: blue;" style:color="red">This will be red</div>
```
## use:_action_
```svelte
use:action
```
```svelte
use:action={parameters}
```
```ts
// @noErrors
action = (node: HTMLElement, parameters: any) => {
update?: (parameters: any) => void,
destroy?: () => void
}
```
Actions are functions that are called when an element is created. They can return an object with a `destroy` method that is called after the element is unmounted:
```svelte
<!--- file: App.svelte --->
<script>
/** @type {import('svelte/action').Action} */
function foo(node) {
// the node has been mounted in the DOM
return {
destroy() {
// the node has been removed from the DOM
}
};
}
</script>
<div use:foo />
```
An action can have a parameter. If the returned value has an `update` method, it will be called whenever that parameter changes, immediately after Svelte has applied updates to the markup.
> Don't worry about the fact that we're redeclaring the `foo` function for every component instance — Svelte will hoist any functions that don't depend on local state out of the component definition.
```svelte
<!--- file: App.svelte --->
<script>
export let bar;
/** @type {import('svelte/action').Action} */
function foo(node, bar) {
// the node has been mounted in the DOM
return {
update(bar) {
// the value of `bar` has changed
},
destroy() {
// the node has been removed from the DOM
}
};
}
</script>
<div use:foo={bar} />
```
## transition:_fn_
```svelte
transition:fn
```
```svelte
transition:fn={params}
```
```svelte
transition:fn|local
```
```svelte
transition:fn|local={params}
```
```js
// @noErrors
transition = (node: HTMLElement, params: any, options: { direction: 'in' | 'out' | 'both' }) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
```
A transition is triggered by an element entering or leaving the DOM as a result of a state change.
When a block is transitioning out, all elements inside the block, including those that do not have their own transitions, are kept in the DOM until every transition in the block has been completed.
The `transition:` directive indicates a _bidirectional_ transition, which means it can be smoothly reversed while the transition is in progress.
```svelte
{#if visible}
<div transition:fade>fades in and out</div>
{/if}
```
> By default intro transitions will not play on first render. You can modify this behaviour by setting `intro: true` when you [create a component](/docs/client-side-component-api).
## Transition parameters
Like actions, transitions can have parameters.
(The double `{{curlies}}` aren't a special syntax; this is an object literal inside an expression tag.)
```svelte
{#if visible}
<div transition:fade={{ duration: 2000 }}>fades in and out over two seconds</div>
{/if}
```
## Custom transition functions
Transitions can use custom functions. If the returned object has a `css` function, Svelte will create a CSS animation that plays on the element.
The `t` argument passed to `css` is a value between `0` and `1` after the `easing` function has been applied. _In_ transitions run from `0` to `1`, _out_ transitions run from `1` to `0` in other words, `1` is the element's natural state, as though no transition had been applied. The `u` argument is equal to `1 - t`.
The function is called repeatedly _before_ the transition begins, with different `t` and `u` arguments.
```svelte
<!--- file: App.svelte --->
<script>
import { elasticOut } from 'svelte/easing';
/** @type {boolean} */
export let visible;
function whoosh(node, params) {
const existingTransform = getComputedStyle(node).transform.replace('none', '');
return {
delay: params.delay || 0,
duration: params.duration || 400,
easing: params.easing || elasticOut,
css: (t, u) => `transform: ${existingTransform} scale(${t})`
};
}
</script>
{#if visible}
<div in:whoosh>whooshes in</div>
{/if}
```
A custom transition function can also return a `tick` function, which is called _during_ the transition with the same `t` and `u` arguments.
> If it's possible to use `css` instead of `tick`, do so — CSS animations can run off the main thread, preventing jank on slower devices.
```svelte
<script>
export let visible = false;
function typewriter(node, { speed = 1 }) {
const valid = node.childNodes.length === 1 && node.childNodes[0].nodeType === Node.TEXT_NODE;
if (!valid) {
throw new Error(`This transition only works on elements with a single text node child`);
}
const text = node.textContent;
const duration = text.length / (speed * 0.01);
return {
duration,
tick: (t) => {
const i = ~~(text.length * t);
node.textContent = text.slice(0, i);
}
};
}
</script>
{#if visible}
<p in:typewriter={{ speed: 1 }}>The quick brown fox jumps over the lazy dog</p>
{/if}
```
If a transition returns a function instead of a transition object, the function will be called in the next microtask. This allows multiple transitions to coordinate, making [crossfade effects](/tutorial/deferred-transitions) possible.
Transition functions also receive a third argument, `options`, which contains information about the transition.
Available values in the `options` object are:
- `direction` - one of `in`, `out`, or `both` depending on the type of transition
## Transition events
An element with transitions will dispatch the following events in addition to any standard DOM events:
- `introstart`
- `introend`
- `outrostart`
- `outroend`
```svelte
{#if visible}
<p
transition:fly={{ y: 200, duration: 2000 }}
on:introstart={() => (status = 'intro started')}
on:outrostart={() => (status = 'outro started')}
on:introend={() => (status = 'intro ended')}
on:outroend={() => (status = 'outro ended')}
>
Flies in and out
</p>
{/if}
```
Local transitions only play when the block they belong to is created or destroyed, _not_ when parent blocks are created or destroyed.
```svelte
{#if x}
{#if y}
<p transition:fade>fades in and out when x or y change</p>
<p transition:fade|local>fades in and out only when y changes</p>
{/if}
{/if}
```
## in:_fn_/out:_fn_
```svelte
in:fn
```
```svelte
in:fn={params}
```
```svelte
in:fn|local
```
```svelte
in:fn|local={params}
```
```svelte
out:fn
```
```svelte
out:fn={params}
```
```svelte
out:fn|local
```
```svelte
out:fn|local={params}
```
Similar to `transition:`, but only applies to elements entering (`in:`) or leaving (`out:`) the DOM.
Unlike with `transition:`, transitions applied with `in:` and `out:` are not bidirectional — an in transition will continue to 'play' alongside the out transition, rather than reversing, if the block is outroed while the transition is in progress. If an out transition is aborted, transitions will restart from scratch.
```svelte
{#if visible}
<div in:fly out:fade>flies in, fades out</div>
{/if}
```
## animate:_fn_
```svelte
animate:name
```
```svelte
animate:name={params}
```
```js
// @noErrors
animation = (node: HTMLElement, { from: DOMRect, to: DOMRect } , params: any) => {
delay?: number,
duration?: number,
easing?: (t: number) => number,
css?: (t: number, u: number) => string,
tick?: (t: number, u: number) => void
}
```
```ts
// @noErrors
DOMRect {
bottom: number,
height: number,
left: number,
right: number,
top: number,
width: number,
x: number,
y: number
}
```
An animation is triggered when the contents of a [keyed each block](/docs/logic-blocks#each) are re-ordered. Animations do not run when an element is added or removed, only when the index of an existing data item within the each block changes. Animate directives must be on an element that is an _immediate_ child of a keyed each block.
Animations can be used with Svelte's [built-in animation functions](/docs/svelte-animate) or [custom animation functions](/docs/element-directives#custom-transition-functions).
```svelte
<!-- When `list` is reordered the animation will run-->
{#each list as item, index (item)}
<li animate:flip>{item}</li>
{/each}
```
## Animation Parameters
As with actions and transitions, animations can have parameters.
(The double `{{curlies}}` aren't a special syntax; this is an object literal inside an expression tag.)
```svelte
{#each list as item, index (item)}
<li animate:flip={{ delay: 500 }}>{item}</li>
{/each}
```
## Custom animation functions
Animations can use custom functions that provide the `node`, an `animation` object and any `parameters` as arguments. The `animation` parameter is an object containing `from` and `to` properties each containing a [DOMRect](https://developer.mozilla.org/en-US/docs/Web/API/DOMRect#Properties) describing the geometry of the element in its `start` and `end` positions. The `from` property is the DOMRect of the element in its starting position, and the `to` property is the DOMRect of the element in its final position after the list has been reordered and the DOM updated.
If the returned object has a `css` method, Svelte will create a CSS animation that plays on the element.
The `t` argument passed to `css` is a value that goes from `0` and `1` after the `easing` function has been applied. The `u` argument is equal to `1 - t`.
The function is called repeatedly _before_ the animation begins, with different `t` and `u` arguments.
<!-- TODO: Types -->
```svelte
<!--- file: App.svelte --->
<script>
import { cubicOut } from 'svelte/easing';
/**
* @param {HTMLElement} node
* @param {Object} states
* @param {DOMRect} states.from
* @param {DOMRect} states.to
* @param {any} params
*/
function whizz(node, { from, to }, params) {
const dx = from.left - to.left;
const dy = from.top - to.top;
const d = Math.sqrt(dx * dx + dy * dy);
return {
delay: 0,
duration: Math.sqrt(d) * 120,
easing: cubicOut,
css: (t, u) => `transform: translate(${u * dx}px, ${u * dy}px) rotate(${t * 360}deg);`
};
}
</script>
{#each list as item, index (item)}
<div animate:whizz>{item}</div>
{/each}
```
A custom animation function can also return a `tick` function, which is called _during_ the animation with the same `t` and `u` arguments.
> If it's possible to use `css` instead of `tick`, do so — CSS animations can run off the main thread, preventing jank on slower devices.
```svelte
<script>
import { cubicOut } from 'svelte/easing';
/**
* @param {HTMLElement} node
* @param {Object} states
* @param {DOMRect} states.from
* @param {DOMRect} states.to
* @param {any} params
*/
function whizz(node, { from, to }, params) {
const dx = from.left - to.left;
const dy = from.top - to.top;
const d = Math.sqrt(dx * dx + dy * dy);
return {
delay: 0,
duration: Math.sqrt(d) * 120,
easing: cubicOut,
tick: (t, u) => Object.assign(node.style, { color: t > 0.5 ? 'Pink' : 'Blue' })
};
}
</script>
{#each list as item, index (item)}
<div animate:whizz>{item}</div>
{/each}
```