美文网首页
Getting started with Svelte - Ad

Getting started with Svelte - Ad

作者: 游文影月志 | 来源:发表于2023-12-12 00:43 被阅读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...

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

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

相关文章

网友评论

      本文标题:Getting started with Svelte - Ad

      本文链接:https://www.haomeiwen.com/subject/gtwdgdtx.html