この記事は最初に公開されました: https://github.com/bigo-frontend/blog/ フォローと再投稿を歓迎します。
Vue フローティング ポップアップの画像ビューア コンポーネントをカプセル化します。
序文
初期の社内テクノロジー フォーラムの開発では、プロセスの画像閲覧エクスペリエンスを実現するために、Vue に基づいて画像ビューア コンポーネントを開発し、実装のアイデアを簡単に整理して、皆さんの役に立つことを期待していました。
まずレンダリングを見てみましょう。
インタラクションに関しては、コンテンツは非常にシンプルで、ページ上の画像をクリックすると、画像の現在の位置から画像フローティング レイヤーがポップアップし、画像閲覧の目的を達成します。
原理分析
- clickイベントに応じて、クリックされた画像要素を取得します
- 現在の画像要素を非表示にします (可視性または不透明度によって)
- シェードレイヤーを作成する
- 画像要素の現在の位置と同じサイズの画像要素を作成します
- 画像を適切なサイズに拡大するアニメーションを作成します(位置、スケールを更新)
一度考え方が明確になったら、それを実現するのは難しくありません。
実行計画
最終的な目標は 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>
簡単な分析:
- トランジション: 外側のレイヤーはトランジション コンポーネントをネストします。これは、将来画像の移動やスケーリングのアニメーション効果を行うのに非常に便利です。
- .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
画像要素に関する次の情報が含まれます。
データ | 説明する |
---|---|
サイズ: { 幅、高さ } | 画像の実際の幅と高さ |
位置: { 上、左 } | 画像の絶対位置 |
規模 | 画像要素の実際のサイズと画像の自然なサイズの比率。後続の画像スケーリング アニメーションに使用されます。 |
翻訳: { 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) {
![preview](https://user-images.githubusercontent.com/8649710/122009053-46478400-cdec-11eb-986c-134763e15a5d.gif)
![preview](https://user-images.githubusercontent.com/8649710/122009110-55c6cd00-cdec-11eb-8fa2-6f4e9f479a1a.gif)
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>
要約する
機能は比較的シンプルですが、主要な画像ブラウジング機能が実現されており、ほとんどの画像ブラウジングプラグインと比較して、ユーザーエクスペリエンスがはるかにスムーズであり、ユーザーはよりスムーズな視覚的遷移を持ち、より優れた没入感のあるブラウジングエクスペリエンスを提供できます。
ブラウジング中に写真をドラッグ&ドロップして移動する、マウスホイールでズームする、ジェスチャー操作を最適化する、モバイル端末のエクスペリエンスを最適化する、複数の写真を閲覧するなど、実現されていないアイデアはまだたくさんあります。について考える。
議論するメッセージを残してくださる皆様を歓迎します。スムーズな仕事と幸せな生活をお祈りしています。
私は bigo のフロントエンドです。次号でお会いしましょう。