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 등의 스타일이 적용되지 않는다.