2020-04-25

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}

No comments:

Post a Comment

THINK: is it True? is it Helpful? is it Inspiring? is it Necessary? is it Kind?