svelte action (Action)

1. use command

Action (Action), its essence is the life cycle function on the element. They can be used for example in the following ways:

  • Docking with third-party libraries
  • Lazy loading images
  • tooltips
  • Add a custom event handler

Let's simulate a third-party library first. It is very simple to adapt the third-party library to Svelte's Action. In fact, Action is just an ordinary function that receives a parameter, which is the DOM node object of the current element.

The function assumption of our self-made "third-party" library is a JS library that allows any element to support mobile. Its framework is roughly like this:

movable.js

export function movable(node) {
  // ... 第三方库的代码

  return {
    destroy() {
      // ... destroy 函数会在元素被清除时调用,
      // 我们可以在此做一些清理工作
    }
  }
}

This function returns an object containing  destroy methods that destroy are automatically called when the element is removed.

To enable movement of any element, we need to listen to some events:

movable.js

export default function movable(node) {
  let moving = false
  let x, y, left, top
	
  function handleMove(e) {
    if (moving) {
      node.style.left = (e.clientX - x + left) + 'px'
      node.style.top = (e.clientY - y + top) + 'px'
    }
  }
	
  function startMove(e) {
    moving = true
		
    x = e.clientX
    y = e.clientY
    left = parseInt(node.style.left) || 0
    top = parseInt(node.style.top) || 0
  }
	
  function endMove() { moving = false }
	
  window.addEventListener('mousemove', handleMove)
  window.addEventListener('mouseup', endMove)
  node.addEventListener('mousedown', startMove)
	
  return {
    destroy() {
      window.removeEventListener('mousemove', handleMove)
      window.removeEventListener('mouseup', endMove)
      node.removeEventListener('mousedown', startMove)
    }
  }
}

After completing the "third-party" component, we now implement the main program and   apply it to the specified element through use the instruction  :movable

App.svelte

<script>
  import movable from './movable'
</script>

<style>
  div {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: blue;
    text-align: center;
    line-height: 100px;
    color: #fff;
    user-select: none;
    cursor: move;
  }
</style>

<!-- 通过 use 指令将 movable 应用到元素 div -->
<div use:movable>Box</div>

At this point, try to drag  div, and you can see that  div dragging is already supported.

Next, the question we have to think about is: can the div monitor when it starts to move? When did you end moving? The consumer may have things to do at these moments.

This requires movable support to send these events for external use. We plan to design 3 events, namely: , movestartand moving ,  moveendas the name suggests, they represent the start of movement, movement and end of movement respectively.

First listen to these events on the UI:

<script>
  import movable from './movable'
</script>

<style>
  div {
    position: absolute;
    width: 100px;
    height: 100px;
    background-color: blue;
    text-align: center;
    line-height: 100px;
    color: #fff;
    user-select: none;
    cursor: move;
  }
</style>

<div
  use:movable
  on:movestart={e => console.log(`start => x: ${e.detail.x}, y: ${e.detail.y}`)}
  on:moving={e => console.log(`moving => x: ${e.detail.x}, y: ${e.detail.y}`)}
  on:moveend={() => console.log('move end...')}
>Box</div>

Then turn to movable to implement sending of these events:

export default function movable(node) {
  let moving = false
  let x, y, left, top
	
  // Note: 发送自定义事件
  function fire(type, option) {
    node.dispatchEvent(new CustomEvent(type, option))
  }
	
  function handleMove(e) {
    if (moving) {
      node.style.left = (e.clientX - x + left) + 'px'
      node.style.top = (e.clientY - y + top) + 'px'
			
      // Note: 发送 moving
      fire('moving', {
        detail: { x: e.clientX - x + left, y: e.clientY - y + top }
      })
    }
  }

  function startMove(e) {
    moving = true
		
    x = e.clientX
    y = e.clientY
		
    left = parseInt(node.style.left) || 0
    top = parseInt(node.style.top) || 0
		
    // Note: 发送 movestart
    fire('movestart', { detail: {x:left, y:top}})
  }
	
  function endMove() {
    moving = false
		
    // Note: 发送 moveend
    fire('moveend')
  }
	
  window.addEventListener('mousemove', handleMove)
  window.addEventListener('mouseup', endMove)
  node.addEventListener('mousedown', startMove)
	
  return {
    destroy() {
      window.removeEventListener('mousemove', handleMove)
      window.removeEventListener('mouseup', endMove)
      node.removeEventListener('mousedown', startMove)
    }
  }
}
This implementation is only for demonstration, touch events need to be considered in real scenarios.

2. Additional parameters

Just like  transition transition effects and  animate animations, 动作(Action) also supports parameters, and the action function will be called with the element it is on.

Now we have designed a boring game, called  长按按钮 the game, there is a button on the interface, long press the specified time to display a foolish prompt such as "Congratulations, you have pressed enough for x seconds", let's write the UI first:

App.svelte

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

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

<label>
  <input type=range bind:value={duration} max={8} step={1}>
  {duration}s
</label>

<button
  use:longpress
  on:longpress={() => pressed = true}
  on:mouseenter={() => pressed = false}
>按住我别放</button>

{#if pressed}
  <p>恭喜你,长按了 {duration} 秒</p>
{/if}

Here we use longpressthis action which fires an event with the same name whenever the user presses the button for a given amount of time (provided via the rang component).

Now the question is, how do we  pressed tell  longpress this  action ?

The answer, of course, is through action parameters.

Without further ado, we start writing longpress.jsthe file:

longpress.js

export function longpress(node, duration) {
  let timer;
	
  const handleMousedown = () => {
    timer = setTimeout(() => {
      node.dispatchEvent(
        new CustomEvent('longpress')
      );
    }, duration * 1000);
  };
	
  const handleMouseup = () => clearTimeout(timer);

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

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

longpress Use the timer to control the time, and if you press enough  duration seconds, a congratulations message will be displayed.

Back App.svelte, we can pass a specific durationvalue to the action:

<button
  use:longpress={duration}
  ...
>按住我别放</button>

If you adjust the duration slider, for example, from 5 seconds to 2 seconds, you will find that it still takes 5 seconds.

To fix this, we can longpress.jsadd a updatemethod to call this method immediately whenever the parameter changes:

longpress.js

return {
  destroy() {
    node.removeEventListener('mousedown', handleMousedown);
    node.removeEventListener('mouseup', handleMouseup);
  },
  update(newDuration) {
    duration = newDuration;
  }
};
If you need to pass multiple parameters to an action at the same time, you need to combine them into an object, such as:  use:longpress={ {duration, spiciness}}

Guess you like

Origin blog.csdn.net/ramblerviper/article/details/124941731