4 changed files with 2 additions and 439 deletions
@ -1,408 +0,0 @@ |
|||||
<template> |
|
||||
<div class="fabric-view"> |
|
||||
<el-space :gutter="10" class="toolbar"> |
|
||||
<el-button @click="()=>performanceTest()">性能测试</el-button> |
|
||||
<el-button @click="selectAll">全选</el-button> |
|
||||
<div class="demo-color-block"> |
|
||||
<span class="demonstration">物体数:<el-text type="danger">{{ restate.objects }}</el-text></span> |
|
||||
<span class="demonstration"> 顶点数:<el-text type="danger">{{ restate.vertices }}</el-text></span> |
|
||||
<span class="demonstration"> 三角形数:<el-text type="danger">{{ restate.faces }}</el-text></span> |
|
||||
</div> |
|
||||
</el-space> |
|
||||
<div class="main-content"> |
|
||||
<div class="canvas-container" ref="canvasContainer" /> |
|
||||
</div> |
|
||||
</div> |
|
||||
</template> |
|
||||
<script setup lang="ts"> |
|
||||
import * as fabric from 'fabric' |
|
||||
import { nextTick, onBeforeUnmount, onMounted, reactive, ref } from 'vue' |
|
||||
import Theme from '@/core/Theme.ts' |
|
||||
|
|
||||
// DOM refs |
|
||||
const canvasContainer = ref(null) |
|
||||
|
|
||||
const restate = reactive({ |
|
||||
targetColor: '#ff0000', |
|
||||
loadScale: 1, |
|
||||
mode: 'translate', |
|
||||
objects: 0, |
|
||||
vertices: 0, |
|
||||
faces: 0 |
|
||||
}) |
|
||||
|
|
||||
// 状态变量 |
|
||||
const state = { |
|
||||
showAxesHelper: true, |
|
||||
showGridHelper: true, |
|
||||
camera: { |
|
||||
position: { x: 0, y: 5, z: 10 }, |
|
||||
rotation: { x: 0, y: 0, z: 0 } |
|
||||
} |
|
||||
} |
|
||||
onMounted(() => { |
|
||||
nextTick(() => { |
|
||||
initCanvas() |
|
||||
drawOriginMarker() |
|
||||
|
|
||||
drawHelpGrid() |
|
||||
addPanAndZoomHandlers() |
|
||||
}) |
|
||||
}) |
|
||||
|
|
||||
onBeforeUnmount(() => { |
|
||||
disposeFabric() |
|
||||
}) |
|
||||
|
|
||||
let canvas: fabric.Canvas |
|
||||
let resizeObserver: ResizeObserver | null = null |
|
||||
|
|
||||
function initCanvas() { |
|
||||
const container = canvasContainer.value |
|
||||
if (!container) return |
|
||||
|
|
||||
// 创建 fabric.Canvas 实例 |
|
||||
canvas = new fabric.Canvas(null, { |
|
||||
width: container.clientWidth, |
|
||||
height: container.clientHeight, |
|
||||
selection: true, |
|
||||
backgroundColor: Theme.backgroundColor |
|
||||
// preserveObjectStacking: true, |
|
||||
// renderOnAddRemove: false |
|
||||
}) |
|
||||
container.appendChild(canvas.getElement()) |
|
||||
canvas.enableMouseWheel = true |
|
||||
|
|
||||
// 设置视口初始位置 |
|
||||
// canvas.setViewportTransform([1, 0, 0, 1, 0, 0]) |
|
||||
canvas.requestRenderAll() |
|
||||
canvas.zoomToPoint(new fabric.Point(0, 0), 36) |
|
||||
|
|
||||
const viewerDom = canvasContainer.value |
|
||||
if (resizeObserver) { |
|
||||
resizeObserver.unobserve(viewerDom) |
|
||||
} |
|
||||
resizeObserver = new ResizeObserver(handleResize) |
|
||||
resizeObserver.observe(viewerDom) |
|
||||
} |
|
||||
|
|
||||
const handleResize = _.debounce(() => { |
|
||||
const container = canvasContainer.value // 假设这是你的容器元素引用 |
|
||||
if (container && canvas) { |
|
||||
const newWidth = container.clientWidth |
|
||||
const newHeight = container.clientHeight |
|
||||
|
|
||||
// 更新 canvas 尺寸 |
|
||||
canvas.setDimensions({ |
|
||||
width: newWidth, |
|
||||
height: newHeight |
|
||||
}) |
|
||||
canvas.requestRenderAll() |
|
||||
} |
|
||||
}, 200) |
|
||||
|
|
||||
function addPanAndZoomHandlers() { |
|
||||
const lowerCanvas = canvas.getElement() |
|
||||
let isDragging = false |
|
||||
let lastPosX = 0 |
|
||||
let lastPosY = 0 |
|
||||
let dragStartTransform = [] |
|
||||
|
|
||||
// 🔥 阻止默认右键菜单弹出 |
|
||||
lowerCanvas.addEventListener('contextmenu', (e) => e.preventDefault()) |
|
||||
|
|
||||
// ✅ 使用原生事件监听右键按下 |
|
||||
lowerCanvas.addEventListener('mousedown', function(e) { |
|
||||
if (e.button === 2) { // 右键 |
|
||||
isDragging = true |
|
||||
lastPosX = e.clientX |
|
||||
lastPosY = e.clientY |
|
||||
dragStartTransform = [...canvas.viewportTransform] |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
lowerCanvas.addEventListener('mousemove', function(e) { |
|
||||
if (isDragging) { |
|
||||
const deltaX = e.clientX - lastPosX |
|
||||
const deltaY = e.clientY - lastPosY |
|
||||
|
|
||||
// 更新 viewport 平移部分 |
|
||||
canvas.viewportTransform[4] = dragStartTransform[4] + deltaX |
|
||||
canvas.viewportTransform[5] = dragStartTransform[5] + deltaY |
|
||||
|
|
||||
canvas.renderAll() |
|
||||
} |
|
||||
}) |
|
||||
|
|
||||
lowerCanvas.addEventListener('mouseup', function() { |
|
||||
isDragging = false |
|
||||
}) |
|
||||
|
|
||||
lowerCanvas.addEventListener('wheel', function(e) { |
|
||||
const delta = e.deltaY |
|
||||
let zoom = canvas.getZoom() |
|
||||
zoom *= 0.999 ** delta |
|
||||
if (zoom > 150) zoom = 150 |
|
||||
if (zoom < 5) zoom = 5 |
|
||||
canvas.zoomToPoint({ x: e.offsetX, y: e.offsetY }, zoom) |
|
||||
console.log('Zoom level:', zoom) |
|
||||
e.preventDefault() |
|
||||
e.stopPropagation() |
|
||||
}, { passive: false }) |
|
||||
} |
|
||||
|
|
||||
/** |
|
||||
* 绘制辅助网格线 |
|
||||
*/ |
|
||||
function drawHelpGrid() { |
|
||||
const step = 1 |
|
||||
const xcount = 200 |
|
||||
const ycount = 200 |
|
||||
|
|
||||
// 从 -xcount/2 到 xcount/2 画辅助线, 步长为 step |
|
||||
// 从 -ycount/2 到 ycount/2 画辅助线, 步长为 step |
|
||||
// 颜色为 Theme.helpGridLineColor |
|
||||
|
|
||||
const halfWidth = (xcount / 2) * step |
|
||||
const halfHeight = (ycount / 2) * step |
|
||||
|
|
||||
// 垂直线(X轴方向) |
|
||||
for (let x = -halfWidth; x <= halfWidth; x += step) { |
|
||||
const line = new fabric.Line([x, -halfHeight, x, halfHeight], { |
|
||||
stroke: Theme.helpGridLineColor, |
|
||||
selectable: false, |
|
||||
evented: false, |
|
||||
strokeWidth: 0.05, |
|
||||
hasControls: false, |
|
||||
hasBorders: false |
|
||||
}) |
|
||||
canvas.add(line) |
|
||||
} |
|
||||
|
|
||||
// 水平线(Y轴方向) |
|
||||
for (let y = -halfHeight; y <= halfHeight; y += step) { |
|
||||
const line = new fabric.Line([-halfWidth, y, halfWidth, y], { |
|
||||
stroke: Theme.helpGridLineColor, |
|
||||
selectable: false, |
|
||||
evented: false, |
|
||||
strokeWidth: 0.05, |
|
||||
hasControls: false, |
|
||||
hasBorders: false |
|
||||
}) |
|
||||
canvas.add(line) |
|
||||
} |
|
||||
|
|
||||
canvas.renderAll() |
|
||||
} |
|
||||
|
|
||||
|
|
||||
function drawOriginMarker() { |
|
||||
const marker = new fabric.Circle({ |
|
||||
radius: 5, |
|
||||
fill: 'red', |
|
||||
left: 0, |
|
||||
top: 0, |
|
||||
hasControls: false, |
|
||||
hasBorders: false, |
|
||||
lockMovementX: true, |
|
||||
lockMovementY: true, |
|
||||
selectable: false, |
|
||||
excludeFromExport: true, |
|
||||
objectCaching: false |
|
||||
}) |
|
||||
|
|
||||
marker.render = function(ctx) { |
|
||||
ctx.save() |
|
||||
ctx.translate(this.left, this.top) |
|
||||
ctx.beginPath() |
|
||||
ctx.arc(0, 0, 0.5, 0, Math.PI * 2, true) |
|
||||
ctx.fillStyle = 'red' |
|
||||
ctx.fill() |
|
||||
ctx.restore() |
|
||||
} |
|
||||
|
|
||||
canvas.add(marker) |
|
||||
canvas.requestRenderAll() |
|
||||
} |
|
||||
|
|
||||
|
|
||||
function selectAll() { |
|
||||
const objects = canvas.getObjects().filter(obj => obj.selectable !== false) |
|
||||
const group = new fabric.Group(objects, { |
|
||||
originX: 'center', |
|
||||
originY: 'center' |
|
||||
}) |
|
||||
canvas.setActiveObject(group) |
|
||||
canvas.requestRenderAll() |
|
||||
} |
|
||||
|
|
||||
function disposeFabric() { |
|
||||
|
|
||||
} |
|
||||
|
|
||||
function performanceTest(count = 10) { |
|
||||
const spacing = 0.3 |
|
||||
const nodes = [] |
|
||||
debugger |
|
||||
|
|
||||
// 创建节点并加入画布 |
|
||||
for (let y = 0; y < count; y++) { |
|
||||
for (let x = 0; x < count; x++) { |
|
||||
const node = new fabric.Rect({ |
|
||||
left: x * spacing, |
|
||||
top: y * spacing, |
|
||||
width: 10, |
|
||||
height: 10, |
|
||||
fill: 'blue', |
|
||||
originX: 'center', |
|
||||
originY: 'center', |
|
||||
hasControls: false, |
|
||||
hasBorders: false |
|
||||
}) |
|
||||
canvas.add(node) |
|
||||
nodes.push(node) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
// 创建连线 |
|
||||
for (let i = 0; i < nodes.length; i++) { |
|
||||
const node = nodes[i] |
|
||||
const col = i % count |
|
||||
const row = Math.floor(i / count) |
|
||||
|
|
||||
// 连接右侧节点 |
|
||||
if (col < count - 1) { |
|
||||
const rightNode = nodes[i + 1] |
|
||||
const line = new fabric.Line( |
|
||||
[node.left, node.top, rightNode.left, rightNode.top], |
|
||||
{ |
|
||||
stroke: 'black', |
|
||||
strokeWidth: 0.5, |
|
||||
selectable: false |
|
||||
} |
|
||||
) |
|
||||
canvas.add(line) |
|
||||
} |
|
||||
|
|
||||
// 连接下方节点 |
|
||||
if (row < count - 1) { |
|
||||
const bottomNode = nodes[i + count] |
|
||||
const line = new fabric.Line( |
|
||||
[node.left, node.top, bottomNode.left, bottomNode.top], |
|
||||
{ |
|
||||
stroke: 'black', |
|
||||
strokeWidth: 0.5, |
|
||||
selectable: false |
|
||||
} |
|
||||
) |
|
||||
canvas.add(line) |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
canvas.requestRenderAll() |
|
||||
} |
|
||||
|
|
||||
function deleteSelected() { |
|
||||
const activeObj = canvas.getActiveObject() |
|
||||
if (!activeObj) return |
|
||||
|
|
||||
if (activeObj.type === 'group') { |
|
||||
const g: fabric.Group = activeObj |
|
||||
g.getObjects().forEach(obj => canvas.remove(obj)) |
|
||||
} else { |
|
||||
canvas.remove(activeObj) |
|
||||
} |
|
||||
canvas.discardActiveObject() |
|
||||
canvas.requestRenderAll() |
|
||||
} |
|
||||
|
|
||||
fabric.Canvas.prototype.rateRule = 1 |
|
||||
|
|
||||
fabric.Canvas.prototype.toPixel = function(value) { |
|
||||
return value * this.rateRule |
|
||||
} |
|
||||
|
|
||||
fabric.Canvas.prototype.toUnit = function(value, unit = 'cm') { |
|
||||
return value / this.rateRule |
|
||||
} |
|
||||
|
|
||||
</script> |
|
||||
<style scoped lang="less"> |
|
||||
.fabric-view { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
flex-grow: 1; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
.canvas-left-toolbar { |
|
||||
position: absolute; |
|
||||
left: 5px; |
|
||||
top: 5px; |
|
||||
width: 32px; |
|
||||
background: #eee; |
|
||||
text-align: center; |
|
||||
|
|
||||
button { |
|
||||
color: #555; |
|
||||
background-color: #ddd; |
|
||||
border: 0; |
|
||||
margin: 0; |
|
||||
padding: 5px 8px; |
|
||||
font-size: 12px; |
|
||||
text-transform: uppercase; |
|
||||
cursor: pointer; |
|
||||
outline: none; |
|
||||
} |
|
||||
|
|
||||
button.selected { |
|
||||
background-color: #fff; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.toolbar { |
|
||||
padding: 10px; |
|
||||
background: #f5f5f5; |
|
||||
border-bottom: 1px solid #ccc; |
|
||||
} |
|
||||
|
|
||||
.dialog-container { |
|
||||
display: flex; |
|
||||
flex-direction: column; |
|
||||
height: 100vh; |
|
||||
} |
|
||||
|
|
||||
.main-content { |
|
||||
display: flex; |
|
||||
flex: 1; |
|
||||
overflow: hidden; |
|
||||
|
|
||||
.model3d-content { |
|
||||
height: 100%; |
|
||||
display: flex; |
|
||||
flex-direction: row; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
.canvas-container { |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
position: relative; |
|
||||
overflow: hidden; |
|
||||
} |
|
||||
|
|
||||
.model3d-gui-wrap { |
|
||||
border-left: 1px solid #ccc; |
|
||||
background: #111; |
|
||||
width: 100%; |
|
||||
height: 100%; |
|
||||
overflow-y: auto; |
|
||||
} |
|
||||
|
|
||||
.gui-panel { |
|
||||
:deep(.lil-gui.root) { |
|
||||
width: 100%; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
</style> |
|
||||
@ -1,7 +0,0 @@ |
|||||
import 'fabric' |
|
||||
|
|
||||
declare module 'fabric' { |
|
||||
interface Canvas { |
|
||||
enableMouseWheel: boolean |
|
||||
} |
|
||||
} |
|
||||
Loading…
Reference in new issue