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: , movestart
and moving
, moveend
as 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 longpress
this 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.js
the 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 duration
value 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.js
add a update
method 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}}