在项目中遇到这样一个需求,需要实现一组图片的预览,要求图片可以放缩、拖拽。并且在点击左侧目录切换到下一张图片时使得初始时居中。
实现效果如下:页面左侧为图片集预览菜单,通过点击左侧缩略图,右侧出现该图片的预览内容。核心功能在于右侧预览组件,该组件允许用户使用鼠标移动和放大/缩小图像。图像最初位于其父容器的中心,可以分别通过单击、拖动或滚动鼠标滚轮来更改其位置和缩放级别。
当时遇到的问题主要有两个,一是对react封装的鼠标事件不熟悉,二是对于切换到下张图片时如何获取图片的原始宽高,进而将其居中显示。
组件接口定义:
interface ImageDraggerProps {
selectedItem: Photo;
}
Photo是后端定义的单个图片数据类型。从上层组件中传给图片预览组件。
export interface Photo {
/**
* 图片名称
*/
name: string;
/**
* 图片地址
*/
url: string;
}
首先是如何在切换图片时获取原始宽高进行下一步操作。
解决方法是结合useEffect和回调函数,在当前选中的图片发生改变时执行useEffect,在useEffect中再执行回调函数,通过回调函数获取图片宽高后改变图片的样式,这样就避免了直接在useEffect中执行函数的异步问题。
// 回调函数
const getImgNaturalStyle = (
img: HTMLImageElement,
callback: (width: number, height: number) => void,
) => {
if (img.naturalWidth) {
// modern browsers
callback(img.naturalWidth, img.naturalHeight);
} else {
// IE6/7/8
const image = new Image();
image.src = img.src;
image.onload = function () {
callback(image.width, image.height);
};
}
};
useDeepCompareEffect(() => {
const img: HTMLImageElement = document.querySelector(
`#img${selectedItem?.name.replace(/\./g, '_')}`,
);
getImgNaturalStyle(img, (width, height) => {
img.style.left = `calc(50% - ${width / 2}px)`;
img.style.top = `calc(50% - ${height / 2}px)`;
});
setZoom(1.0);
}, [selectedItem?.name]);
接下来是实现图片放缩拖拽的部分:功能分为放缩和拖拽两个部分,通过不同的函数实现。
图片拖拽的核心是move函数,它是一个事件处理程序,当用户单击图像时调用它。允许用户通过用鼠标单击和拖动图像在其父容器中移动图像。
// Move image function
const move = (e: React.MouseEvent<HTMLElement>) => {
e.preventDefault();
// 获取元素
const wrapper = document.querySelector(
`#wrapper${selectedItem?.name.replace(/\./g, '_')}`,
) as HTMLElement;
const img: any = document.querySelector(`#img${selectedItem?.name.replace(/\./g, '_')}`);
const x = e.pageX - img.offsetLeft;
const y = e.pageY - img.offsetTop;
// 添加鼠标移动事件
wrapper.addEventListener('mousemove', moveImg);
function moveImg(p: any) {
img.style.left = p.pageX - x + 'px';
img.style.top = p.pageY - y + 'px';
}
// 添加鼠标抬起事件,鼠标抬起,将事件移除
img.addEventListener('mouseup', function () {
wrapper.removeEventListener('mousemove', moveImg);
});
// 鼠标离开父级元素,把事件移除
wrapper.addEventListener('mouseout', function () {
wrapper.removeEventListener('mousemove', moveImg);
});
};
图片缩放的核心是rollImg函数,当用户滚动鼠标滚轮而光标在图像的父容器上时调用该函数,即给图片的外部div设置属性onWheel={rollImg}:
// Roll image function
const rollImg = (e: React.WheelEvent<HTMLElement>) => {
/* 获取当前页面的缩放比 若未设置zoom缩放比,则为默认100%,即1,原图大小 */
let scale: number = zoom;
/* 获取滚轮滚动值并将滚动值叠加给缩放比zoom deltaY变动值单位为±124,其中负数表示为向上滚动,正数表示向下滚动 */
scale -= e.deltaY / 1240;
/* 最小范围 和 最大范围 的图片缩放尺度 */
if (scale >= 0.2 && scale <= 5) {
setZoom(scale);
}
return false;
};
当时还遇到一个小问题,有一张图片上传之后发现无法显示,之后发现是CSS的id选择器除了要满足唯一性之外,需要以字母开头而且之间不能有.这个符号。而那张图片的命名是pic.1。
所以对图片的id做了如下处理:
<img
id={`img${selectedItem?.name.replace(/\./g, '_')}`}
onMouseDown={move}
src={buildCosUrl(selectedItem?.url)}
draggable={true}
alt=""
className={styles.image}
style={{
WebkitTransform: `scale(${zoom})`,
left: `0px`,
top: `0px`,
}}
/>