前言
早年在开发外部技术论坛时,为了实现一种流程的图片浏览体验,基于 Vue 开发了一个图片查看器组件,简略梳理了实现思路,心愿能给大家提供一下帮忙。
先来看看效果图:
从交互上来说内容很简略,点击页面上的图片,从图片以后的地位弹出图片浮动层,以便达到图片浏览的目标。
原理剖析
依据点击事件,获得点击的图片元素
使以后图片元素不可见(通过 visibility 或者 opacity)
创立遮照层
在图片元素以后地位创立等同大小的图片元素
创立动画将图片放大至适合的尺寸(更新地位、比例)
思路清晰了,实现起来也就不难了。
实现计划
因为最终目标就是在 Vue 我的项目中应用,所以以下计划就间接封装成 Vue 组件了。
图片查看器根本构造
图片查看器组件的视图构造很简略:
<template>
<transition>
<div v-if="visible" class="image-viewer">
<img class="image" :src="src" />
</div>
</transition>
</template>
<script>
export default {
data() {
return {
el: null, // 鼠标点中的图片元素
visible: false, // 图片查看器是否可见
};
},
computed: {src() {return this.el?.src;},
},
methods: {show(el) {
el.style.opacity = 0; // 暗藏源图片
this.el = el;
this.visible = true;
},
},
};
</script>
复制代码
简略解析一下:
transition:外层嵌套 transition 组件,能够十分不便咱们后续做图片位移、缩放的动画成果
.image-viewer:根元素用于搁置图片元素,还充当了遮照层的作用
.image:点击图片后展现的浮动图片就是这个元素,之后操作都在这个图片上
show(el):点击图片后会调用这个办法,将图片元素传入到该组件中,并将图片查看器显示进去
款式也相当简略,绘制一个半透明的遮照很简略的动画:
<style lang=”less” scoped>
.image-viewer {
position: fixed;
z-index: 99;
top: 0;
left: 0;
height: 100%;
width: 100%;
background: rgba(0, 0, 0, 0.6);
cursor: move;
transition: background 0.3s;
/* 渐入渐出的动画成果 */
&.v-enter,
&.v-leave-to {background: rgba(0, 0, 0, 0);
}
.image {
position: absolute;
user-select: none;
transform-origin: center;
will-change: transform, top, left;
}
}
</style>
复制代码
从原处弹出图片并放大
咱们的图片查看器曾经能把图片展现进去了,接下来使如何使查看器中的指标图片元素(.image)从源图片元素(el)中弹进去。
依据 Vue 数据驱动的思维,其本质就是通过利用起始数据和完结数据,来达到图片从原处弹出,放到至适合的尺寸的动画成果。这里通过保护一份维度数据 dimension,依据维度数据来计算指标图片元素的款式 style。
export default {
data() {
return {
// ...
// 图片维度信息
dimension: null,
};
},
computed: {
// ...
// 指标图片款式
style() {if (!this.dimension) return null;
const {
scale,
size: {width, height},
position: {top, left},
translate: {x, y},
} = this.dimension;
return {width: `${width}px`,
height: `${height}px`,
top: `${top}px`,
left: `${left}px`,
transform: `translate3d(${x}px, ${y}px, 0) scale(${scale})`,
transition: 'transform 0.3s',
};
},
},
methods: {
show(el) {
el.style.opacity = 0;
this.el = el;
this.visible = true;
this.dimension = getDimension(el); // 从源图片获取维度数据
},
},
};
复制代码
这里的 dimension 蕴含了图片元素的以下信息:
数据 形容
size: {width, height} 图片理论宽高
position: {top, left} 图片相对地位
scale 图片元素理论尺寸与图片天然尺寸的比值,用于后续图片缩放动画
translate: {x, y} 图片位移地位,默认为 0,用于后续图片缩放位移动画
获取图片元素的 dimension 的办法:
const getDimension = (el) => {
const {naturalWidth, naturalHeight} = el;
const rect = el.getBoundingClientRect();
// 放大后的图片宽高
const height = clamp(naturalHeight, 0, window.innerHeight * 0.9);
const width = naturalWidth * (height / naturalHeight);
return {
size: {width, height},
position: {left: rect.left + (rect.width - width) / 2,
top: rect.top + (rect.height - height) / 2,
},
scale: rect.height / height,
translate: {x: 0, y: 0},
};
};
复制代码
当初,咱们曾经在源图片的为止笼罩了一张一样尺寸的图片,而后是依据屏幕大小将图片放大至适合的尺寸。
咱们只须要批改 show 局部的逻辑,使其在下一刻更新 dimension 的值:
export default {
// …
methods: {
show(el) {
el.style.opacity = 0;
this.el = el;
this.dimension = getDimension(el);
this.visible = true;
doubleRaf(() => {const { innerWidth, innerHeight} = window;
const {size, position} = this.dimension;
this.dimension = {
...this.dimension,
// 批改比例为 1,即放大之后的比例
scale: 1,
// 计算位移,使图片放弃居中
translate: {x: (innerWidth - size.width) / 2 - position.left,
y: (innerHeight - size.height) / 2 - position.top,
},
};
});
},
},
};
复制代码
这里应用了 doubleRaf(即 Double RequestAnimationFrame),期待浏览器从新渲染再执行:
const doubleRaf = (cb) => {
requestAnimationFrame(() => {
requestAnimationFrame(cb);
});
};
复制代码
这样一来,图片放大的动画成果就进去了。
同理,当咱们点击遮照层触发敞开图片浏览器时,应使图片放大并回到原来的地位:
<template>
<transition @afterLeave=”hidden”>
<div v-if="visible" class="image-viewer" @mouseup="hide">
<img class="image" :style="style" :src="src" />
</div>
</transition>
</template>
<script>
export default {
// ...
methods: {
// 暗藏
hide() {
// 从新获取源图片的 dimension
this.dimension = getDimension(this.el);
this.visible = false;
},
// 齐全暗藏之后
hidden() {
this.el.style.opacity = null;
document.body.style.overflow = this.bodyOverflow;
this.$emit('hidden');
},
},
};
</script>
复制代码
当初,图片查看器组件局部的逻辑根本实现了。
封装为函数调用
为了让这个组件更加不便易用,咱们将其封装成函数调用形式:
import Vue from ‘vue’;
import ImageViewer from ‘./ImageViewer.vue’;
const ImageViewerConstructor = Vue.extend(ImageViewer);
function showImage(el) {
// 创立组件实例,并调用组件的 show 办法
let instance = new ImageViewerConstructor({
el: document.createElement('div'),
mounted() {this.show(el);
},
});
// 将组件根元素插入到 body
document.body.appendChild(instance.$el);
// 销毁函数:移除根元素,销毁组件
function destroy() {
if (instance && instance.$el) {document.body.removeChild(instance.$el);
instance.$destroy();
instance = null;
}
}
// 组件动画完结时,执行销毁函数
instance.$once(‘hidden’, destroy);
// 如果是在某个父元素调用了该办法,当父元素被销毁时(如切换路由),也执行销毁函数
if (this && ‘$on’ in this) {
this.$on('hook:destroyed', destroy);
}
}
showImage.install = (VueClass) => {
VueClass.prototype.$showImage = showImage;
};
export default showImage;
复制代码
到这里,组件的封装也实现了,能够在任何中央欢快地应用了:
// ========== main.js ==========
import Vue from ‘vue’;
import VueImageViewer from ‘@bigo/vue-image-viewer’;
Vue.use(VueImageViewer);
// ========== App.vue ==========
<template>
<div class=”app”>
<img src="http://wiki.bigo.sg:8090/download/attachments/441943984/preview.gif?version=1&modificationDate=1622463742000&api=v2" />
</div>
</template>
<script>
export default {
methods: {
onImageClick(e) {this.$showImage(e.target);
},
},
};
</script>
复制代码
总结
尽管性能比拟简陋,但次要的图片浏览性能曾经实现了,相比大多数的图片浏览插件,在用户体验方面要晦涩很多,能让用户从视觉上有更加平滑的适度,提供更好的沉迷式浏览体验。
还有许多想法还没有实现,例如在浏览过程中拖拽图片挪动、鼠标滚轮实现缩放、手势操作优化、挪动端体验优化、多图片浏览等等,就留给大家去思考了。
欢送大家留言探讨,祝工作顺利、生存欢快!
如果你感觉这篇文章对你有点用的话,麻烦请给咱们的开源我的项目点点 star: http://github.crmeb.net/u/defu 不胜感激!