svelte动作(Action)

1、use 指令

动作(Action),其本质是元素上的生命周期函数。它们可用于譬如以下几个方面:

  • 与第三方库对接
  • 延迟加载图片
  • 工具提示(tooltips)
  • 添加自定义事件处理程序

我们模拟先做一个第三方库,要使第三方库适配 Svelte 的 Action 十分简单,实际上,Action 只是一个普通的函数,它接收一个参数,就是当前元素的 DOM 节点对象。

我们自制的“第三方”库的功能假设是一个让任意元素支持移动的 JS 库,它的框架大概是这样:

movable.js

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

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

这个函数返回一个包含了 destroy 方法的对象,destroy 函数在元素被移除时自动被调用。

让任意元素支持移动,我们需要监听一些事件:

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

完成了“第三方”组件后,我们现在来实现主程序,并通过 use 指令,将 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>

此时,尝试拖动 div,可以看到 div 已经支持拖动。

下一步,我们要思考的问题是:div 能否监听得到它是什么时候开始移动?什么时候结束移动的?在这些时刻使用端可能有自己的事情要做。

这就需要 movable 支持发送这些事件供外部使用,我们计划设计3个事件,分别是:movestartmoving 和 moveend,顾名思义,它们分别代表移动开始、移动中和移动结束。

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

然后转到 movable 去实现这些事件的发送:

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)
    }
  }
}
这个实现方案仅用于演示,真实场景还需考虑触摸事件。

2、附加参数

正如 transition 过渡效果和 animate 动画一样,动作(Action)也支持附带参数,动作函数会与它所在的元素一起被调用。

我们现在设计了一个无聊透顶的游戏,叫 长按按钮 游戏,界面上有一个按钮,长按指定时间显示“恭喜你按够了 x 秒”之类的傻瓜提示,先来写 UI:

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}

在这里我们使用longpress这个动作,每当用户按下按钮并持续一段给定的时间(通过 rang 组件提供)的话,该动作就会触发一个具有相同名称的时间。

现在有一个问题,我们如何将 pressed 告知 longpress 这个 action 呢?

答案自然就是通过动作参数。

事不宜迟,我们开始编写longpress.js文件:

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 借助计时器来控制时间,如果按够了 duration 秒,就展示恭喜信息。

回到App.svelte,我们可以传递一个具体的duration值给动作了:

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

如果你调整了持续时间的滑块,例如从5秒调整到2秒,你会发现还是需要5秒。

要修复这个问题,我们可以在longpress.js中添加一个update方法,每当参数有变,立即调用此方法:

longpress.js

return {
  destroy() {
    node.removeEventListener('mousedown', handleMousedown);
    node.removeEventListener('mouseup', handleMouseup);
  },
  update(newDuration) {
    duration = newDuration;
  }
};
如果你需要将多个参数同时传递给一个动作,则需要将它们组合成一个对象,比如:  use:longpress={ {duration, spiciness}}

猜你喜欢

转载自blog.csdn.net/ramblerviper/article/details/124941731
今日推荐