画布增加的新功能

This commit is contained in:
李志鹏
2026-01-02 11:24:11 +08:00
parent 1ae365b1f3
commit f8e4ab8cdb
59 changed files with 4401 additions and 1213 deletions

View File

@@ -0,0 +1,190 @@
<template>
<div class="offset-tool">
<div
class="dish"
@mousedown="mousedown"
@touchstart="mousedown"
ref="dishRef"
>
<span
:style="{ top: data.top + '%', left: data.left + '%' }"
></span>
</div>
<input
class="top"
type="range"
:min="0"
:max="100"
:step="0.1"
v-model="data.top"
@input="onInput"
@change="onChange"
/>
<input
class="left"
type="range"
:min="0"
:max="100"
:step="0.1"
v-model="data.left"
@input="onInput"
@change="onChange"
/>
<span class="tip"
>x:{{ tofix(data.left) }}% y:{{ tofix(data.top) }}%</span
>
</div>
</template>
<script setup lang="ts">
import { ref, defineProps, defineEmits, watch } from "vue";
const props = defineProps({
top: {
type: Number,
default: 50,
},
left: {
type: Number,
default: 50,
},
});
const tofix = (v: number | string) => Number(Number(v).toFixed(1));
const emit = defineEmits(["change", "input"]);
const data = reactive({
top: tofix(props.top),
left: tofix(props.left),
});
watch(
() => props.top,
(v) => (data.top = tofix(v))
);
watch(
() => props.left,
(v) => (data.left = tofix(v))
);
const dishRef = ref<HTMLDivElement>();
const mousedown = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const mousemove = (e: MouseEvent | TouchEvent) => {
if (!dishRef.value) return;
const { left, top, width, height } =
dishRef.value.getBoundingClientRect();
const X = e.clientX || (e as TouchEvent).touches[0].clientX;
const Y = e.clientY || (e as TouchEvent).touches[0].clientY;
var x = ((X - left) / width) * 100;
var y = ((Y - top) / height) * 100;
if (x < 0) x = 0;
if (x > 100) x = 100;
if (y < 0) y = 0;
if (y > 100) y = 100;
data.left = tofix(x);
data.top = tofix(y);
onInput();
};
mousemove(e);
const mouseup = () => {
onChange();
document.removeEventListener("mousemove", mousemove);
document.removeEventListener("touchmove", mousemove);
document.removeEventListener("mouseup", mouseup);
document.removeEventListener("touchend", mouseup);
};
document.addEventListener("mousemove", mousemove);
document.addEventListener("touchmove", mousemove);
document.addEventListener("mouseup", mouseup);
document.addEventListener("touchend", mouseup);
};
const onInput = () => emit("input", { ...data });
var changeTime: any = null;
const onChange = () => {
clearTimeout(changeTime);
changeTime = setTimeout(() => emit("change", { ...data }), 500);
};
// var offsetTime = null;
// watch(data, (v) => {
// const obj = { ...v };
// emit("input", obj);
// clearTimeout(offsetTime);
// offsetTime = setTimeout(() => emit("change", obj), 50);
// });
// defineExpose({
// open,
// close,
// });
</script>
<style scoped lang="less">
.offset-tool {
width: 125px;
height: 125px;
display: flex;
position: relative;
overflow: hidden;
--gap: 15px;
> .dish {
margin: var(--gap) 0 0 var(--gap);
flex: 1;
border: 1px solid #000;
border-radius: 5px;
cursor: pointer;
position: relative;
background-color: #fff;
> span {
pointer-events: none;
user-select: none;
position: absolute;
top: 0%;
left: 0%;
transform: translate(-50%, -50%);
width: 8px;
height: 8px;
background-color: #000;
border-radius: 50%;
}
}
> .tip {
position: absolute;
right: 4px;
bottom: 0;
font-size: 10px;
pointer-events: none;
user-select: none;
color: #666;
}
> input.left {
right: 0;
}
> input.top {
bottom: 0;
left: 0;
transform-origin: left bottom;
transform: rotate(90deg) translateX(-100%);
}
> input {
position: absolute;
width: calc(100% - var(--gap));
-webkit-appearance: none;
appearance: none;
height: 8px;
border-radius: 8px;
background: rgba(0, 0, 0, 0.1); /* 更柔和的颜色 */
// outline: none;
&::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 8px;
height: 8px;
border-radius: 50%;
background: #4285f4; /* 蓝色滑块 */
cursor: pointer;
transition: all 0.2s;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.1);
}
&::-webkit-slider-thumb:hover {
background: #3b77db;
transform: scale(1.1);
}
}
}
</style>