canvas 可视化操作-拖拽&缩放&移动


canvas拖拽的实现

/*
canvas 可视化操作-拖拽&缩放&移动
*/
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');

const stautsConfig = {
	//拖拽开始
	IDLE: 0, 
	//拖拽中
	DRAG_START: 1,
	//拖拽结束
	DRAGGING: 2

}
//画布信息
const canvasInfo = {
	status: stautsConfig.IDLE,			//拖拽状态
	dragTarget: null, 					//拖拽对象
	lastEvtPos: { x: null, y: null },	//计算偏移量坐标
	offsetEvtPos: { x: null, y: null }, //偏移事件位置
    offset: { x: 0, y: 0 },             //缩放偏移
    scale: 1,                           //缩放比例
    scaleStep: 0.1,                     //每次缩放产生的变化量
    maxScale: 2,                        //最大缩放倍数
    minScale: 0.5                       //最小缩放倍数
 }
const cirlces = [];
//画圆
const drawCircle = (ctx, cx, cy, r) => {
	ctx.save();

	ctx.beginPath();
	ctx.strokeStyle = 'blue';
	ctx.arc(cx, cy, r, 0, Math.PI*2);
	ctx.stroke();

	ctx.closePath();
	ctx.restore();
}
// ctx.translate(100, 0);
//视图层绘制
drawCircle(ctx, 100, 100, 20);
//数据层记录
cirlces.push({
	x: 100, 
	y: 100,
	r: 20
})
drawCircle(ctx, 200, 200, 30);
cirlces.push({
	x: 200, 
	y: 200,
	r: 30
})

/*————————————————拖拽———————————————————*/

//画布位置
const getCanvasPosition = e => {
	return {
		x: e.offsetX,
		y: e.offsetY
	}
}

//获取距离
const getDistance = (p1, p2) => {
	return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2);
}

//判断是否在圆内
const ifInCirlce = (pos) => {
	for (let i = 0; i < cirlces.length; i++) {
		//如果两个距离小于半径就返回
		if (getDistance(cirlces[i], pos) < cirlces[i].r) {
			return cirlces[i]
		}
	}
	return false;

}

//鼠标按下
canvas.addEventListener('mousedown', e => {
	const canvasPosition = getCanvasPosition(e);
	const cirlceRef = ifInCirlce(canvasPosition);
	//如果拖拽对象条件成立,系统进入拖拽状态
	if (cirlceRef) {
		//记录拖拽目标、状态、偏移量位置、偏移事件位置
		canvasInfo.dragTarget = cirlceRef;
		canvasInfo.status = stautsConfig.DRAG_START;
		canvasInfo.lastEvtPos = canvasPosition; 
		canvasInfo.offsetEvtPos = canvasPosition;
	}
})

//鼠标移动
canvas.addEventListener('mousemove', e => {
	const canvasPosition = getCanvasPosition(e);
	//如果在某个圆内,修改拖动中的鼠标样式
	if (ifInCirlce(canvasPosition)) {
		canvas.style.cursor = 'all-scroll';
	} else {
		canvas.style.cursor = ''
	}
	//如果第一次距离和第二次之间大于5,代表真正的拖动(防止抖动,一按下就移动的问题)
	if (canvasInfo.status === stautsConfig.DRAG_START && getDistance(canvasPosition, canvasInfo.lastEvtPos) > 5) {
		console.log('try');
		canvasInfo.status = stautsConfig.DRAGGING;
		//更新偏移事件位置
		canvasInfo.offsetEvtPos = canvasPosition;
	} else if (canvasInfo.status === stautsConfig.DRAGGING){
		console.log('拖拽中');
		const { dragTarget } = canvasInfo;
		dragTarget.x += (canvasPosition.x - canvasInfo.offsetEvtPos.x);
		dragTarget.y += (canvasPosition.y - canvasInfo.offsetEvtPos.y);
		//拖拽时候清空并重绘圆
		ctx.clearRect(0, 0, canvas.width, canvas.height);
		cirlces.forEach(item => drawCircle(ctx, item.x, item.y, item.r));
		canvasInfo.offsetEvtPos = canvasPosition;
	}
})

//鼠标抬起
canvas.addEventListener('mouseup', e => {
	if (canvasInfo.status === stautsConfig.DRAGGING) canvasInfo.status = stautsConfig.IDLE;
})

/*————————————————滚轮缩放———————————————————*/
canvas.addEventListener('wheel', e => {
    e.preventDefault();
    const canvasPosition = getCanvasPosition(e);
    //计算出鼠标在画布的坐标位置
    const realCanvasPosition = {
        x: canvasPosition.x - canvasInfo.offset.x,
        y: canvasPosition.y - canvasInfo.offset.y
    }
    //变化偏移量
    const { scaleStep } = canvasInfo;
    const deltaX = realCanvasPosition.x / canvasInfo.scale + scaleStep;
    const deltaY = realCanvasPosition.y / canvasInfo.scale + scaleStep;
    //上下滚轮分别赋值
    if (e.wheelDelta > 0) {
        canvasInfo.offset.x -= deltaX;
        canvasInfo.offset.y -= deltaY;
        canvasInfo.scale += scaleStep;
    } else {
        canvasInfo.offset.x += deltaX;
        canvasInfo.offset.y += deltaY;
        canvasInfo.scale -= scaleStep;
    }
    //通过矩阵变换重置当前的坐标系
    ctx.setTransform(canvasInfo.scale, 0, 0, canvasInfo.scale, canvasInfo.offset.x, canvasInfo.offset.y);
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    cirlces.forEach(item => drawCircle(ctx, item.x, item.y, item.r));
})