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