前言

对于图片查看器,在实践中做了两种方案的对比。

第一种是借助 canvas 的方案,如果使用 canvas 会生成一张静态图片, 并且操作 canvas 旋转时,旋转后的坐标比较难计算,对于计算图片的放大缩小有一定的难度。

第二种是 “img+css+js” 的组合方案,可以支持 gif 的动态图片。

相比之下,img 只需要操作 css,其旋转后的坐标也比较容易计算,并且旋转使用了transform 来提高渲染的性能。


下面来介绍一下 “img+css+js” 组合方案的实现方式。


实现效果

【electron学习笔记】如何通过electron实现图片查看器_图片查看器

可参考如下html:

<main class="imageView-container">
<div class="image-view" id="image-view" @contextmenu="showRightMenuFunc">
...
<img
:src="link"
v-show="link && !imageLoad"
:width="imgWidth"
:height="imgHeight"
@load="onload"
class="imageSrc"
:style="position"
id="imgSrc"
/>
<img src="/img/loading.gif" class="img-loading" v-show="imageLoad" />
<div class="pre" @click.stop="optHandle('pre')" :title="preTip" :style="show">
<i class="iconfont icon icon-prev"></i>
</div>
<div class="next" @click.stop="optHandle('next')" :title="nextTip" :style="show">
<i class="iconfont icon icon-next"></i>
</div>
</div>
<div class="image-opt">
...
</div>
</main>


核心功能

放大缩小的难点在于,需要随鼠标放大、缩小、旋转后的坐标变换。

1、先记录鼠标的位置​

// 滚轮放大缩小
imageView.onmousewheel = imageView.onwheel = (event: any) => {
// 记录当前鼠标的位置
const imageView: any = document.getElementById("image-view");
const box = imageView.getBoundingClientRect();
const tempPos = {
x: event.x - box.left,
y: event.y - box.top,
};
const x = position.left.substring(0, position.left.indexOf("px"));
const y = position.top.substring(0, position.left.indexOf("px"));
startPointX.value = Number(((pos.x - Number(x)) / imgScale.value).toFixed(2));
startPointY.value = Number(((pos.y - Number(y)) / imgScale.value).toFixed(2));
pos.x = tempPos.x;
pos.y = tempPos.y;
if (event.wheelDelta > 0) {
increase();
} else {
decrease();
}
};


2、放大​

/**
* @description: 放大
* @param {*}
* @return {*}
*/
const increase = () => {
const image = imageArr[index.value];
if (imgScale.value == 15) {
return;
}
let rR = imgScale.value + 0.1;
if (rR > 15) {
rR = 15;
}
const width = image.width * rR;
const height = image.height * rR;
const [wrapW, wrapH] = getRect();
let x = (1 - rR) * startPointX.value + (pos.x - startPointX.value);
let y = (1 - rR) * startPointY.value + (pos.y - startPointY.value);
if (rotateIndex.value % 2 == 0) {
// console.log(height, wrapW, wrapH, width, x, y);
if (width <= wrapW) {
x = (wrapW - width) / 2;
} else if (width >= wrapW && x > 0) {
x = 0;
} else if (width + x < wrapW) {
x = wrapW - width;
}
if (height <= wrapH) {
y = (wrapH - height) / 2;
} else if (height >= wrapH && y > 0) {
y = 0;
} else if (height + y < wrapH) {
y = wrapH - height;
}
} else {
const abs = Math.abs(width / 2 - height / 2);
let X = 0;
let Y = 0;
if (imgWidth.value < imgHeight.value) {
X = x - abs;
Y = y + abs;
} else {
X = x + abs;
Y = y - abs;
}
if (height <= wrapW) {
x = (wrapW - width) / 2;
} else if (height >= wrapW && X > 0) {
if (imgWidth.value < imgHeight.value) {
x = +abs;
} else {
x = -abs;
}
} else if (height + X <= wrapW) {
const mx = wrapW - height;
if (imgWidth.value < imgHeight.value) {
x = mx + abs;
} else {
x = mx - abs;
}
}
if (width <= wrapH) {
y = (wrapH - height) / 2;
} else if (width >= wrapH && Y > 0) {
if (imgWidth.value < imgHeight.value) {
y = -abs;
} else {
y = abs;
}
} else if (width + Y <= wrapH) {
const my = wrapH - width;
if (imgWidth.value < imgHeight.value) {
y = my - abs;
} else {
y = my + abs;
}
}
}
imgScale.value = rR;
position.left = x + "px";
position.top = y + "px";
imgWidth.value = width;
imgHeight.value = height;
};


3、缩小​

/**
* @description: 缩小
* @param {*}
* @return {*}
*/
const decrease = () => {
const image = imageArr[index.value];
if (imgScale.value == 0.01) {
return;
}
let rR = imgScale.value - 0.1;
if (rR < 0.01) {
rR = 0.01;
}
const width = image.width * rR;
const height = image.height * rR;
const [wrapW, wrapH] = getRect();
// 判断鼠标是否在图片上
let x = (1 - rR) * startPointX.value + (pos.x - startPointX.value);
let y = (1 - rR) * startPointY.value + (pos.y - startPointY.value);
if (rotateIndex.value % 2 == 0) {
// console.log(height, wrapW, wrapH, width, x, y);
if (width <= wrapW) {
x = (wrapW - width) / 2;
} else if (width >= wrapW && x > 0) {
x = 0;
} else if (width + x < wrapW) {
x = wrapW - width;
}
if (height <= wrapH) {
y = (wrapH - height) / 2;
} else if (height >= wrapH && y > 0) {
y = 0;
} else if (height + y < wrapH) {
y = wrapH - height;
}
} else {
const abs = Math.abs(width / 2 - height / 2);
let X = 0;
let Y = 0;
if (imgWidth.value < imgHeight.value) {
X = x - abs;
Y = y + abs;
} else {
X = x + abs;
Y = y - abs;
}
if (height <= wrapW) {
x = (wrapW - width) / 2;
} else if (height >= wrapW && X > 0) {
if (imgWidth.value < imgHeight.value) {
x = +abs;
} else {
x = -abs;
}
} else if (height + X <= wrapW) {
const mx = wrapW - height;
if (imgWidth.value < imgHeight.value) {
x = mx + abs;
} else {
x = mx - abs;
}
}
if (width <= wrapH) {
y = (wrapH - height) / 2;
} else if (width >= wrapH && Y > 0) {
if (imgWidth.value < imgHeight.value) {
y = -abs;
} else {
y = abs;
}
} else if (width + Y <= wrapH) {
const my = wrapH - width;
if (imgWidth.value < imgHeight.value) {
y = my - abs;
} else {
y = my + abs;
}
}
}
imgScale.value = rR;
position.left = x + "px";
position.top = y + "px";
imgWidth.value = width;
imgHeight.value = height;
};


4、移动

const imageV: any = document.getElementById("imgSrc");
imageV.onmousedown = (e: any) => {
e.preventDefault();
e.target.setAttribute("draggable", true);
e.target.style.cursor = "pointer";
const startX = e.clientX;
const startY = e.clientY;
const left = position.left;
const leftX = Number(left.substring(0, left.indexOf("px")));
const top = position.top;
const topX = Number(top.substring(0, top.indexOf("px")));
imageV.onmouseup = () => {
e.target.setAttribute("draggable", false);
e.target.style.cursor = "default";
imageV.onmouseup = null;
imageV.mouseleave = null;
imageV.onmousemove = null;
};
imageV.onmouseleave = () => {
e.target.setAttribute("draggable", false);
e.target.style.cursor = "default";
imageV.onmouseup = null;
imageV.mouseleave = null;
imageV.onmousemove = null;
};
imageV.onmousemove = (event: any) => {
const [w, h] = getRect();
const moveX = event.clientX;
const moveY = event.clientY;
let moveLeft = leftX + (moveX - startX);
let moveTop = topX + (moveY - startY);
const abs = Math.abs(imgWidth.value / 2 - imgHeight.value / 2);
let leftT = 0;
let topT = 0;
if (rotateIndex.value % 2 !== 0) {
if (imgHeight.value <= w && imgWidth.value <= h) {
return;
}
if (imgWidth.value < imgHeight.value) {
leftT = leftX - abs + (moveX - startX);
topT = topX + abs + (moveY - startY);
} else {
leftT = leftX + abs + (moveX - startX);
topT = topX - abs + (moveY - startY);
}
if (leftT > 0) {
if (imgHeight.value <= w) {
moveLeft = leftX;
} else {
moveLeft = abs;
}
} else {
if (imgHeight.value <= w) {
moveLeft = leftX;
} else if (leftT + imgHeight.value < w) {
const ml = w - imgHeight.value;
if (imgWidth.value < imgHeight.value) {
moveLeft = ml + abs;
} else {
moveLeft = ml - abs;
}
}
}
if (topT > 0) {
if (imgWidth.value <= h) {
moveTop = topX;
} else {
moveTop = abs;
}
} else {
if (imgWidth.value <= h) {
moveTop = topX;
} else if (topT + imgWidth.value < h) {
// console.log(h - imgWidth.value);
const mT = h - imgWidth.value;
if (imgWidth.value < imgHeight.value) {
moveTop = mT - abs;
} else {
moveTop = mT + abs;
}
}
}
} else {
if (imgWidth.value <= w && imgHeight.value <= h) {
return;
}
if (moveLeft > 0) {
if (imgWidth.value <= w) {
moveLeft = leftX;
} else {
moveLeft = 0;
}
} else {
if (imgWidth.value <= w) {
moveLeft = leftX;
} else if (moveLeft + imgWidth.value < w) {
moveLeft = w - imgWidth.value;
}
}
if (moveTop > 0) {
if (imgHeight.value <= h) {
moveTop = topX;
} else {
moveTop = 0;
}
} else {
if (imgHeight.value <= h) {
moveTop = topX;
} else if (moveTop + imgHeight.value < h) {
moveTop = h - imgHeight.value;
}
}
}
position.left = moveLeft + "px";
position.top = moveTop + "px";
};
};


5、旋转

/**
* @description: 旋转
* @param {*}
* @return {*}
*/
const rotate = () => {
rotateIndex.value = (rotateIndex.value + 1) % 4;
const deg = rotateIndex.value * 90;
const [w, h] = getRect();
const oW = imageArr[index.value].width;
const oH = imageArr[index.value].height;
let nowW = oW;
let nowH = oH;
let scale = 1;
// console.log(deg);
switch (deg) {
case 180:
case 0: {
if (oH > h) {
scale = h / oH;
nowH = h;
nowW = oW * scale;
}
if (nowW > w) {
scale = w / oW;
nowW = w;
nowH = oH * scale;
}
imgHeight.value = nowH;
imgWidth.value = nowW;
imgScale.value = scale;
// 位置
position.top = h / 2 - nowH / 2 + "px";
position.left = w / 2 - nowW / 2 + "px";
position.transform = `rotate(${-deg}deg)`;
break;
}
case 90:
case 270: {
if (oW > h) {
scale = h / oW;
nowW = h;
nowH = oH * scale;
}
if (nowH > w) {
scale = w / oH;
nowH = w;
nowW = oW * scale;
}
imgHeight.value = nowH;
imgWidth.value = nowW;
imgScale.value = scale;
// 位置
position.top = h / 2 - nowH / 2 + "px";
position.left = w / 2 - nowW / 2 + "px";
position.transform = `rotate(${-deg}deg)`;
break;
}
}
};


总结

总的来说,通过 electron 实现图片查看器还是比较方便快捷的,主要难点在于坐标计算上,同时还需要注意边界值的情况,大家有兴趣也可以动手尝试一下!


给大家分享更多 electron 实战中的点滴,如果大家对此感兴趣,欢迎各位关注、留言,大家的支持就是我的动力!