木灵鱼儿
阅读:967
自己照着别人教程写的canvas操作类
预览图
类源码
/*
* @Author: mulingyuer
* @Date: 2021-06-21 11:29:12
* @LastEditTime: 2021-06-21 18:39:31
* @LastEditors: mulingyuer
* @Description: canvas
* @FilePath: \form-create\src\utils\canvas.js
* 怎么可能会有bug!!!
*/
class Chart {
constructor(options) {
const defaultOptions = {
el: null, //dom或者选择器
data: [], //组件数据
scale: 1, //canvas缩放等级
minScale: 0.2, //最小缩放
maxScale: 3, //最大缩放
scaleStep: 0.1, //每次缩放比率
offsetX: 0, //画布x轴缩放偏移值
offsetY: 0, //画布y轴缩放偏移值
};
//合并配置
Object.assign(this, defaultOptions, options);
this.init(); //初始化
this.addScaleEvent(); //添加缩放
this.addCanvasDragEvent(); //添加画布拖拽
this.render(); //首次渲染
}
//初始化
init() {
this.wrap = null;
//获取容器
if (typeof this.el === "string") {
this.wrap = document.querySelector(el);
} else {
if (this.utils.getType(this.el) !== "htmldivelement") {
throw new Error("el参数不正确:它应该是选择器或者dom对象");
}
this.wrap = this.el;
}
//获取容器样式&创建canvas
const wrapStyle = getComputedStyle(this.wrap);
this.width = parseInt(wrapStyle.width, 10);
this.height = parseInt(wrapStyle.height, 10);
this.canvas = document.createElement('canvas');
this.canvas.width = this.width;
this.canvas.height = this.height;
this.ctx = this.canvas.getContext('2d');
this.wrap.appendChild(this.canvas);
}
//绘制背景图
drawBg(bgData) {
const { src, data } = bgData;
if (!src) return;
if (bgData.el) {
this.ctx.drawImage(bgData.el, data.left ?? 0, data.top ?? 0, data.width ?? 0, data.height ?? 0);
} else {
bgData.el = new Image();
bgData.el.onload = () => {
this.ctx.drawImage(bgData.el, data.left ?? 0, data.top ?? 0, data.width ?? 0, data.height ?? 0);
};
bgData.el.src = src;
}
}
//是否为背景图
isDrawBg(itemData, x, y) {
const { data } = itemData;
const isX = x >= data.left && x <= data.left + data.width;
const isY = y >= data.top && y <= data.top + data.height;
return isX && isY;
}
//添加滚轮缩放事件
addScaleEvent() {
//移入-添加
this.canvas.addEventListener('mouseenter', () => {
document.addEventListener('mousewheel', this.scaleMouseWhell, { passive: false });
document.addEventListener('DOMMouseScroll', this.scaleMouseWhell, { passive: false }); //火狐
});
//移出-删除
this.canvas.addEventListener('mouseleave', () => {
document.removeEventListener('mousewheel', this.scaleMouseWhell, { passive: false });
document.removeEventListener('DOMMouseScroll', this.scaleMouseWhell, { passive: false });//火狐
});
}
//缩放滚轮
scaleMouseWhell = (event) => {
//阻止默认事件 (缩放时外部容器禁止滚动)
event.preventDefault();
const delta = event.deltaY || event.detail;
// const x = event.offsetX - this.offsetX;
// const y = event.offsetY - this.offsetY;
const offsetX = (x / this.scale) * this.scaleStep;
const offsetY = (y / this.scale) * this.scaleStep;
if (delta > 0) {
//往下滚-缩放
// this.offsetX -= this.scale >= this.maxScale ? 0 : offsetX;
// this.offsetY -= this.scale >= this.maxScale ? 0 : offsetY;
this.scale -= this.scaleStep
} else {
//往上滚-放大
// this.offsetX += this.scale <= this.minScale ? 0 : offsetX;
// this.offsetY += this.scale <= this.minScale ? 0 : offsetY;
this.scale += this.scaleStep;
};
this.scale = Math.min(this.maxScale, Math.max(this.scale, this.minScale));
//重新渲染
this.render();
}
//添加画布拖拽事件
addCanvasDragEvent = () => {
this.canvas.addEventListener('mousedown', this.addMoveEvent);
document.addEventListener('mouseup', this.removeMoveEvent);
}
//添加鼠标移动 功能,获取保存当前点击坐标
addMoveEvent = (event) => {
this.targetX = event.offsetX;
this.targetY = event.offsetY;
this.mousedownOriginX = this.offsetX;
this.mousedownOriginY = this.offsetY;
const x = (this.targetX - this.offsetX) / this.scale;
const y = (this.targetY - this.offsetY) / this.scale;
//判断移动的是谁
this.activeModData = null;
this.data.forEach((itemData, index) => {
switch (itemData.type) {
case 'background':
if (this.isDrawBg(itemData, x, y)) {
//提高层级
this.data.push(...this.data.splice(index, 1));
this.draw(itemData);
this.activeModData = itemData;
}
break;
}
});
if (!this.activeModData) {
this.wrap.style.cursor = 'grabbing'
this.canvas.addEventListener('mousemove', this.moveCanvasFunc, false);
} else {
this.wrap.style.cursor = 'all-scroll'
this.modOldX = null
this.modOldY = null
this.canvas.addEventListener('mousemove', this.moveModFunc, false)
}
}
//移除拖拽事件
removeMoveEvent = () => {
this.wrap.style.cursor = '';
this.canvas.removeEventListener('mousemove', this.moveCanvasFunc, false);
this.canvas.removeEventListener('mousemove', this.moveModFunc, false);
}
//画布拖拽方法
moveCanvasFunc = (event) => {
// 获取 最大可移动宽
const maxMoveX = this.canvas.width / 2;
const maxMoveY = this.canvas.height / 2;
const offsetX = this.mousedownOriginX + (event.offsetX - this.targetX);
const offsetY = this.mousedownOriginY + (event.offsetY - this.targetY);
this.offsetX = Math.abs(offsetX) > maxMoveX ? this.offsetX : offsetX;
this.offsetY = Math.abs(offsetY) > maxMoveY ? this.offsetY : offsetY;
this.render()
}
//模块拖动事件
moveModFunc = (event) => {
let moveX = event.offsetX - (this.modOldX || this.targetX);
let moveY = event.offsetY - (this.modOldY || this.targetY);
moveX /= this.scale
moveY /= this.scale
switch (this.activeModData.type) {
case 'background':
const { data } = this.activeModData;
let x = data.left;
let y = data.top;
Object.assign(this.activeModData.data, { left: x + moveX, top: y + moveY })
break;
}
this.modOldX = event.offsetX
this.modOldY = event.offsetY
this.render()
}
//通用绘制方法
draw(data) {
this.ctx.setTransform(this.scale, 0, 0, this.scale, this.offsetX, this.offsetY);
switch (data.type) {
case 'background':
this.drawBg(data);
break;
default:
console.log("该组件数据暂不支持", data);
}
}
//渲染
render() {
this.canvas.width = this.width;
this.data.forEach(itemData => {
this.draw(itemData);
});
}
//push
push(data) {
this.data.push(data);
this.draw(data);
}
//工具
utils = {
//获取文件类型
getType(value) {
let type = typeof value;
if (type !== "object") {
return type;
}
return Object.prototype.toString.call(value).slice(8, -1).toLowerCase();
}
}
}
export default Chart;
使用
const $canvas = new Chart({
el: this.$refs.chartWrap,
data: [],
});
$canvas.push({
id: uuid(),
type: "background",
src: "https://testsuperres.shetuan.pro/cos/admin/images/20210427/928c2e5ba23997b3e19307608b2fe76e.png",
data: {
top: 20,
left: 18,
width: 80,
height: 80,
},
});
$canvas.push({
id: uuid(),
type: "background",
src: "https://testsuperres.shetuan.pro/cos/admin/images/20210408/843ebf2969ad1635cf893ec74887ddd8.png",
data: {
top: 20,
left: 18,
width: 364,
height: 350,
},
});
参考文章
版权申明
本文系作者 @木灵鱼儿 原创发布在木灵鱼儿 - 有梦就能远航站点。未经许可,禁止转载。
相关推荐
Promise失败重试,可指定重试次数
//模拟异步请求 function axiosFn() { return new Promise((resolve, reject) => { const flge = Math.random(); //随机值 setTimeout(() => { //大于0.7就是成功 if (flge > 0.7) { return resolve(flge); } else { return reject(...
animation 动画的三个事件
const div = document.querySelector("div.box"); div.addEventListener("animationstart", function() { //动画开始运行触发 }); div.addEventListener("animationiteration", function() { //动画每执行一次触发一次,适用用多次动画 }); div.addEventListener("animationend", function() { //...
手写Promise
/* * @Author: mulingyuer * @Date: 2021-12-30 22:06:58 * @LastEditTime: 2022-01-03 05:22:30 * @LastEditors: mulingyuer * @Description: 手写promise * @FilePath: \undefinedc:\Users\13219\Desktop\promise.js * 怎么可能会有bug!!! */ /** * @description: 自定义promise * @param {fucntion} executor 执行器函数(同...
class 与 await结合
class As { constructor() {} then(resolve, reject) { setTimeout(() => { resolve(true); }, 3000) } } (async () => { const b = await new As(); console.log(b); })()
深度合并对象的方法
找了很久,现有的库有两个:1. Mergenpm地址: Merge用法:import merge from 'merge'; const obj1 = { name: 2}; const obj2 = { value: 1 }; //合并 merge(obj1,obj2); console.log(obj1); //{name:2,value:1} //克隆合并-与目标对象无关联 const obj3 = merge(true,obj1,obj2); console.log(obj3); //{name:2,value:1} console.log(obj3 === obj1)...
利用JSON过滤对象和数组中指定的key属性
有时候我们在vue中进行for循环,就会涉及到绑定唯一值key的问题,但是并不是任何时候都会存在所谓的唯一值,使用index下标明显是不合适的,官方也不推荐,除非你for循环出来的列表不用变化。所以一般常用的做法就是给for循环的对象添加一个属性,属性的值是随机的uuid或者时间戳。这样前端问题解决了,如果遍历的数据还需要提交到后端,那么不就多了一个属性,这个属性后端不需要的。所以,我们需要在提交数据前,对数据进行过滤。过滤又得for循环删除?那怎么行,有没有那种通用的,简单的方法。过滤方法/** * @description: 过滤对象中指定的属性,也可以拿来浅拷贝 * @para...
Copy 一个复制操作的类
前言js有一个31k多的star的开源复制库:clipboard.js;但是一些简单复制并不想安装一个库来解决,所以就想自己写一个。copy所需要的东西Selection 对象用于获取被用户选中的部分,通过toString()方法可以获取被选中的文本内容,以及js操作选中。MDN文档:SelectionexecCommand 对象用于以命令的形式来操作网页的内容,说白了就是用它来实现复制文本操作,复制的是选中的文本MDN文档:execCommand 需要注意的是,execCommand在未来将会被遗弃,因为这个api本身是从ie浏览器那边继承的,久而久之各大浏览器都对其做了兼容,虽然...
对象扁平化
前言后端返回给前端的数据,有时候会是一个多层级对象,但是我们前端使用的时候,for循环遍历渲染时,多层级对象往往需要进行单独处理,因为还需要判断这个key值是否存在,否则会报错。强行让后端改变数据结构又好像不现实,无奈,只有自己处理了。掘金看到一位大佬文章《【算法】JS 实现对象的扁平化》感觉很合适,逻辑清晰。要求将对象中的层级扁平化,改成如下格式:// 实现一个 flatten 函数,实现如下的转换功能 const obj = { a: 1, b: [1, 2, { c: true }], c: { e: 2, f: 3 }, g: null, }; // 转换为 l...