Showing posts with label svelte. Show all posts
Showing posts with label svelte. Show all posts

2020-04-25

Svelte Tutorial 13: Classes (English & Indonesia)

English

The Class Directive


Like any other attribute, you can specify classes with a JavaScript attribute, seen here:

<button
  class="{current === 'foo' ? 'active' : ''}"
  on:click="{() => current = 'foo'}"
>foo</button>

This is such a common pattern in UI development that Svelte includes a special directive to simplify it:

<button
class:active="{current === 'foo'}"
on:click="{() => current = 'foo'}"
>foo</button>

The active class is added to the element whenever the value of the expression is truthy, and removed when it's falsy. So as you can see in the example below, once class is set to active, the button will change color according to what css is set to, in this case, to color orange.

<script>
  let current = 'foo';
</script>

<style>
  button {
    display: block;
  }

  .active {
    background-color: #ff3e00;
    color: white;
  }
</style>

<button
  class:active="{current === 'foo'}"
  on:click="{() => current = 'foo'}"
>foo</button>

<button
  class:active="{current === 'bar'}"
  on:click="{() => current = 'bar'}"
>bar</button>

<button
  class:active="{current === 'baz'}"
  on:click="{() => current = 'baz'}"
>baz</button>

Shorthand Class Directive


Often, the name of the class will be the same as the name of the value it depends on:

<div class:big={big}>
<!-- ... -->
</div>

In those cases we can use a shorthand form:

<div class:big>
<!-- ... -->
</div>

Here is full example when variable big is set to true as the checkbox is checked, the class of div also change to big, so the font also get bigger as it's set in the css.

<script>
  let big = false;
</script>

<style>
  .big {
    font-size: 4em;
  }
</style>

<label>
  <input type=checkbox bind:checked={big}> big
</label>

<div class:big>
  some {big ? 'big' : 'small'} text
</div>




Indonesia

The Class Directive


Seperti atribut yang lain, kita juga bisa menentukan class dengan atribut JavaScript, seperti berikut:

<button
  class="{current === 'foo' ? 'active' : ''}"
  on:click="{() => current = 'foo'}"
>foo</button>

Karena begitu seringnya digunakan dalam pembuatan UI, maka Svelte memasukkan direktiv spesial untuk membuat proses ini lebih mudah:

<button
class:active="{current === 'foo'}"
on:click="{() => current = 'foo'}"
>foo</button>

Class active akan ditambahkan pada elemen kapanpun hasil dari ekspresi adalah benar, dan dihilangkan jika hasilnya salah. Jadi seperti yang bisa dilihat pada contoh di atas, begitu class sudah diset menjadi active, tombol akan berubah warna sesuai dengan yang sudah diatur di dalam css, dalam hal ini menjadi warna oranye.

<script>
  let current = 'foo';
</script>

<style>
  button {
    display: block;
  }

  .active {
    background-color: #ff3e00;
    color: white;
  }
</style>

<button
  class:active="{current === 'foo'}"
  on:click="{() => current = 'foo'}"
>foo</button>

<button
  class:active="{current === 'bar'}"
  on:click="{() => current = 'bar'}"
>bar</button>

<button
  class:active="{current === 'baz'}"
  on:click="{() => current = 'baz'}"
>baz</button>

Shorthand Class Directive


Seringkali, nama dari class akan sama dengan nama variabel yang menentukan class tersebut:

<div class:big={big}>
<!-- ... -->
</div>

Pada kasus seperti itu kita bisa menggunakan bentuk singkat:

<div class:big>
<!-- ... -->
</div>

Berikut adalah contoh ketika variabel big diset true saat checkbox dicentang, maka class dari div juga akan berubah menjadi big, jadi ukuran font juga akan menjadi lebih besar seperti seting di css.

<script>
  let big = false;
</script>

<style>
  .big {
    font-size: 4em;
  }
</style>

<label>
  <input type=checkbox bind:checked={big}> big
</label>

<div class:big>
  some {big ? 'big' : 'small'} text
</div>

Svelte Tutorial 12: Actions (English & Indonesia)

English


The Use Directive


Actions are essentially element-level lifecycle functions. They're useful for things like:
  • interfacing with third-party libraries
  • lazy-loaded images
  • tooltips
  • adding custom event handlers
In this app, we want to make the orange box 'pannable'. It has event handlers for the panstartpanmove and panend events, but these aren't native DOM events. We have to dispatch them ourselves. First, import the pannable function and then use it with <div class="box"> element.

<script>
  import { spring } from 'svelte/motion';
  import { pannable } from './pannable.js';

  const coords = spring({ x: 0, y: 0 }, {
    stiffness: 0.2,
    damping: 0.4
  });

  function handlePanStart() {
    coords.stiffness = coords.damping = 1;
  }

  function handlePanMove(event) {
    coords.update($coords => ({
      x: $coords.x + event.detail.dx,
      y: $coords.y + event.detail.dy
    }));
  }

  function handlePanEnd(event) {
    coords.stiffness = 0.2;
    coords.damping = 0.4;
    coords.set({ x: 0, y: 0 });
  }
</script>

<style>
  .box {
    --width: 100px;
    --height: 100px;
    position: absolute;
    width: var(--width);
    height: var(--height);
    left: calc(50% - var(--width) / 2);
    top: calc(50% - var(--height) / 2);
    border-radius: 4px;
    background-color: #ff3e00;
    cursor: move;
  }
</style>

<div class="box"
  use:pannable
  on:panstart={handlePanStart}
  on:panmove={handlePanMove}
  on:panend={handlePanEnd}
  style="transform:
    translate({$coords.x}px,{$coords.y}px)
    rotate({$coords.x * 0.2}deg)"
></div>

Then on the pannable.js file. Like transition functions, an action function receives a node and some optional parameters, and returns an action object. That object can have a destroy function, which is called when the element is unmounted.

We want to fire panstart event when the user mouses down on the element, panmove events (with dx and dy properties showing how far the mouse moved) when they drag it, and panend events when they mouse up. One possible implementation looks like this, you can try to move box around.

export function pannable(node) {
  let x;
  let y;

  function handleMousedown(event) {
    x = event.clientX;
    y = event.clientY;

    node.dispatchEvent(new CustomEvent('panstart', {
      detail: { x, y }
    }));

    window.addEventListener('mousemove', handleMousemove);
    window.addEventListener('mouseup', handleMouseup);
  }

  function handleMousemove(event) {
    const dx = event.clientX - x;
    const dy = event.clientY - y;
    x = event.clientX;
    y = event.clientY;

    node.dispatchEvent(new CustomEvent('panmove', {
      detail: { x, y, dx, dy }
    }));
  }

  function handleMouseup(event) {
    x = event.clientX;
    y = event.clientY;

    node.dispatchEvent(new CustomEvent('panend', {
      detail: { x, y }
    }));

    window.removeEventListener('mousemove', handleMousemove);
    window.removeEventListener('mouseup', handleMouseup);
  }

  node.addEventListener('mousedown', handleMousedown);

  return {
    destroy() {
      node.removeEventListener('mousedown', handleMousedown);
    }
  };
}

Adding Parameters


Like transitions and animations, an action can take an argument, which the action function will be called with alongside the element it belongs to.

Here, we're using a longpress action that fires an event with the same name whenever the user presses and holds the button for a given duration. Here is the example of longpress.js. Important part is duration in second parameter of function declaration; pass duration value in setTimeout; and update(newDuration) in return value.

export function longpress(node, duration) {
  let timer;

  const handleMousedown = () => {
    timer = setTimeout(() => {
      node.dispatchEvent(
        new CustomEvent('longpress')
      );
    }, duration);
  };

  const handleMouseup = () => {
    clearTimeout(timer)
  };

  node.addEventListener('mousedown', handleMousedown);
  node.addEventListener('mouseup', handleMouseup);

  return {
    update(newDuration) {
      duration = newDuration;
    },
    destroy() {
      node.removeEventListener('mousedown', handleMousedown);
      node.removeEventListener('mouseup', handleMouseup);
    }
  };
}

And here is App.svelte, where we can pass the duration value to the action using this <button use:longpress={duration}:

<script>
  import { longpress } from './longpress.js';

  let pressed = false;
  let duration = 2000;
</script>

<label>
  <input type=range bind:value={duration} max={2000} step={100}>
  {duration}ms
</label>

<button use:longpress={duration}
  on:longpress="{() => pressed = true}"
  on:mouseenter="{() => pressed = false}"
>press and hold</button>

{#if pressed}
  <p>congratulations, you pressed and held for {duration}ms</p>
{/if}



Indonesia


The Use Directive


Pada dasarnya action adalah fungsi lifecycle pada tingkat elemen. Action bermanfaat untuk hal-hal seperti:
  • memberikan interface untuk library pihak ketiga
  • melakukan lazy-loaded pada gambar
  • tooltips
  • menambahkan event handlers custom
Pada aplikasi ini, kita ingin membuat kotak oranye menjadi bisa di pan. Kotak mempunyai even handler untuk even panstartpanmove dan panend, tapi even-even ini bukan even asli bawaan DOM. Kita harus melakukan dispatch even ini sendiri. Pertama import fungsi pannable kemudian gunakan di dalam elemen <div class="box">.

<script>
  import { spring } from 'svelte/motion';
  import { pannable } from './pannable.js';

  const coords = spring({ x: 0, y: 0 }, {
    stiffness: 0.2,
    damping: 0.4
  });

  function handlePanStart() {
    coords.stiffness = coords.damping = 1;
  }

  function handlePanMove(event) {
    coords.update($coords => ({
      x: $coords.x + event.detail.dx,
      y: $coords.y + event.detail.dy
    }));
  }

  function handlePanEnd(event) {
    coords.stiffness = 0.2;
    coords.damping = 0.4;
    coords.set({ x: 0, y: 0 });
  }
</script>

<style>
  .box {
    --width: 100px;
    --height: 100px;
    position: absolute;
    width: var(--width);
    height: var(--height);
    left: calc(50% - var(--width) / 2);
    top: calc(50% - var(--height) / 2);
    border-radius: 4px;
    background-color: #ff3e00;
    cursor: move;
  }
</style>

<div class="box"
  use:pannable
  on:panstart={handlePanStart}
  on:panmove={handlePanMove}
  on:panend={handlePanEnd}
  style="transform:
    translate({$coords.x}px,{$coords.y}px)
    rotate({$coords.x * 0.2}deg)"
></div>

Kemudian pada file pannable.js. Seperti pada fungsi transisi, sebuah fungsi action akan menerima node dan beberapa parameter optional, dan me return sebuah object action. Object tersebut bisa memiliki fungsi destroy, yang akan dipanggil ketika elemen di unmount.

Kita ingin menjalankan even panstart ketika terjadi mouse down pada elemen, even panmove  (dengan properti dx dan dy menunjukkan sejauh mana mouse sudah bergerak) ketika kotaknya di drag, dan even panend ketika mouse up. Satu contoh implementasi yang mungkin adalah sebagai berikut, kita bisa coba menggerak-gerakkan kotaknya.

export function pannable(node) {
  let x;
  let y;

  function handleMousedown(event) {
    x = event.clientX;
    y = event.clientY;

    node.dispatchEvent(new CustomEvent('panstart', {
      detail: { x, y }
    }));

    window.addEventListener('mousemove', handleMousemove);
    window.addEventListener('mouseup', handleMouseup);
  }

  function handleMousemove(event) {
    const dx = event.clientX - x;
    const dy = event.clientY - y;
    x = event.clientX;
    y = event.clientY;

    node.dispatchEvent(new CustomEvent('panmove', {
      detail: { x, y, dx, dy }
    }));
  }

  function handleMouseup(event) {
    x = event.clientX;
    y = event.clientY;

    node.dispatchEvent(new CustomEvent('panend', {
      detail: { x, y }
    }));

    window.removeEventListener('mousemove', handleMousemove);
    window.removeEventListener('mouseup', handleMouseup);
  }

  node.addEventListener('mousedown', handleMousedown);

  return {
    destroy() {
      node.removeEventListener('mousedown', handleMousedown);
    }
  };
}

Adding Parameters


Seperti pada transisi dan animasi, sebuah action bisa menerima argumen, yang akan digunakan untuk memanggil fungsi action pada elemen yang akan menjadi tempatnya.

Sekarang, kita akan menggunakan action longpress yang akan memicu even yang memiliki nama sama kapanpun user menekan dan menahan tombol selama waktu yang ditentukan. Di bawah ini adalah contoh dari longpress.js. Bagian penting adalah duration dalam parameter sekon pada deklarasi fungsi; pass nilai duration ke dalam setTimeout; dan update(newDuration) pada nilai return.

export function longpress(node, duration) {
  let timer;

  const handleMousedown = () => {
    timer = setTimeout(() => {
      node.dispatchEvent(
        new CustomEvent('longpress')
      );
    }, duration);
  };

  const handleMouseup = () => {
    clearTimeout(timer)
  };

  node.addEventListener('mousedown', handleMousedown);
  node.addEventListener('mouseup', handleMouseup);

  return {
    update(newDuration) {
      duration = newDuration;
    },
    destroy() {
      node.removeEventListener('mousedown', handleMousedown);
      node.removeEventListener('mouseup', handleMouseup);
    }
  };
}

Dan di sini adalah contoh App.svelte, di mana kita bisa pass nilai durasi ke action menggunakan ini <button use:longpress={duration}:

<script>
  import { longpress } from './longpress.js';

  let pressed = false;
  let duration = 2000;
</script>

<label>
  <input type=range bind:value={duration} max={2000} step={100}>
  {duration}ms
</label>

<button use:longpress={duration}
  on:longpress="{() => pressed = true}"
  on:mouseenter="{() => pressed = false}"
>press and hold</button>

{#if pressed}
  <p>congratulations, you pressed and held for {duration}ms</p>

{/if}

Svelte Tutorial 11: Animations (English & Indonesia)

English


The Animate Directive


In the previous example, we used deferred transitions to create the illusion of motion as elements move from one todo list to the other.

To complete the illusion, we also need to apply motion to the elements that aren't transitioning. For this, we use the animate directive.

First, import the flip function — flip stands for  'First, Last, Invert, Play' — from svelte/animate:

import { flip } from 'svelte/animate';

Then add it to the <label> elements:

<label
  in:receive="{{key: todo.id}}"
  out:send="{{key: todo.id}}"
  animate:flip
>

You'll see that if you check something and it moved to other side,remaining item will slowly going up, instead of immediately jump. If you want to change speed of movement, we can add a duration parameter:

<label
  in:receive="{{key: todo.id}}"
  out:send="{{key: todo.id}}"
  animate:flip="{{duration: 200}}"
>

duration can also be a d => miliseconds function, where d is the number of pixels the element has to travel.

Note that all the transitions and animations are being applied with CSS, rather than JavaScript, meaning they won't block (or be blocked by) the main thread.

Indonesia


The Animate Directive


Pada contoh sebelumnya, kita menggunakan transisi defer untuk membuat ilusi gerakan saat satu elemen pindah dari daftar todo ke daftar satunya.

Untuk melengkapi, kita juga bisa memberikan efek gerakan untuk elemen yang tidak pindah. Untuk keperluan ini kita bisa menggunakan direktiv animate.

Pertama, kita import fungsi flip — flip singkatan dari  'First, Last, Invert, Play' — dari svelte/animate:

import { flip } from 'svelte/animate';

Kemudian tinggal menambahkan ke elemen <label> :

<label
  in:receive="{{key: todo.id}}"
  out:send="{{key: todo.id}}"
  animate:flip
>

Kamu akan bisa lihat, jika kita memilih sesuatu kemudian pindah ke daftar sebelahnya, item yang tersisa akan pelan-pelan naik ke atas, dan tidak lompat seketika. Jika ingin mengganti kecepatan gerakan, kita bisa menambahkan parameter duration:

<label

  in:receive="{{key: todo.id}}"
  out:send="{{key: todo.id}}"
  animate:flip="{{duration: 200}}"
>

duration juga bisa berupa fungsi d => milisecondsd adalah jumlah pixel yang dilewati elemen.

Perhatikan bahwa semua transisi dan animasi akan dijalankan pada CSS, dan bukan JavaScript, jadi mereka tidak akan menghalangi (atau dihalangi) oleh main thread.

2020-04-24

Svelte Tutorial 10: Transitions (English & Indonesia)

English


We can make more appealing user interfaces by gracefully transitioning elements into and out of the DOM. Svelte makes this very easy with the transition directive.

The Transitions Directive


This is the transitions that will fade your sentence in and out as it is appear or disappear.

<script>
  import { fade } from 'svelte/transition';
  let visible = true;
</script>

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <p transition:fade>
    Fades in and out
  </p>
{/if}

Adding Parameters


Transitions function can receive parameters, here is example of fly transitions:

<script>
  import { fly } from 'svelte/transition';
  let visible = true;
</script>

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <p transition:fly="{{ y: 200, duration: 2000 }}">
    Flies in and out
  </p>
{/if}

Note that the transition is reversible — if you toggle the checkbox while the transition is ongoing, it transitions from the current point, rather than the beginning or the end.

In and Out


Instead of the transition directive, an element can have an in or an out directive, or both together. Notice the transition directive is replaced with in and out directive.

<script>
  import { fade, fly } from 'svelte/transition';
  let visible = true;
</script>

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <p in:fly="{{ y: 200, duration: 2000 }}" out:fade>
    Flies in, fades out
  </p>
{/if}

Custom CSS Transitions


The svelte/transition module has a handful of built-in transitions, but it's very easy to create your own. By way of example, this is the source of the fade transition:

function fade(node, {
delay = 0,
duration = 400
}) {
const o = +getComputedStyle(node).opacity;

return {
delay,
duration,
css: t => `opacity: ${t * o}`
};
}

The function takes two arguments — the node to which the transition is applied, and any parameters that were passed in — and returns a transition object which can have the following properties:
  • delay — milliseconds before the transition begins
  • duration — length of the transition in milliseconds
  • easing — a p => t easing function (see the chapter on )
  • css — a (t, u) => css function, where u === 1 - t
  • tick — a (t, u) => {...} function that has some effect on the node. The t value is 0 at the beginning of an intro or the end of an outro, and 1 at the end of an intro or beginning of an outro.
Most of the time you should return the css property and not the tick property, as CSS animations run off the main thread to prevent jank where possible. Svelte 'simulates' the transition and constructs a CSS animation, then lets it run.

For example, the fade transition generates a CSS animation somewhat like this:

0% { opacity: 0 }
10% { opacity: 0.1 }
20% { opacity: 0.2 }
/* ... */
100% { opacity: 1 }

We can get a lot more creative though. Let's make something truly gratuitous:

<script>
  import { fade } from 'svelte/transition';
  import { elasticOut } from 'svelte/easing';
  let visible = true;

  function spin(node, { duration }) {
    return {
      duration,
      css: t => {
const eased = elasticOut(t);
return `
          transform: scale(${eased}) rotate(${eased * 1080}deg);
          color: hsl(
            ${~~(t * 360)},
            ${Math.min(100, 1000 - 1000 * t)}%,
            ${Math.min(50, 500 - 500 * t)}%
          );`
      }
    };
  }
</script>

We will use spin function like this :

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <div class="centered" in:spin="{{duration: 8000}}" out:fade>
    <span>transitions!</span>
  </div>
{/if}

As the words is going visible (intro) it will spin and change color as we set up in spin function

Custom JS Transitions


While you should generally use CSS for transitions as much as possible, there are some effects that can't be achieved without JavaScript, such as a typewriter effect:

function typewriter(node, { speed = 50 }) {
  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;

  return {
    duration,
    tick: t => {
      const i = ~~(text.length * t);
      node.textContent = text.slice(0, i);
    }
  };
}

Here is how we're gonna call the function

<p in:typewriter>
  The quick brown fox jumps over the lazy dog
</p>

Transitions Events


It can be useful to know when transitions are beginning and ending. Svelte dispatches events that you can listen to like any other DOM event:

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

Local Transitions


Ordinarily, transitions will play on elements when any container block is added or destroyed. In the example here, toggling the visibility of the entire list also applies transitions to individual list elements.

Instead, we'd like transitions to play only when individual items are added and removed — in other words, when the user drags the slider.

We can achieve this with a local transition, which only plays when the immediate parent block is added or removed:

<script>
  import { slide } from 'svelte/transition';
  let showItems = true;
  let i = 5;
  let items = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
</script>

<style>
  div {
    padding: 0.5em 0;
    border-top: 1px solid #eee;
  }
</style>

<label>
  <input type="checkbox" bind:checked={showItems}> show list
</label>

<label>
  <input type="range" bind:value={i} max=10>
</label>

{#if showItems}
  {#each items.slice(0, i) as item}
    <div transition:slide|local>{item}</div>
  {/each}
{/if}

Defered Transitions


A particularly powerful feature of Svelte's transition engine is the ability to defer transitions, so that they can be coordinated between multiple elements.

Take this pair of todo lists, in which toggling a todo sends it to the opposite list. In the real world, objects don't behave like that — instead of disappearing and reappearing in another place, they move through a series of intermediate positions. Using motion can go a long way towards helping users understand what's happening in your app.

We can achieve this effect using the crossfade function, which creates a pair of transitions called send and receive. When an element is 'sent', it looks for a corresponding element being 'received', and generates a transition that transforms the element to its counterpart's position and fades it out. When an element is 'received', the reverse happens. If there is no counterpart, the fallback transition is used. This is how we're gonna use send and receive in <label>:

<label
  in:receive="{{key: todo.id}}"
  out:send="{{key: todo.id}}"
>

Full example is here, this is example of todo list so if you check something on the left list (todo) it will move to right (done). Defered transitions used to make moving object smoother, instead of jumping immediately there will be moving and fade effect, you can try to remove in and out attribute to see what it would be like without crossfade.

<script>
  import { quintOut } from 'svelte/easing';
  import { crossfade } from 'svelte/transition';

  const [send, receive] = crossfade({
    duration: d => Math.sqrt(d * 200),

    fallback(node, params) {
      const style = getComputedStyle(node);
      const transform = style.transform === 'none' ? '' : style.transform;

      return {
        duration: 600,
        easing: quintOut,
        css: t => `
          transform: ${transform} scale(${t});
          opacity: ${t}
        `
      };
    }
  });

  let uid = 1;

  let todos = [
    { id: uid++, done: false, description: 'write some docs' },
    { id: uid++, done: false, description: 'writing blog post' },
    { id: uid++, done: true,  description: 'buy some milk' },
    { id: uid++, done: false, description: 'mow the lawn' },
    { id: uid++, done: false, description: 'feed the turtle' },
    { id: uid++, done: false, description: 'fix some bugs' },
  ];

  function add(input) {
    const todo = {
      id: uid++,
      done: false,
      description: input.value
    };

    todos = [todo, ...todos];
    input.value = '';
  }

  function remove(todo) {
    todos = todos.filter(t => t !== todo);
  }

  function mark(todo, done) {
    todo.done = done;
    remove(todo);
    todos = todos.concat(todo);
  }
</script>

<div class='board'>
  <input
    placeholder="what needs to be done?"
    on:keydown={e => e.which === 13 && add(e.target)}
  >

  <div class='left'>
    <h2>todo</h2>
    {#each todos.filter(t => !t.done) as todo (todo.id)}
      <label in:receive="{{key:todo.id}}" 
             out:send="{{key:todo.id}}">
         <input type=checkbox on:change={() => mark(todo, true)}>
           {todo.description}
         <button on:click="{() => remove(todo)}">remove</button>
       </label>
    {/each}
   </div>

  <div class='right'>
    <h2>done</h2>
    {#each todos.filter(t => t.done) as todo (todo.id)}
      <label class="done" in:receive="{{key:todo.id}}" 
                          out:send="{{key:todo.id}}">
        <input type=checkbox checked on:change={() => mark(todo, false)}> {todo.description}
        <button on:click="{() => remove(todo)}">remove</button>
      </label>
    {/each}
  </div>
</div>

<style>
  .board {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-gap: 1em;
    max-width: 36em;
    margin: 0 auto;
  }

  .board > input {
    font-size: 1.4em;
    grid-column: 1/3;
  }

  h2 {
    font-size: 2em;
    font-weight: 200;
    user-select: none;
    margin: 0 0 0.5em 0;
  }

  label {
    position: relative;
    line-height: 1.2;
    padding: 0.5em 2.5em 0.5em 2em;
    margin: 0 0 0.5em 0;
    border-radius: 2px;
    user-select: none;
    border: 1px solid hsl(240, 8%, 70%);
    background-color:hsl(240, 8%, 93%);
    color: #333;
  }

  input[type="checkbox"] {
    position: absolute;
    left: 0.5em;
    top: 0.6em;
    margin: 0;
  }

  .done {
    border: 1px solid hsl(240, 8%, 90%);
    background-color:hsl(240, 8%, 98%);
  }

  button {
    position: absolute;
    top: 0;
    right: 0.2em;
    width: 2em;
    height: 100%;
    background: no-repeat 50% 50% url("data:image/svg+xml,%3Csvg      xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23676778' d='M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M17,7H14.5L13.5,6H10.5L9.5,7H7V9H17V7M9,18H15A1,1 0 0,0 16,17V10H8V17A1,1 0 0,0 9,18Z'%3E%3C/path%3E%3C/svg%3E");
    background-size: 1.4em 1.4em;
    border: none;
    opacity: 0;
    transition: opacity 0.2s;
    text-indent: -9999px;
    cursor: pointer;
  }

  label:hover button { opacity: 1; }
</style>





Indonesia


Kita bisa membuat user interface yang menarik dengan cara melakukan transisi elemen masuk dan keluar DOM secara elegan. Svelte memberikan kemudahan untuk itu dengan directiv transition.

The Transitions Directive


Transisi ini akan memberikan efek fade masuk dan kluar saat tulisan muncul dan menghilang.

<script>
  import { fade } from 'svelte/transition';
  let visible = true;
</script>

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <p transition:fade>
    Fades in and out
  </p>
{/if}

Adding Parameters


Fungsi transisi dapat menerima parameter, berikut contoh transisi fly:

<script>
  import { fly } from 'svelte/transition';
  let visible = true;
</script>

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <p transition:fly="{{ y: 200, duration: 2000 }}">
    Flies in and out
  </p>
{/if}

Perhatikan bawah transisi bersifat reversible, artinya jika kita toggle checkbox saat transisi sedang berjalan, maka transisi akan dilanjutkan dari titik terakhir, bukan dari awal atau akhir.

In and Out


Selain direktiv transition, suatu elemen juga bisa mempunya direktiv in dan atau out. Perhatikan direktiv transition digantikan dengan direktiv in dan out.

<script>
  import { fade, fly } from 'svelte/transition';
  let visible = true;
</script>

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <p in:fly="{{ y: 200, duration: 2000 }}" out:fade>
    Flies in, fades out
  </p>
{/if}

Custom CSS Transitions


Modul svelte/transition memiliki sejumlah transisi built-in yang berguna, tapi kita bisa membuat transisi sendiri. Sebagai contoh berikut adalah isi program dari transisi fade:

function fade(node, {
delay = 0,
duration = 400
}) {
const o = +getComputedStyle(node).opacity;

return {
delay,
duration,
css: t => `opacity: ${t * o}`
};
}

Fungsi ini menerima 2 argumen —  node tempat transisi dijalankan, dan semua parameter yang kita masukkan  — dan me return object transisi yang bisa memiliki properti sebagai berikut:
  • delay — milisekon sebelum transisi dimulai
  • duration — lama transisi dalam milisekon
  • easing — sebuah fungsi easing p => t (lihat tutorial sebelumnya )
  • css — sebuah fungsi (t, u) => css , dimana u === 1 - t
  • tick — sebuah fungsi (t, u) => {...} yang mempunyai efek pada node. Nilai t  adalah 0 pada permulaan intro atau pada akhir outro, dan 1 pada kebalikannya.
Sebaiknya lebih diutamakan untuk mereturn properti css dan bukan properti tick, karena animasi CSS dijalankan di luar main thread untuk menghindari gangguan semaksimal mungkin. Svelte akan mensimulasikan transisi dan akan membentuk animasi CSS, kemudian baru menjalankannya.

Sebagai contoh transisi fade membentuk animasi CSS kurang lebih seperti ini:

0% { opacity: 0 }
10% { opacity: 0.1 }
20% { opacity: 0.2 }
/* ... */
100% { opacity: 1 }

Kita bisa lebih kreatif lagi, untuk membuat sesuatu yang sebetulnya tidak terlalu penting:

<script>
  import { fade } from 'svelte/transition';
  import { elasticOut } from 'svelte/easing';
  let visible = true;

  function spin(node, { duration }) {
    return {
      duration,
      css: t => {
const eased = elasticOut(t);
return `
          transform: scale(${eased}) rotate(${eased * 1080}deg);
          color: hsl(
            ${~~(t * 360)},
            ${Math.min(100, 1000 - 1000 * t)}%,
            ${Math.min(50, 500 - 500 * t)}%
          );`
      }
    };
  }
</script>

Kita akan menggunakan fungsi spin seperti ini :

<label>
  <input type="checkbox" bind:checked={visible}> visible
</label>

{#if visible}
  <div class="centered" in:spin="{{duration: 8000}}" out:fade>
    <span>transitions!</span>
  </div>
{/if}

Selagi tampilan kata mulai muncul (intro) kalimat akan berputar dan berubah warna seperti yang kita buat di dalam fungsi spin.

Custom JS Transitions


Sementara kita sebaiknya mengutamakan menggunakan transisi CSS, kadang ada efek tertentu yang tidak bisa dihasilkan kecuali menggunakan JavaScript, seperti efek mengetik:

function typewriter(node, { speed = 50 }) {
  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;

  return {
    duration,
    tick: t => {
      const i = ~~(text.length * t);
      node.textContent = text.slice(0, i);
    }
  };
}

Kemudian tinggal memanggil fungsi typewriter seperti ini:

<p in:typewriter>
  The quick brown fox jumps over the lazy dog
</p>

Transitions Events


Terkadang kita perlu mengakses kapan saat transisi sedang dimulai atau berakhir. Svelte akan men dispatch even yang bisa kita akses seperti even DOM pada umumnya:

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

Local Transitions


Biasanya, transisi akan dimainkan pada element ketika suatu blok kontainer ditambahkan atau dihancurkan. Pada contoh berikut, mengubah show list akan menjalankan juga transisi ke masing-masing elemen.

Padahal kita ingin transisi hanya berjalan saat kita menambah atau menghilangkan item, atau dengan kata lain saat kita menggeser slider.

Kita bisa mencapai keinginan kita dengan memanfaatkan local transition, yang hanya akan berjalan ketika blok parent ditambahkan atau dihilangkan:

<script>
  import { slide } from 'svelte/transition';
  let showItems = true;
  let i = 5;
  let items = ['one', 'two', 'three', 'four', 'five', 'six', 'seven', 'eight', 'nine', 'ten'];
</script>

<style>
  div {
    padding: 0.5em 0;
    border-top: 1px solid #eee;
  }
</style>

<label>
  <input type="checkbox" bind:checked={showItems}> show list
</label>

<label>
  <input type="range" bind:value={i} max=10>
</label>

{#if showItems}
  {#each items.slice(0, i) as item}
    <div transition:slide|local>{item}</div>
  {/each}
{/if}

Defered Transitions


Suatu fitur yang sangat hebat dari engine transisi Svelte adalah kemampuannya untuk defer (=menunda) transisi, jadi transisi bisa dikoordinasikan antar elemen.

Sebagai contoh di sini kita mempunyai daftar todo, yang jika kita klik akan memindah seketika item ke daftar done. Pada dunia nyata, benda tidak hilang dan muncul tiba-tiba seperti itu melainkan akan bergerak dari tempat asal ke tempat tujuan. Menggunakan gerakan seperti ini bisa membantu user untuk memahami maksud dari aplikasi yang kita buat.

Kita bisa mendapatkan efek ini dengan menggunakan fungsi crossfade,yang akan menghasilkan sepasang efek transisi yang disebut send dan receive. Ketika satu elemen dikirim, maka dia akan mencari elemen lain yang berhubungan yang sedang diterima, dan menghasilkan transisi yang akan memindahkan elemen ke posisi sebelah dan kemudian menghilang dari tempat asalnya. Ketika elemen diterima maka akan terjadi kebalikannya. Sedangkan jika tidak menemukan pasangannya, makan transisi fallback yang akan digunakan, berikut adalah cara kita akan menggunakan  send dan receive pada <label>:

<label
  in:receive="{{key: todo.id}}"
  out:send="{{key: todo.id}}"
>

Contoh lengkap bisa dilihat di sini. ini adalah contoh aplikasi yang menampilkan daftar todo, yang jika kita klik maka item akan pindah ke sebelah kanan ke daftar done. Transisi defered digunakan untuk membuat transisi menjadi lebih mulus, dan tidak pindah tempat begitu saja. Kamu bisa coba menghilangkan atribut in dan out untuk melihat hasilnya akan seperti apa tanpa efek crossfade.

<script>
  import { quintOut } from 'svelte/easing';
  import { crossfade } from 'svelte/transition';

  const [send, receive] = crossfade({
    duration: d => Math.sqrt(d * 200),

    fallback(node, params) {
      const style = getComputedStyle(node);
      const transform = style.transform === 'none' ? '' : style.transform;

      return {
        duration: 600,
        easing: quintOut,
        css: t => `
          transform: ${transform} scale(${t});
          opacity: ${t}
        `
      };
    }
  });

  let uid = 1;

  let todos = [
    { id: uid++, done: false, description: 'write some docs' },
    { id: uid++, done: false, description: 'writing blog post' },
    { id: uid++, done: true,  description: 'buy some milk' },
    { id: uid++, done: false, description: 'mow the lawn' },
    { id: uid++, done: false, description: 'feed the turtle' },
    { id: uid++, done: false, description: 'fix some bugs' },
  ];

  function add(input) {
    const todo = {
      id: uid++,
      done: false,
      description: input.value
    };

    todos = [todo, ...todos];
    input.value = '';
  }

  function remove(todo) {
    todos = todos.filter(t => t !== todo);
  }

  function mark(todo, done) {
    todo.done = done;
    remove(todo);
    todos = todos.concat(todo);
  }
</script>

<div class='board'>
  <input
    placeholder="what needs to be done?"
    on:keydown={e => e.which === 13 && add(e.target)}
  >

  <div class='left'>
    <h2>todo</h2>
    {#each todos.filter(t => !t.done) as todo (todo.id)}
      <label in:receive="{{key:todo.id}}" 
             out:send="{{key:todo.id}}">
         <input type=checkbox on:change={() => mark(todo, true)}>
           {todo.description}
         <button on:click="{() => remove(todo)}">remove</button>
       </label>
    {/each}
   </div>

  <div class='right'>
    <h2>done</h2>
    {#each todos.filter(t => t.done) as todo (todo.id)}
      <label class="done" in:receive="{{key:todo.id}}" 
                          out:send="{{key:todo.id}}">
        <input type=checkbox checked on:change={() => mark(todo, false)}> {todo.description}
        <button on:click="{() => remove(todo)}">remove</button>
      </label>
    {/each}
  </div>
</div>

<style>
  .board {
    display: grid;
    grid-template-columns: 1fr 1fr;
    grid-gap: 1em;
    max-width: 36em;
    margin: 0 auto;
  }

  .board > input {
    font-size: 1.4em;
    grid-column: 1/3;
  }

  h2 {
    font-size: 2em;
    font-weight: 200;
    user-select: none;
    margin: 0 0 0.5em 0;
  }

  label {
    position: relative;
    line-height: 1.2;
    padding: 0.5em 2.5em 0.5em 2em;
    margin: 0 0 0.5em 0;
    border-radius: 2px;
    user-select: none;
    border: 1px solid hsl(240, 8%, 70%);
    background-color:hsl(240, 8%, 93%);
    color: #333;
  }

  input[type="checkbox"] {
    position: absolute;
    left: 0.5em;
    top: 0.6em;
    margin: 0;
  }

  .done {
    border: 1px solid hsl(240, 8%, 90%);
    background-color:hsl(240, 8%, 98%);
  }

  button {
    position: absolute;
    top: 0;
    right: 0.2em;
    width: 2em;
    height: 100%;
    background: no-repeat 50% 50% url("data:image/svg+xml,%3Csvg      xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='%23676778' d='M12,2C17.53,2 22,6.47 22,12C22,17.53 17.53,22 12,22C6.47,22 2,17.53 2,12C2,6.47 6.47,2 12,2M17,7H14.5L13.5,6H10.5L9.5,7H7V9H17V7M9,18H15A1,1 0 0,0 16,17V10H8V17A1,1 0 0,0 9,18Z'%3E%3C/path%3E%3C/svg%3E");
    background-size: 1.4em 1.4em;
    border: none;
    opacity: 0;
    transition: opacity 0.2s;
    text-indent: -9999px;
    cursor: pointer;
  }

  label:hover button { opacity: 1; }
</style>