SnapAxis
是一个用于管理吸附轴(如水平轴或垂直轴)的类,支持吸附点的添加、删除、更新以及吸附逻辑的实现。适用于拖拽、对齐、辅助线等场景。
使用 npm 或 yarn 安装:
npm install snap-axis
或
yarn add snap-axis
import { SnapAxis } from "snap-axis";
const snapAxis = new SnapAxis({
snapValues: [
{ value: 10, id: "10" },
{ value: 20, id: "20" },
{ value: 30, id: "30" },
],
});
let startPageX = 0;
let result;
const updater = snapAxis.getSnapGroupUpdater([3, 30, 50], startPageX, { distance: 5 });
startPageX++;
result = updater(startPageX);
console.log(result); // { values: [4,31,51], snapped: false }
startPageX++;
result = updater(startPageX);
console.log(result); // { values: [10,37,57], snapped: true }
startPageX++;
result = updater(startPageX);
console.log(result); // { values: [10,37,57], snapped: false }
// ...
const snapAxis = new SnapAxis({
snapValues: [
{ value: 10, id: "10" },
{ value: 20, id: "20" },
{ value: 30, id: "30" },
],
});
let pageX = 1;
const updater = snapAxis.getSnapUpdater(8, pageX);
pageX++;
const result = updater(pageX);
console.log(result); // { value: 10, snapped: true }
pageX++;
const result = updater(pageX);
console.log(result); // { value: 10, snapped: false }
const snapAxis = new SnapAxis({
snapValues: [
{ value: 10, id: "10" },
{ value: 20, id: "20" },
{ value: 30, id: "30" },
],
});
const updater = snapAxis.getSnapGroupUpdater([10, 20], 0);
const result = updater(1);
console.log(result); // { values: [10, 20], snapped: false }
snapAxis.deleteSnapValue({ value: 10, id: "A" });
snapAxis.deleteSnapValue({ value: 10, id: "B" });
const snappedValues = snapAxis.getSnappedValues(10);
console.log(snappedValues); // [{ value: 10, id: "A" },{ value: 10, id: "B" }]
判断某个值或一组值是否处于吸附状态。
snapAxis.checkSnapped(10);
snapAxis.checkGroupSnapped([10, 20]);
<!DOCTYPE html>
<html>
<head>
<title>SnapAxis Example</title>
<meta charset="UTF-8" />
</head>
<body>
<style>
#app {
font-family: sans-serif;
text-align: center;
font-size: 12px;
}
.container {
height: 100px;
position: relative;
margin: 0 auto;
user-select: none;
width: 700px;
}
.tickWarp {
position: absolute;
bottom: 0;
left: 0;
}
.tick {
height: 5px;
position: "relative";
border-bottom: 1px solid #757575;
}
.tickItem {
position: absolute;
bottom: 0;
left: 0;
width: 1px;
height: 5px;
background-color: #757575;
}
.dragBox {
position: absolute;
left: 0;
bottom: 0;
height: 40px;
background-color: #0307f2;
opacity: 0.3;
}
.snappedLabel {
position: absolute;
top: 0;
left: 0;
pointer-events: none;
font-size: 12px;
color: #a3a3a3;
text-align: left;
}
.setting {
position: absolute;
top: 0;
right: 0;
font-size: 12px;
color: #a3a3a3;
}
.setting label {
display: flex;
align-items: center;
}
.setting input {
margin: 0;
padding: 0;
}
</style>
<div id="app">
<div class="container" id="demo02">
<div class="snappedLabel" id="snappedLabel" style="display: none"></div>
<div class="setting">
<label>
禁用吸附:
<input type="checkbox" id="disableSnap" />
</label>
</div>
<div class="tickWarp">
<div class="tick" id="tick"></div>
</div>
<div class="dragBox" id="dragBox"></div>
</div>
</div>
<script src="./index.mjs" type="module"></script>
</body>
</html>
// index.mjs
import { SnapAxis } from "snap-axis";
const TickWidth = 700;
const BoxWidth = 60;
// 随机生成刻度
function randomRange(min, max) {
return Math.floor(Math.random() * (max - min + 1)) + min;
}
const tickValues = [0, TickWidth, TickWidth / 2];
for (let i = 0; i < 10; i++) {
tickValues.push(randomRange(0, TickWidth));
}
tickValues.sort((a, b) => a - b);
// 初始化 SnapAxis
const snapAxis = new SnapAxis({
snapValues: tickValues.map((value, idx) => ({
id: `snap_${idx}`,
value,
})),
});
// 渲染刻度
const tickEl = document.getElementById("tick");
tickEl.style.width = TickWidth + "px";
tickValues.forEach((value) => {
const div = document.createElement("div");
div.className = "tickItem";
div.style.left = value + "px";
tickEl.appendChild(div);
});
// 拖拽逻辑
let value = 0;
let disableSnap = false;
const dragBox = document.getElementById("dragBox");
dragBox.style.width = BoxWidth + "px";
dragBox.style.left = value + "px";
const snappedLabel = document.getElementById("snappedLabel");
const disableSnapInput = document.getElementById("disableSnap");
function getSnapGroup(val) {
return [val, val + BoxWidth / 2, val + BoxWidth];
}
function updateUI() {
// 检查是否吸附
let isSnapped = false;
let snappedValues = [];
const snapGroup = getSnapGroup(value);
for (let i = 0; i < snapGroup.length; i++) {
const v = snapGroup[i];
if (snapAxis.checkSnapped(v)) {
isSnapped = true;
snappedValues = snapAxis.getSnappedValues(v);
break;
}
}
dragBox.className = "dragBox" + (isSnapped ? " snapped" : "");
dragBox.style.left = value + "px";
if (isSnapped && snappedValues.length) {
dragBox.style.backgroundColor = "red";
snappedLabel.style.display = "";
snappedLabel.innerHTML =
`<div>Snapped Values:</div>` +
snappedValues
.map((item) => `id=${item.id}, value=${item.value}`)
.join("<br>");
} else {
dragBox.style.backgroundColor = "";
snappedLabel.style.display = "none";
snappedLabel.innerHTML = "";
}
}
dragBox.addEventListener("mousedown", (e) => {
const snapGroup = getSnapGroup(value);
// 获取吸附更新器
const updater = snapAxis.getSnapGroupUpdater(snapGroup, e.clientX, {
distance: 5,
disableSnap,
});
function onMouseMove(e) {
// 计算拖动偏移量
const result = updater(e.clientX);
const values = result.values;
value = Math.max(0, Math.min(TickWidth, values[0]));
updateUI();
}
function onMouseUp() {
document.removeEventListener("mousemove", onMouseMove);
document.removeEventListener("mouseup", onMouseUp);
document.body.style.cursor = "";
}
document.addEventListener("mousemove", onMouseMove);
document.addEventListener("mouseup", onMouseUp);
document.body.style.cursor = "move";
});
disableSnapInput.addEventListener("change", (e) => {
disableSnap = e.target.checked;
});
updateUI();