Getting started with Svelte - Ad

2023-12-12  本文已影响0人  游文影月志

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...

...and five two-way bindings:

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}
/>

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).

上一篇 下一篇

猜你喜欢

热点阅读