前言: 上次实现的完整版,老实说有一些不满意的地方。这是当时思考欠缺的地方,所以在实现时进行了一些妥协,在不影响整体功能的情况下进行了实现。今天下班回家的路上,在班车上忽然想起最近的伤心事,不免有些失落,还是觉得应该找点事情做。想起我的这个东西还不够完善,当时想了一个方法,我觉得可行,然后到了住处点了一个外卖就开始进行实现了。最初的想法只是部分可用,但是实现的效果在逻辑上还是不对,不过它也让我对这个东西的逻辑更加了解了,方法再改进即可,最终完成了这个实现。我觉得很多东西就是这样,最初的想法可能不对,但是只要持续进行思考和努力,基本都会有解决办法的!当然了,这是对于事物,对于人,不行就是不行了,也没必要做无谓的努力。
改进效果
GIF图演示
视频演示
canvas图片拖动实现
改进的效果就是让图片拖动时或者点击时的逻辑符合正常人的思维,原来那种实现有问题,图片会会在最底层进行移动,图片的顺序也会产生问题。
具体的实现思路
鼠标点击图形时选择图形思路
图片叠加在一起时,当用户点击图片时,如果是选中了最上层的图片,那就应该拖动最上层的图片,但是原来那种实现方式其实是拖动最下层的图片。因为当时不知道如何拖动最上层的图片,多张图片重叠时,当鼠标点击到图片重叠处时,此时判断是否选中,实际上是都选中了。这次的实现方式是虽然选中了多张图片,但是我只处理最后一张被选中的。图片是依次进行绘制的。所以最底下的图片是最先进行绘制,也就是说最先被选中的,最上层的图片反而是最后被选中的。理清楚这个逻辑之后,那就只取最后被选中的这个图片即可,它就是最上层的图片了。
图形移动时保持图形的顺序
现在虽然选中了图片,但是图片移动过程中的顺序问题也要考虑清楚。当我们拖动图片经过别的图片时,通常的逻辑是被拖动的图片在上层,经过的图片在下层。我最开始的想法是直接将该图片变为对象数组中的最后一个元素,这样每次绘制时 ,它就是最后一个,自然是在所有图片的上方了。但是,在我进行测试时,我发现了不对劲。这里举一个例子说明一下:
图片1 图片2 图片3 图片4 图片5
现在我选择了图片3,此时图片数组的顺序变为了:
图片1 图片2 图片5 图片4 图片3
这里问题就出现了,可能看这个文字描述是看不出来的。这里的问题是从选中的图片之后的图片的顺序错误了,不考虑被选中的图片,最后两张图片的顺序颠倒了 。它会导致一个问题,假如图片4和图片5是重叠的,即图片4在图片5的下层,在选中图片3后,图片4和图片5的顺序错误了,导致图片4跑到了图片5的上层了。 所以解决方式就是:首先保存当前被选中的图片,然后将当前被选中图片之后的图片全部向前移动一位,最后将保存的图片移动到数组的最后。这样就保证了,对象数组中其它对象的相对位置不变了。所以,上述图片的顺序应该变为:
图片1 图片2 图片4 图片5 图片3 。
实现代码
注意: 优化了部分代码,建议结合前两篇博客,以便更好的进行理解。
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>中秋快乐!</title>
<style>
body, div {
margin: 0;
padding: 0;
}
</style>
</head>
<body>
<div id="cas">
<canvas id="cs" width="800" height="600" style="border: 2px solid green"></canvas>
</div>
<script type="text/javascript">
'use strict'
let canvas = document.getElementById("cs");
// 获取canvas的宽高
let width = canvas.width;
let height = canvas.height;
let ctx = canvas.getContext("2d");
// 这里使用默认值即可
//ctx.globalCompositeOperation = "source-over";
let now = {x: 0, y: 0}; // 鼠标的位置
let current = -1;
let rewidth = 150; // 重定义图片的宽度
let imgs = [];
// 创建img元素
Array.from({length: 6}, (a, i)=>i).forEach(index => {
let img = document.createElement("img")
img.src = index + ".jpg";
// 当图片加载完成之后,将其绘制到canvas上,图片没有加载完成时,
// 图片的宽高是0,因为这时候图片其实是空的
img.onload = () => {
let h = resize(img, rewidth);
img.width = rewidth;
img.height = h;
let obj = {img: img, x: 0, y: 0};
console.log(obj);
imgs.push(obj); // 每张图片的默认位置
drawCanvas(obj, "#FFFFFF");
};
});
// 添加鼠标事情,来实现图片的拖放功能。
// 当鼠标按下时,获取当前的坐标
canvas.onmousedown = e => {
console.log("鼠标按下")
let x = e.pageX-canvas.offsetLeft; // 后面这个是偏移量,但是在这里为0
let y = e.pageY-canvas.offsetTop;
now.x = x;
now.y = y;
console.log(x + " -> " + y);
// 清空当前的canvas图形
ctx.clearRect(0, 0, canvas.width, canvas.height);
imgs.forEach((obj, index) => {
drawCanvas(obj, "#FFFFFF");
if (ctx.isPointInPath(x, y)) {
// 最上层的图片是最后绘制的,在图片重叠的情况下,
// 最先选中的是最底层的图片,最后一张被选中的图片
// 才是我们选中的图片。我们只需要操作重叠情况下的最后一张即可。
current = index;
console.log("被选中的图片:", index);
}
});
// 有图片被选中
if (current != -1) {
console.log("被选中的最后一张图片为第 %s 张图片。", current);
// 将当前选中元素移动到数组的最后,同时保证其它元素的保持原来的顺序
let obj = imgs[current]
for (let i = current; i < imgs.length-1; i++) {
imgs[i] = imgs[i+1];
}
imgs[imgs.length-1] = obj;
// 别忘了现在 current 指向的是最后一张图片了。
current = imgs.length-1;
console.log(imgs);
// 绘制红色选中框
drawRect(imgs[current], "red");
}
};
// 在鼠标移动时,不断重绘制整个canvas
canvas.onmousemove = e => {
// 鼠标未按下或者未选中图片时则直接返回,不去响应该事件。
if (current == -1) {
return;
}
// 获取点的坐标可以封装成函数
let x = e.pageX;
let y = e.pageY;
// 清空当前的canvas图形
ctx.clearRect(0, 0, canvas.width, canvas.height);
imgs.forEach((obj, index) => {
drawCanvas(obj, "#FFFFFF");
// 更新当前图片的坐标,它一定是最后一张图片了!
imgs[current].x = imgs[current].x + (x-now.x);
imgs[current].y = imgs[current].y + (y-now.y);
// 判断并即时矫正图片的坐标
judgePosition(imgs[current]);
console.log(imgs);
// 重绘制偏移之后的canvas
imgs.forEach(obj => drawCanvas(obj, "#FFFFFF"));
drawRect(imgs[current], "red");
now.x = x; now.y = y;
console.log("鼠标在移动..." + x + " ==> " + y, obj.img);
});
}
let stopDraw = e => {
current = -1; // 鼠标松开,重置图片选中状态为未选中
console.log("鼠标松开, current: ", current);
imgs.forEach(obj => drawCanvas(obj, "#FFFFFF"));
}
// 鼠标松开和鼠标离开元素绑定相同的处理事件。
canvas.onmouseup = stopDraw;
canvas.onmouseout = stopDraw;
function drawCanvas(obj, color) {
// 绘制所有的图片,这里的宽高是缩放后的宽高
ctx.drawImage(obj.img, obj.x, obj.y, obj.img.width, obj.img.height);
drawRect(obj, color);
}
// 绘制选中框
function drawRect(obj, color) {
// 绘制红色选中框
ctx.beginPath();
ctx.strokeStyle = color;
ctx.rect(obj.x, obj.y, obj.img.width, obj.img.height);
ctx.stroke();
}
// 根据传如的指定宽度,自动调整高度
function resize(img, width) {
let w = img.width;
let h = img.height;
return parseInt(h*width/w);
}
// 判断位置,当点越界时,进行处理
function judgePosition(obj) {
// 这里只需要考虑点的坐标值即可,两个点,4个坐标判断四种情况。
obj.x = obj.x < 0 ? 0 : (obj.x + obj.img.width) < width ? obj.x : width-obj.img.width;
obj.y = obj.y < 0 ? 0 : (obj.y + obj.img.height) < height ? obj.y : height-obj.img.height;
}
</script>
</body>
</html>
总结
对于问题要多进行思考,思考不是一蹴而就的,而是一个逐渐纠错的过程。可能最初的想法是迷糊的、错误的,但是在思考的过程中就会逐渐清晰、正确了。要勇于尝试,不断的尝试自己的想法,当你明白最初的想法是错误的,这才是最重要的,你知道了为什么错误,那么正确的方法也就会逐渐被找到了。