Fabric 画布缩放、拖动、初始化大小
作为自己项目的基础功能之一,自然是需要第一个回顾记录的了!
1.拖动画布
2.缩放画布
3.监听窗口大小变化,从而初始化画布位置、大小
涉及相关API:键盘快捷键功能、滚轮功能、监听窗口变化、fabric.js相关事件及API;
示例说明:
- 拖动画布:按住空格键,然后点击鼠标左键拖动画布;(在操作过程中,需要处理fabric.js图形的相关控制项,避免影响拖动操作!)
- 缩放画布:以鼠标当前位置为中心,进行画布内容的整体缩放;
- 初始化大小:设定一个舞台与画布之间的大小比例,当窗口大小变化时,对舞台进行居中且缩放至自己所设定的比例大小!
以下代码仅作为功能的完整示例;未进行相关模块划分、封装,实际开发中自行处理这些即可!
<template><div class="cdie" id="cdie"><canvas id="c" ref="canvas"></canvas></div>
</template><script setup lang="ts">
import { ref, onMounted, reactive } from "vue";
import { fabric } from "fabric";
import hotkeys from 'hotkeys-js';
let isDragMode = false
let f = null
function getAllValidObjects() {const objs = f.getObjects().filter(e => !e.death)return objs;
}
function setDragState(selectable) {const alls = getAllValidObjects();alls?.forEach((selection) => {selection.selectable = selectable;selection.evented = selectable;// 禁掉图形事件,避免hover时覆盖鼠标样式});
}
function initHotkey() {hotkeys('space', {keydown: true,keyup: true,}, (event, handler) => {if (event.type === 'keydown') {f.discardActiveObject();isDragMode = truesetDragState(false)f.selection = false;f.defaultCursor = 'grab';f.setCursor('grab');} else {drag = falseisDragMode = falsesetDragState(true)f.selection = true;f.defaultCursor = 'default';f.setCursor('default');}event.preventDefault();});
}
let canvas = ref();
window.fabric = fabric
onMounted(() => {const stageWidth = 400const stageHeight = 300window.canvas = f = new fabric.Canvas(canvas.value, {backgroundColor: "grey",minZoom: 0.5, // 最小缩放比例maxZoom: 10, // 最大缩放比例width: 1000,height: 500,});function resize() {f.setWidth(canvas.value.clientWidth);f.setHeight(canvas.value.clientHeight);const canvasW = f.getWidth();const canvasH = f.getHeight();const screenRatio = stageWidth / stageHeight; // 计算舞台的宽高比const sreenW = canvasW * 0.8; // 计算舞台将要缩放至最大的宽高值const sreenH = canvasH * 0.8;const ratioW = sreenW / stageWidth; // 计算对应最大宽高值与舞台对应宽高值的比例const ratioH = sreenH / stageHeight;let absoluteP: fabric.Point;let ratio; // 哪个边放大的比例小,就取哪个边的比例,例如高度只需要1.333倍就能达到对应边的80%,则宽度同样放大1.333倍即可。if (ratioW < ratioH) { absoluteP = new fabric.Point(-(canvasW - sreenW) / 2, -(canvasH - sreenW / screenRatio) / 2);ratio = ratioW;} else {// absoluteP = new fabric.Point(-(canvasW - sreenH * screenRatio) / 2, -(canvasH - sreenH) / 2); absoluteP = new fabric.Point(-(canvasW - stageWidth * ratioH) / 2, -(canvasH - sreenH) / 2); // 这种计算规则更容易理解点。。。ratio = ratioH;}// absoluteP = new fabric.Point(0,0)f.setZoom(ratio); // 设置画布的缩放比例,根据画布元素的左上角为参考点进行缩放的; 此案例为舞台长的那边等比缩放至画布对应边的 80% 大小时的比例。f.absolutePan(absoluteP); // 平移视口,根据画布元素左上角作为参考点进行平移,负数xy就是向右下平移。}window.addEventListener('resize', resize);f.upperCanvasEl.addEventListener('wheel', function (e: WheelEvent) {const point = f.getPointer(e, true);const oldZoom = f.getZoom();let newZoomif (e.deltaY < 0) {// console.log('放大');newZoom = oldZoom * 1.1newZoom > f.maxZoom && (newZoom = f.maxZoom)} else {// console.log('缩小');newZoom = oldZoom / 1.1;newZoom < f.minZoom && (newZoom = f.minZoom)}f.zoomToPoint(point, newZoom); // 基于画布html元素的左上角的相对坐标进行缩放。e.preventDefault();});initHotkey()setInterval(() => {f.renderAll(); // 懒得在测试代码renderAll, 统一这里处理了;}, 1)// 解决矢量图放大过程中模糊 ; 缓存相关,性能好坏影响程度暂不确定;fabric.Object.prototype.objectCaching = false;// fabric.Object.prototype.originX = fabric.Object.prototype.originY = "center";const stage = new fabric.Rect({ // 创建舞台,还可给舞台添加网格,便于用户参考坐标位置等。left: 0,top: 0,width: stageWidth,height: stageHeight,fill: '#30b980',stroke: 'red',strokeWidth: 0,strokeUniform: true,selectable: false,evented: false,death: true // 随便声明个变量,表示该图形为非活动图形;});f.add(stage);const relativeP = new fabric.Point((1000 - stageWidth) / 2,(500 - stageHeight) / 2,);f.relativePan(relativeP);// 相对当前位置进行视点平移 > 将舞台移动至居中;后续新图形的起始点也从该点位开始。let line = new fabric.Line([0, 0, 100, 100], {stroke: "blue",});f.add(line);let line2 = new fabric.Line([220, 220, 311, 311], {stroke: "red",});f.add(line2);initEvent(f)resize()
});
let drag = false
function initEvent(canvas) {canvas.on('mouse:down', (IEvent) => {// console.log(IEvent);// IEvent.absolutePointer 是鼠标点击的相对于起始点的偏移坐标。// IEvent.pointer 是相对于fabric画布左上角的偏移坐标。window.selected = IEvent?.target // 当点击选择到有可选图形时,会获得图形的数据。if (isDragMode) {drag = true}}).on('mouse:up', (e) => {drag = falseif (isDragMode) {f.defaultCursor = 'grab';f.setCursor('grab');}}).on('mouse:move', (IEvent: fabric.IEvent<MouseEvent>) => {if (isDragMode) {if (drag) {f.defaultCursor = 'grab';f.setCursor('grab');const { e } = IEvent// console.log(e.movementX, e.movementY); // 表示鼠标相对于上一次触发鼠标事件时的移动距离。const point = new fabric.Point(e.movementX, e.movementY);f.relativePan(point);}}}).on('mouse:dblclick', (e) => {})
}</script><style scoped lang="less">.cdie {width: 100%;text-align: center;display: flex;justify-content: center;
}
</style>