본문 바로가기
Web과 프로그래밍 언어/JavaScript

[SVELTE] Special elements - svelte:self, component, element, window, window bindings, document, body, head, options, fragment

by cosmicgy 2023. 3. 17.

 

Special elements

 

1. svelte:self

 

<svelte:self>는 컴포넌트가 재귀적으로 자신을 포함할 수 있게 하는 요소이다. 폴더 트리구조와 같은 형태를 표현해야 할 때 유용하다.

 

<!-- App.svelte -->
<script>
    import Folder from './Folder.svelte';

    let root = [
        {
            name: 'Important work stuff',
            files: [
                { name: 'quarterly-results.xlsx' }
            ]
        },
        {
            name: 'Animal GIFs',
            files: [
                {
                    name: 'Dogs',
                    files: [
                        { name: 'treadmill.gif' },
                        { name: 'rope-jumping.gif' }
                    ]
                },
                {
                    name: 'Goats',
                    files: [
                        { name: 'parkour.gif' },
                        { name: 'rampage.gif' }
                    ]
                },
                { name: 'cat-roomba.gif' },
                { name: 'duck-shuffle.gif' },
                { name: 'monkey-on-a-pig.gif' }
            ]
        },
        { name: 'TODO.md' }
    ];
</script>

<Folder name="Home" files={root} expanded/>

<!-- File.svelte -->
<script>
    export let name;
    $: type = name.slice(name.lastIndexOf('.') + 1);
</script>

<span style="background-image: url(/tutorial/icons/{type}.svg)">{name}</span>

<style>
    span {
        padding: 0 0 0 1.5em;
        background: 0 0.1em no-repeat;
        background-size: 1em 1em;
    }
</style>

<!-- Folder.svelte -->
<script>
    import File from './File.svelte';

    export let expanded = false;
    export let name;
    export let files;

    function toggle() {
        expanded = !expanded;
    }
</script>

<button class:expanded on:click={toggle}>{name}</button>

{#if expanded}
    <ul>
        {#each files as file}
            <li>
                {#if file.files}
                    <svelte:self {...file}/>
                      <!-- <Folder {...file}/> 이지만 자기 자신을 import 하는 것은 불가능 하기 때문에 .. -->
                {:else}
                    <File {...file}/>
                {/if}
            </li>
        {/each}
    </ul>
{/if}

<style>
    button {
        padding: 0 0 0 1.5em;
        background: url(/tutorial/icons/folder.svg) 0 0.1em no-repeat;
        background-size: 1em 1em;
        font-weight: bold;
        cursor: pointer;
        border: none;
        margin: 0;
    }

    .expanded {
        background-image: url(/tutorial/icons/folder-open.svg);
    }

    ul {
        padding: 0.2em 0 0 0.5em;
        margin: 0 0 0 0.5em;
        list-style: none;
        border-left: 1px solid #eee;
    }

    li {
        padding: 0.2em 0;
    }
</style>

 

2. svelte:component

 

<!-- App.svelte -->
<script>
    import RedThing from './RedThing.svelte';
    import GreenThing from './GreenThing.svelte';
    import BlueThing from './BlueThing.svelte';

    const options = [
        { color: 'red',   component: RedThing   },
        { color: 'green', component: GreenThing },
        { color: 'blue',  component: BlueThing  },
    ];

    let selected = options[0];
</script>

<select bind:value={selected}>
    {#each options as option}
        <option value={option}>{option.color}</option>
    {/each}
</select>

<svelte:component this={selected.component}/>

<!-- {#if selected.color === 'red'}
    <RedThing/>
{:else if selected.color === 'green'}
    <GreenThing/>
{:else if selected.color === 'blue'}
    <BlueThing/>
{/if} -->

<!-- BlueThing.svelte -->
<strong>Blue thing</strong>

<style>
    strong {
        color: blue;
    }
</style>

<!-- GreenThing.svelte -->
<strong>Green thing</strong>

<style>
    strong {
        color: green;
    }
</style>

<!-- RedThing.svelte -->
<strong>Red thing</strong>

<style>
    strong {
        color: red;
    }
</style>
  • 위의 코드에서 사용된 this는 컴포넌트나 falsy 값이 올 수 있다. falsy가 올 경우 컴포넌트는 렌더링 되지 않는다.

 

3. svelte.window

 

DOM 요소에 이벤트를 등록하는 것처럼 <svelte:window>를 사용하여 window 객체에 이벤트를 등록할 수 있다. DOM 요소와 동일하게 preventDefault와 같은 이벤트 수식어를 사용할 수 있다.

 

<script>
    let key;
    let code;

    function handleKeydown(event) {
        key = event.key;
        code = event.code;
    }
</script>

<svelte:window on:keydown={handleKeydown}/>

<div style="text-align: center">
    {#if key}
        <kbd>{key === ' ' ? 'Space' : key}</kbd>
        <p>{code}</p>
    {:else}
        <p>Focus this window and press any key</p>
    {/if}
</div>

<style>
    div {
        display: flex;
        height: 100%;
        align-items: center;
        justify-content: center;
        flex-direction: column;
    }

    kbd {
        background-color: #eee;
        border-radius: 4px;
        font-size: 6em;
        padding: 0.2em 0.5em;
        border-top: 5px solid rgba(255, 255, 255, 0.5);
        border-left: 5px solid rgba(255, 255, 255, 0.5);
        border-right: 5px solid rgba(0, 0, 0, 0.2);
        border-bottom: 5px solid rgba(0, 0, 0, 0.2);
        color: #555;
    }
</style>

 

4. binding

 

window 객체의 scrolY 등의 값을 바인딩 할 수 있다.

  • 바인딩 가능한 목록
    : innerWidth, innerHeight, outerWidth, outerHeight, scrollX, scrollY, online (scrollX와 scrollY 를 제외하고는 모두 readonly이다)

 

<script>
    const layers = [0, 1, 2, 3, 4, 5, 6, 7, 8];

    let y;
</script>

<svelte:window bind:scrollY={y}/>

<a class="parallax-container" href="https://www.firewatchgame.com">
    {#each layers as layer}
        <img
            style="transform: translate(0,{-y * layer / (layers.length - 1)}px)"
            src="https://www.firewatchgame.com/images/parallax/parallax{layer}.png"
            alt="parallax layer {layer}"
        >
    {/each}
</a>

<div class="text">
    <span style="opacity: {1 - Math.max(0, y / 40)}">
        scroll down
    </span>

    <div class="foreground">
        You have scrolled {y} pixels
    </div>
</div>

<style>
    .parallax-container {
        position: fixed;
        width: 2400px;
        height: 712px;
        left: 50%;
        transform: translate(-50%,0);
    }

    .parallax-container img {
        position: absolute;
        top: 0;
        left: 0;
        width: 100%;
        will-change: transform;
    }

    .parallax-container img:last-child::after {
        content: '';
        position: absolute;
        width: 100%;
        height: 100%;
        background: rgb(45,10,13);
    }

    .text {
        position: relative;
        width: 100%;
        height: 300vh;
        color: rgb(220,113,43);
        text-align: center;
        padding: 4em 0.5em 0.5em 0.5em;
        box-sizing: border-box;
        pointer-events: none;
    }

    span {
        display: block;
        font-size: 1em;
        text-transform: uppercase;
        will-change: transform, opacity;
    }

    .foreground {
        position: absolute;
        top: 711px;
        left: 0;
        width: 100%;
        height: calc(100% - 712px);
        background-color: rgb(32,0,1);
        color: white;
        padding: 50vh 0 0 0;
    }

    :global(body) {
        margin: 0;
        padding: 0;
        background-color: rgb(253, 174, 51);
    }
</style>

 

5. svelte:body

 

window 객체에 이벤트를 등록하는 것처럼 document.body에도 이벤트 등록이 가능하다. window 객체에는 mouseenter, mouseleave 이벤트를 등록할 수 없지만 <svelte:body>를 사용하면 등록 가능하다.

 

<script>
    let hereKitty = false;

    const handleMouseenter = () => hereKitty = true;
    const handleMouseleave = () => hereKitty = false;
</script>

<svelte:body
    on:mouseenter={handleMouseenter}
    on:mouseleave={handleMouseleave}
/>

<!-- creative commons BY-NC http://www.pngall.com/kitten-png/download/7247 -->
<img
    class:curious={hereKitty}
    alt="Kitten wants to know what's going on"
    src="/tutorial/kitten.png"
>

<style>
    img {
        position: absolute;
        left: 0;
        bottom: -60px;
        transform: translate(-80%, 0) rotate(-30deg);
        transform-origin: 100% 100%;
        transition: transform 0.4s;
    }

    .curious {
        transform: translate(-15%, 0) rotate(0deg);
    }

    :global(body) {
        overflow: hidden;
    }
</style>

 

6. svelte:head

 

<svelte:head>
    <link rel="stylesheet" href="/tutorial/dark-theme.css">
</svelte:head>

<h1>Hello world!</h1>

 

7. svelte:options

 

<svelte:options>를 사용하면 컴파일 옵션을 지정할 수 있다.

 

<svelte:options option={value}/>
  • immutable={true} : 변하지 않는 데이터를 사용할 것이라고 컴파일러에게 알려주는 옵션이다. 컴파일러는 간단한 검사로 참조 값이 변경되었는지 검사한다.
  • immutable={false} : 기본값. 좀 더 보수적으로, 엄격하게 값이 변경되었는지 확인하는 옵션이다.
  • accessors={true} : 컴포넌트의 props에 getter와 setter를 추가한다.
  • accessors={false} : 기본값.
  • namespace:"..." : 컴포넌트가 사용될 네임스페이스. 일반적으로 svg에서 사용된다.
  • tag="..." : 컴포넌트를 사용자가 정의한 요소로 컴파일할 때 사용되는 옵션.

 

<!-- App.svelte -->
<script>
    import Todo from './Todo.svelte';

    let todos = [
        { id: 1, done: true, text: 'wash the car' },
        { id: 2, done: false, text: 'take the dog for a walk' },
        { id: 3, done: false, text: 'mow the lawn' }
    ];

    function toggle(toggled) {
        todos = todos.map(todo => {
            if (todo === toggled) {
                // return a new object
                return {
                    id: todo.id,
                    text: todo.text,
                    done: !todo.done
                };
            }

            // return the same object
            return todo;
        });
    }
</script>

<h2>Todos</h2>
{#each todos as todo}
    <Todo {todo} on:click={() => toggle(todo)}/>
{/each}

<!-- Todo.svelte -->
<svelte:options immutable={true}/>

<script>
    import { afterUpdate } from 'svelte';
    import flash from './flash.js';

    export let todo;

    let button;

    afterUpdate(() => {
        flash(button);
    });
</script>

<!-- the text will flash red whenever
     the `todo` object changes -->
<button bind:this={button} type="button" on:click>
    {todo.done ? '👍': ''} {todo.text}
</button>

<style>
    button {
        all: unset;
        display: block;
        cursor: pointer;
        line-height: 1.5;
    }
</style>

 

// flash.js
export default function flash(element) {
    requestAnimationFrame(() => {
        element.style.transition = 'none';
        element.style.color = 'rgba(255,62,0,1)';
        element.style.backgroundColor = 'rgba(255,62,0,0.2)';

        setTimeout(() => {
            element.style.transition = 'color 1s, background 1s';
            element.style.color = '';
            element.style.backgroundColor = '';
        });
    });
}
  • immutable를 사용하지 않으면 보수적인 검사를 실행하기 때문에, todos가 재할당 되었을 때 모든 리스트 목록이 변경된 것으로 인식한다.

 

9. svelte:fragment

 

<!-- App.svelte -->
<script>
    import Box from './Box.svelte'
</script>

<Box>
    <svelte:fragment slot="footer">
        <p>All rights reserved.</p>
        <p>Copyright (c) 2019 Svelte Industries</p>
    </svelte:fragment>
</Box>

<!-- Box.svelte -->
<div class="box">
    <slot name="header">No header was provided</slot>
    <p>Some content between header and footer</p>
    <slot name="footer"></slot>
</div>

<style>
    .box {
        width: 300px;
        border: 1px solid #aaa;
        border-radius: 2px;
        box-shadow: 2px 2px 8px rgba(0, 0, 0, 0.1);
        padding: 1em;
        margin: 0 0 1em 0;

 

<div slot="footer">
        <p>All rights reserved.</p>
        <p>Copyright (c) 2019 Svelte Industries</p>
    </div>

div tag를 사용하게 되면 box에 설정한 padding 등의 스타일이 적용되지 않는다.