感谢大家的点赞和转发。试用期指导,项目开发,简历优化,毕业设计/论文,欢迎添加本人微 信。
新人作者,欢迎关注和收藏👏🏻👏🏻
1.效果图
2.代码
<template>
<div
ref="container"
class="image-viewer-container"
:style="{ width: containerWidthStyle, height: containerHeightStyle }"
>
<div
ref="imageWrapper"
class="image-wrapper"
:style="{
transform: `scale(${scale}) translate(${translateX}px, ${translateY}px)`,
cursor: isDragging
? 'grabbing'
: scale > 1 ||
(scale === props.initialScale &&
(translateX !== 0 || translateY !== 0))
? 'grab'
: 'default'
}"
@mousedown="startDrag"
@mousemove="handleDrag"
@mouseup="endDrag"
@mouseleave="endDrag"
@wheel.prevent="handleWheel"
>
<img
ref="image"
:src="src"
:alt="alt"
class="image"
@load="handleImageLoad"
/>
<!-- <div class="overlay"></div> -->
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount, computed } from "vue";
const props = defineProps({
src: {
type: String,
required: true
},
alt: {
type: String,
default: ""
},
initialScale: {
type: Number,
default: 1
},
maxScale: {
type: Number,
default: 30
},
minScale: {
type: Number,
default: 1
},
zoomStep: {
type: Number,
default: 0.1
},
containerWidth: {
type: [Number, String],
default: "100%" // 默认宽度,可以是数字或带单位的字符串
},
containerHeight: {
type: [Number, String],
default: "100%" // 默认高度,可以是数字或带单位的字符串
},
autoSize: {
type: Boolean,
default: false // 是否根据图片高度自适应
}
});
const container = ref(null);
const imageWrapper = ref(null);
const image = ref(null);
const scale = ref(props.initialScale);
const translateX = ref(0);
const translateY = ref(0);
const isDragging = ref(false);
const startDragX = ref(0);
const startDragY = ref(0);
const rawContainerWidth = ref(props.containerWidth);
const rawContainerHeight = ref(props.containerHeight);
const containerNumWidth = ref(
typeof props.containerWidth === "number" ? props.containerWidth : 300
);
const containerNumHeight = ref(
typeof props.containerHeight === "number" ? props.containerHeight : 300
);
const imageWidth = ref(0);
const imageHeight = ref(0);
/**
* 计算容器宽度样式
*/
const containerWidthStyle = computed(() => {
if (typeof rawContainerWidth.value === "number") {
return `${rawContainerWidth.value}px`;
}
return rawContainerWidth.value;
});
/**
* 计算容器高度样式
*/
const containerHeightStyle = computed(() => {
if (typeof rawContainerHeight.value === "number") {
return `${rawContainerHeight.value}px`;
}
return rawContainerHeight.value;
});
const handleImageLoad = () => {
imageWidth.value = image.value.naturalWidth;
imageHeight.value = image.value.naturalHeight;
if (props.autoSize) {
rawContainerWidth.value = imageWidth.value;
rawContainerHeight.value = imageHeight.value;
containerNumWidth.value = imageWidth.value;
containerNumHeight.value = imageHeight.value;
}
resetPosition();
};
const updateContainerSize = () => {
if (!props.autoSize) {
rawContainerWidth.value = props.containerWidth;
rawContainerHeight.value = props.containerHeight;
if (typeof props.containerWidth === "number") {
containerNumWidth.value = props.containerWidth;
}
if (typeof props.containerHeight === "number") {
containerNumHeight.value = props.containerHeight;
}
// 如果容器有尺寸,需要更新实际数值宽高,用于计算拖拽边界
if (container.value) {
const rect = container.value.getBoundingClientRect();
if (typeof props.containerWidth !== "number") {
containerNumWidth.value = rect.width;
}
if (typeof props.containerHeight !== "number") {
containerNumHeight.value = rect.height;
}
}
}
};
const resetPosition = () => {
scale.value = props.initialScale;
translateX.value = 0;
translateY.value = 0;
};
const startDrag = e => {
// if (
// scale.value > 1 ||
// (scale.value === props.initialScale &&
// (translateX.value !== 0 || translateY.value !== 0))
// ) {
isDragging.value = true;
startDragX.value = e.clientX;
startDragY.value = e.clientY;
e.preventDefault();
e.stopPropagation();
document.addEventListener("mousemove", handleDrag);
document.addEventListener("mouseup", endDrag);
// }
};
const handleDrag = e => {
if (isDragging.value) {
e.preventDefault();
e.stopPropagation();
const dx = e.clientX - startDragX.value;
const dy = e.clientY - startDragY.value;
const newX = translateX.value + dx;
const newY = translateY.value + dy;
const maxX = (scale.value * imageWidth.value - containerNumWidth.value) / 2;
const maxY =
(scale.value * imageHeight.value - containerNumHeight.value) / 2;
translateX.value = Math.max(-maxX, Math.min(maxX, newX));
translateY.value = Math.max(-maxY, Math.min(maxY, newY));
startDragX.value = e.clientX;
startDragY.value = e.clientY;
}
};
const endDrag = e => {
if (isDragging.value) {
if (e) {
e.preventDefault();
e.stopPropagation();
}
isDragging.value = false;
document.removeEventListener("mousemove", handleDrag);
document.removeEventListener("mouseup", endDrag);
}
};
const handleWheel = e => {
e.preventDefault();
e.stopPropagation();
if (e.deltaY < 0) {
scale.value = Math.min(props.maxScale, scale.value + props.zoomStep);
} else {
scale.value = Math.max(props.minScale, scale.value - props.zoomStep);
}
// 不调用 resetPosition 函数,暂时隐藏该功能
if (
scale.value === props.initialScale &&
translateX.value === 0 &&
translateY.value === 0
) {
resetPosition();
}
// 缩放时不改变图片位置,保持上一次的 translateX 和 translateY 值
};
onMounted(() => {
updateContainerSize();
window.addEventListener("resize", updateContainerSize);
});
onBeforeUnmount(() => {
window.removeEventListener("resize", updateContainerSize);
endDrag();
});
</script>
<style scoped>
.image-viewer-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
overflow: hidden;
touch-action: pan-y;
user-select: none;
}
.image-wrapper {
position: relative;
display: flex;
align-items: flex-start;
justify-content: center;
width: 100%;
height: auto;
overflow: visible;
cursor: pointer;
transform-origin: center top;
will-change: transform;
}
.image {
display: block;
width: auto;
max-width: 100%;
height: auto;
user-select: none;
object-fit: contain;
-webkit-user-drag: none;
}
</style>
3.文档
图片查看器组件文档
一、组件概述
该图片查看器组件是一个基于 Vue 开发的图片展示工具,允许用户对图片进行缩放和拖拽操作。组件提供了灵活的配置选项,如初始缩放比例、最大缩放比例、缩放步长等,同时支持根据图片高度自适应容器大小。
二、组件结构
外层容器:image-viewer-container
类的 div 元素,作为整个组件的容器,设置了固定的宽度和高度。
图片包装器:image-wrapper
类的 div 元素,用于包裹图片,通过 CSS 变换属性 transform
实现缩放和位移。
图片元素:image
类的 img 元素,展示实际的图片内容。
遮罩层:overlay
类的 div 元素,覆盖在图片上方,提供视觉上的效果。
三、组件属性
属性名 | 类型 | 默认值 | 描述 |
---|---|---|---|
src | String | 无 | 图片的源地址,必填项。 |
alt | String | "" | 图片的替代文本。 |
initialScale | Number | 1 | 图片的初始缩放比例。 |
maxScale | Number | 3 | 图片的最大缩放比例。 |
minScale | Number | 1 | 图片的最小缩放比例。 |
zoomStep | Number | 0.1 | 每次缩放的步长。 |
containerWidth | Number | 300 | 容器的宽度,单位为像素。 |
containerHeight | Number | 300 | 容器的高度,单位为像素。 |
autoSize | Boolean | false | 是否根据图片高度自适应容器大小,若为 true,则容器大小会根据图片高度进行调整。 |
四、组件方法
handleImageLoad:在图片加载完成时调用,用于获取图片的自然宽度和高度,并根据 autoSize
属性调整容器大小。
updateContainerSize:根据 autoSize
属性更新容器的宽度和高度。
resetPosition:将图片的缩放比例和位移值重置为初始状态。
startDrag:在鼠标按下时调用,开始拖拽操作,记录鼠标按下时的位置,并添加 mousemove
和 mouseup
事件监听器。
handleDrag:在鼠标移动时调用,根据鼠标移动的距离更新图片的位移值,并确保位移值在可接受的范围内。
endDrag:在鼠标抬起或离开时调用,结束拖拽操作,移除 mousemove
和 mouseup
事件监听器。
handleWheel:在鼠标滚轮滚动时调用,根据滚轮的方向调整图片的缩放比例,并确保缩放比例在可接受的范围内。
五、事件处理
@mousedown:在鼠标按下时触发 startDrag
方法。
@mousemove:在鼠标移动时触发 handleDrag
方法。
@mouseup:在鼠标抬起时触发 endDrag
方法。
@mouseleave:在鼠标离开时触发 endDrag
方法。
@wheel.prevent:在鼠标滚轮滚动时触发 handleWheel
方法,并阻止默认的滚轮事件。
六、样式说明
image-viewer-container:设置为相对定位,溢出隐藏,禁止触摸操作和用户选择。
image-wrapper:设置为相对定位,变换原点为中心,设置 will-change: transform
以优化性能。
image:设置为块级元素,宽度为 100%,高度自适应,禁止用户选择和拖拽。
overlay:设置为绝对定位,覆盖整个图片区域,背景颜色为半透明黑色,指针事件为 none。
七、生命周期钩子函数
onMounted:在组件挂载完成时调用,更新容器大小,并添加 resize
事件监听器。
onBeforeUnmount:在组件卸载前调用,移除 resize
事件监听器,并结束拖拽操作。
感谢大家的点赞和转发。试用期指导,项目开发,简历优化,毕业设计/论文,欢迎添加本人微 信。
新人作者,欢迎关注和收藏👏🏻👏🏻
觉得作者写的不错或者心情愉悦的老板也可以投币打赏,感谢观看,希望能给大家带来帮助