平铺组件

This commit is contained in:
李志鹏
2026-01-07 13:02:08 +08:00
parent 8f1fea30ee
commit ae6f14efa9
4 changed files with 204 additions and 74 deletions

View File

@@ -329,12 +329,6 @@ const otherData = {
<template>
<div class="app-container-example">
<div id="test" style="position: fixed;
width: 500px; height: 500px;
top: 0;
left: 0;
z-index: 9999999;"><pingpu
url="/src/assets/images/canvas/yinhua1.jpg" /></div>
<!-- 模拟左侧导航栏 -->
<!-- <div
class="app-wrapper-btns"

View File

@@ -3,32 +3,154 @@
</template>
<script setup>
import {
defineComponent,
ref,
reactive,
nextTick,
toRefs,
inject,
watch,
computed,
} from "vue";
import { fabric } from "fabric-with-all";
import { ref, watch, onMounted } from "vue";
const props = defineProps({
url: { type: String, required: true },
positionX: { type: Number, default: 0 },// px
positionY: { type: Number, default: 0 },// px
angle: { type: Number, default: 0 },// 角度
size: { type: Number, default: 100 },// %
gapX: { type: Number, default: 0 },// px
gapY: { type: Number, default: 0 },// px
offsetX: { type: Number, default: 0 }, // px
offsetY: { type: Number, default: 0 }, // px
angle: { type: Number, default: 0 }, // 角度
scale: { type: Number, default: 100 }, // %
gapX: { type: Number, default: 0 }, // px
gapY: { type: Number, default: 0 }, // px
});
console.log("==========", props);
watch(
() => props.url,
() => getOriginalImage()
);
watch(
() => [
props.offsetX,
props.offsetY,
props.angle,
props.scale,
props.gapX,
props.gapY,
],
() => setCanvasData()
);
const el = ref(null);
const canvasRef = ref(null);
const canvas = ref(null);
const observer = ref(null);
const id = "asfs123121sfe";
onMounted(async () => {
initCanvas();
await getOriginalImage();
setCanvasData();
let throttleTimeout = null;
let lastRunTime = 0;
let trailingTimeout = null;
observer.value = new ResizeObserver((entries) => {
const now = Date.now();
const throttleDelay = 100;
if (!throttleTimeout) {
updateCanvasSize();
lastRunTime = now;
throttleTimeout = setTimeout(() => {
throttleTimeout = null;
}, throttleDelay);
} else {
clearTimeout(trailingTimeout);
trailingTimeout = setTimeout(() => {
updateCanvasSize();
lastRunTime = Date.now();
}, throttleDelay);
}
});
observer.value.observe(el.value);
});
onBeforeUnmount(() => {
observer.value.disconnect();
});
const initCanvas = () => {
canvas.value = new fabric.Canvas(canvasRef.value, {
selection: false,
evented: false,
});
canvas.value.setWidth(el.value.offsetWidth);
canvas.value.setHeight(el.value.offsetHeight);
};
const updateCanvasSize = () => {
canvas.value.setWidth(el.value.offsetWidth);
canvas.value.setHeight(el.value.offsetHeight);
setCanvasData();
};
const originalImage = ref(null);
const getOriginalImage = () => {
return new Promise((resolve, reject) => {
fabric.Image.fromURL(
props.url,
(object) => {
const imgElement = object.getElement();
// 创建透明 Canvas
const tcanvas = document.createElement("canvas");
tcanvas.width = imgElement.width;
tcanvas.height = imgElement.height;
const ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(imgElement, 0, 0);
originalImage.value = tcanvas;
resolve(tcanvas);
},
{ crossOrigin: "anonymous" }
);
});
};
const setCanvasData = () => {
canvas.value.getObjects().forEach((obj) => {
if (obj.id === id) canvas.value.remove(obj);
});
const image = originalImage.value;
const cwidth = canvas.value.width;
const cheight = canvas.value.height;
const offsetX = props.offsetX;
const offsetY = props.offsetY;
const scaleX = ((cwidth / image.width) * (props.scale / 100)) / 5;
const scaleY = ((cheight / image.height) * (props.scale / 100)) / 5;
const scale = cwidth > cheight ? scaleX : scaleY;
const angle = props.angle;
const gapX = props.gapX;
const gapY = props.gapY;
const patternTransform = fabric.util.composeMatrix({
scaleX: scale,
scaleY: scale,
angle: angle,
});
// 创建透明 Canvas
const tcanvas = document.createElement("canvas");
tcanvas.width = image.width + gapX;
tcanvas.height = image.height + gapY;
const ctx = tcanvas.getContext("2d");
ctx.clearRect(0, 0, tcanvas.width, tcanvas.height);
ctx.drawImage(image, 0, 0);
const pattern = new fabric.Pattern({
source: tcanvas,
repeat: "repeat",
patternTransform,
offsetX, // 水平偏移
offsetY, // 垂直偏移
});
const rect = new fabric.Rect({
id,
width: cwidth,
height: cheight,
top: 0,
left: 0,
// scaleX: 1,
// scaleY: 1,
fill: pattern,
evented: false,
selectable: false,
});
canvas.value.add(rect);
canvas.value.renderAll();
};
</script>
<style lang='less' scoped>
.pingpu {
width: 100%;
height: 100%;
background-color: #f00;
}
</style>

View File

@@ -1,54 +1,73 @@
<template>
<div ref="modalTest"></div>
<a-modal class="modal_test generalModel"
v-model:visible="testModal"
:footer="null"
:get-container="() => $refs.modalTest"
width="78%"
:maskClosable="false"
:centered="true"
:closable="false"
wrapClassName="#app"
:keyboard="false"
>
<div class="generalModel_btn">
<div class="generalModel_closeIcon" @click.stop="cancelDsign()">
<svg width="100%" height="100%" viewBox="0 0 46 46" fill="none" xmlns="http://www.w3.org/2000/svg">
<circle cx="23" cy="23" r="23" fill="#000" fill-opacity="0.3"/>
<rect x="32.5063" y="12" width="3" height="29" rx="1.5" transform="rotate(45 32.5063 12)" fill="white"/>
<rect x="34.6274" y="32.5059" width="3" height="29" rx="1.5" transform="rotate(135 34.6274 32.5059)" fill="white"/>
</svg>
<div class="test">
<div class="control">
<div>
<span>偏移X</span>
<input type="number" v-model="data.offsetX" />
</div>
<div>
<span>偏移Y</span>
<input type="number" v-model="data.offsetY" />
</div>
<div>
<span>角度</span>
<input type="number" v-model="data.angle" />
</div>
<div>
<span>缩放</span>
<input type="number" v-model="data.scale" />
</div>
<div>
<span>水平间距</span>
<input type="number" v-model="data.gapX" />
</div>
<div>
<span>垂直间距</span>
<input type="number" v-model="data.gapY" />
</div>
</div>
<canvasIndex></canvasIndex>
</a-modal>
<div class="box">
<pingpu v-bind="data" />
</div>
</div>
</template>
<script lang="ts">
import { defineComponent,ref,createVNode,nextTick} from 'vue'
import canvasIndex from './index.vue'
export default defineComponent({
components:{canvasIndex},
setup(){
let testModal = ref(true)
const init = ()=>{
testModal.value = true
}
const cancelDsign = ()=>{
testModal.value = false
}
return {
testModal,
init,
cancelDsign,
}
}
<script lang="ts" setup>
import pingpu from "./pingpu.vue";
const data = ref({
url: "/src/assets/images/canvas/yinhua1.jpg",
offsetX: 0, // px
offsetY: 0, // px
angle: 0, // 角度
scale: 100,// %
gapX: 0, // px
gapY: 0, // px
});
window.data = data;
</script>
<style lang='less' scoped>
.box {
border: 1px solid #f00;
}
.test {
> .control {
margin-left: 10px;
margin-top: 10px;
> div {
display: flex;
align-items: center;
margin-bottom: 5px;
> span {
width: 80px;
}
>input{
padding-left: 10px;
border-radius: 5px;
}
}
}
> .box {
width: 100%;
max-width: 400px;
height: 500px;
}
}
</style>

View File

@@ -1,7 +1,5 @@
<template>
<div class='box'>
<tesst></tesst>
</div>
<tesst></tesst>
</template>
<script>
@@ -16,7 +14,4 @@ export default defineComponent({
</script>
<style lang='less' scoped>
.box {
border: 1px solid #f00;
}
</style>