blog

ユーザーガイドコンポーネントのvue実装

初めて要望を受けた時、こんな汎用的なものなら誰かが既に作っているはずだと思い、急いでgayhubに見に行くと、確かにありました - vue-tour。 公式エフェクトを見た後、バブルコンポーネントのよ...

Dec 1, 2020 · 7 min. read
シェア

はじめに

初めて需要を受け、一般的なことなので、誰かがすでにライブラリを構築している必要があり、右、急いでgayhubで見に行くと、確かにある - ビュー-ツアー。

公式のエフェクトを見てみると、バブルコンポーネントのような見た目で、エフェクトもapiも中々です。

しかし、我々のuiはもっとチャラチャラしているし、カスタムコンテンツもあるし、ギャップはかなり大きい。

アイデア

スクリーンマスクレイヤー上のあるエリアを強調したい場合、元の位置に全く同じスタイルのdom要素をカバーする必要があり、カスタムコンテンツはこのエリアを囲む必要があります。その場合、必要なパーツは以下の通りです:

  • ハイライトする要素の座標情報

要素自身の幅と高さ、ブラウザのビューポートの上端と左端からの距離を取得します。

  • ハイライトされた要素のコピー

ここでcloneNodeを使ってdomを操作するのは確かに適切ではありませんし、最終的なスタイルが反映されるとは限りません。

次に、html2canvas ツールライブラリを使用して、dom 要素から canvas オブジェクトを生成し、canvas toDataURL メソッドを使用して、base64 イメージを生成する必要があります。
html2canvas(document.body).then(function(canvas) {
 // base64イメージを生成する
 let img = canvas.toDataURL('image/png')
});

github.com/ht...

コンポーネントデザイン

理想的には、ハイライトされた dom 要素の base64 イメージを自動的に生成する id を渡して、元のオブジェクトのすぐ上にスタンプを押せば、カスタムコンテンツはスロット自体によって実装され、自由度が高くなります。

実装コード

  • 座標の取得
// xx.vue
//要素の垂直座標を取得する
getTop(e) {
 let offset = e.offsetTop
 if (e.offsetParent !== null) offset += this.getTop(e.offsetParent)
 return offset
},
//要素の水平座標を取得する
getLeft(e) {
 let offset = e.offsetLeft
 if (e.offsetParent !== null) offset += this.getLeft(e.offsetParent)
 return offset
},
getImgBase64 (idStr) {
 let _el = document.querySelector(`#${idStr}`)
 let style = window.getComputedStyle(_el)
 //  
 this.width = parseInt(style.width)
 //  
 this.height = parseInt(style.height)
 // トップ距離
 this.offsetTop = vm.getTop(_el)
 // 左の距離
 this.offsetLeft = vm.getLeft(_el)
}

HDスクリーンショットの生成

// xx.vue
getImgBase64(idStr) {
 let vm = this
 let _canvas = document.createElement('canvas')
 let _el = document.querySelector(`#${idStr}`)
 let style = window.getComputedStyle(_el)
 this.width = parseInt(style.width)
 this.height = parseInt(style.height)
 let context = _canvas.getContext('2d')
 //以下のコードは、画面の解像度に応じて、キャンバスの幅と高さを設定し、マルチスクリーンに対応した高精細なイメージを得るためのものだ。
 // 画面のデバイスピクセル比
 let devicePixelRatio = window.devicePixelRatio || 2
 // ブラウザがキャンバスをレンダリングし、キャンバスの情報をピクセル比率で保存する。
 let backingStoreRatio =
 context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1
 // canvas実際のレンダリング倍率は
 let ratio = devicePixelRatio / backingStoreRatio
 _canvas.width = this.width * ratio
 _canvas.height = this.height * ratio
 _canvas.style.width = this.width + 'px'
 _canvas.style.height = this.height + 'px'
 html2canvas(_el, {
 canvas: _canvas
 }).then(function (canvas) {
 // 写真はこちら
 let imgData = canvas.toDataURL('image/png')
 })
 }

このメソッドを実行する前に、親コンポーネントの非同期データが終了し、domが更新されるまで待つ必要があることに注意してください。

this.$nextTick(() =>{
 this.getImgBase64('id')
})

解決すべき問題

  • ブラウザが伸びると、ハイライト領域がその動きに追従します。
created() {
 window.addEventListener('resize', this.resetPos)
},
beforeDestroy() {
 window.removeEventListener('resize', this.resetPos)
},
methods: {
 resetPos() {
 let vm = this
 let _el = document.querySelector(`#${vm.idStr}`)
 let style = window.getComputedStyle(_el)
 this.width = parseInt(style.width)
 this.height = parseInt(style.height)
 this.offsetTop = vm.getTop(_el)
 this.offsetLeft = vm.getLeft(_el)
 }
}
  • ユーザーガイドラインが表示される場合

操作時間をローカルにキャッシュし、1週間など適切な間隔を独自に決定することで、操作できます。

  • 親コンポーネントの呼び出しを容易にする方法

表示のタイミングは、コンポーネント内のopenメソッドとcloseメソッドで制御します。

コンポーネントのコード一式

<template>
 <section class="tips-dialog" v-show="showBtn">
 <span class="bg"></span>
 <div class="target" :style="posStyle">
 <img v-imgErr @click="close" :src="domImg" />
 <div class="content" :style="`${pos === 'left' ? 'right' : 'left'}:${width}px`">
 <slot name="content"></slot>
 </div>
 </div>
 </section>
</template>
<script>
import html2canvas from 'html2canvas'
export default {
 name: 'tips-dialog',
 data() {
 return {
 // 要素ノードのスクリーンショット
 domImg: '',
 // スイッチを表示する
 showBtn: false,
 offsetTop: 0,
 offsetLeft: 0,
 width: 0,
 height: 0
 }
 },
 props: {
 idStr: {
 type: String,
 required: true
 },
 pos: {
 type: String,
 default: () => {
 return 'left'
 }
 }
 },
 watch: {
 idStr() {
 this.getImgBase64()
 }
 },
 computed: {
 posStyle() {
 return `left:${this.offsetLeft}px;top:${this.offsetTop}px;width:${this.width}px;height:${this.height}px;`
 }
 },
 created() {
 window.addEventListener('resize', this.resetPos)
 },
 beforeDestroy() {
 window.removeEventListener('resize', this.resetPos)
 },
 methods: {
 //要素の垂直座標を取得する
 getTop(e) {
 let offset = e.offsetTop
 if (e.offsetParent !== null) offset += this.getTop(e.offsetParent)
 return offset
 },
 //要素の水平座標を取得する
 getLeft(e) {
 let offset = e.offsetLeft
 if (e.offsetParent !== null) offset += this.getLeft(e.offsetParent)
 return offset
 },
 open() {
 !this.domImg && this.getImgBase64()
 if (this.domImg) {
 this.showBtn = true
 }
 },
 close() {
 this.showBtn = false
 this.$emit('close')
 },
 resetPos() {
 let vm = this
 let _el = document.querySelector(`#${vm.idStr}`)
 let style = window.getComputedStyle(_el)
 this.width = parseInt(style.width)
 this.height = parseInt(style.height)
 this.offsetTop = vm.getTop(_el)
 this.offsetLeft = vm.getLeft(_el)
 },
 getImgBase64() {
 let vm = this
 let _canvas = document.createElement('canvas')
 let _el = document.querySelector(`#${vm.idStr}`)
 let style = window.getComputedStyle(_el)
 let w = parseInt(style.width)
 this.width = w
 let h = parseInt(style.height)
 this.height = h
 this.offsetTop = vm.getTop(_el)
 this.offsetLeft = vm.getLeft(_el)
 //ニーズに応じて、オフセットを参照するコンテキスト・パラメータを変更することができる。
 let context = _canvas.getContext('2d')
 //以下のコードは、画面の解像度を取得し、キャンバスの幅と高さを設定して、高解像度のイメージを取得するものだ。
 // 画面のデバイスピクセル比
 let devicePixelRatio = window.devicePixelRatio || 2
 // ブラウザがキャンバスをレンダリングし、キャンバスの情報をピクセル比率で保存する。
 let backingStoreRatio =
 context.webkitBackingStorePixelRatio || context.mozBackingStorePixelRatio || context.msBackingStorePixelRatio || context.oBackingStorePixelRatio || context.backingStorePixelRatio || 1
 // canvas実際のレンダリング倍率は
 let ratio = devicePixelRatio / backingStoreRatio
 _canvas.width = w * ratio
 _canvas.height = h * ratio
 _canvas.style.width = w + 'px'
 _canvas.style.height = h + 'px'
 html2canvas(_el, {
 canvas: _canvas
 }).then(function (canvas) {
 vm.domImg = canvas.toDataURL('image/png')
 let imgEl = document.createElement('img')
 imgEl.onload = function () {
 setTimeout(() => {
 vm.showBtn = true
 }, 20)
 }
 imgEl.setAttribute('src', vm.domImg)
 })
 }
 }
}
</script>
<style lang="stylus" scoped>
.tips-dialog
 position fixed
 top 0
 left 0
 width 100%
 height 100%
 z-index 1001
 .bg
 position relative
 display block
 width 100%
 height 100%
 background rgba(0, 0, 0, 0.5)
 .target
 position absolute
 img
 display block
 width 100%
 height 100%
 cursor pointer
 .content
 position absolute
 top 50%
</style>

コンポーネントの使い方

<!-- HTML -->
<tips-dialog idStr="domId" ref="tipsDialog" pos="left">
 <template v-slot:content>
 <div class="user-explan">
 <!-- 独自のユーザーガイドコンテンツ>
 </div>
 </template>
</tips-dialog>
<!-- js -->
// asyncの最後に
this.$nextTick(() => {
 // オープンユーザガイドライン
 this.$refs.tipsDialog.open()
})
<!-- CSS -->
// スロットのコンテンツは、ポジション・オフセットのスタイルを補完することを忘れてはならない。

この時点で、比較的単純なライトボックス・ブートストラップ・コンポーネントが実装されました。しかし、まだ拡張の余地があります。

  • ユーザーガイドに複数のステップがある場合は?

すでに0から1を達成したように、1から100まで、原理は同じですが、需要がない限り、私はこのコンポーネントを拡大し続けるべきではありませんが、いくつかのより多くの髪や香。

  • カスタムコンテンツは、スタイルの書き込みを減らすために自動配置することができますか?

それはvue-tourと同じであることができ、単純なニーズを満たすために、正規の先端テンプレートとapiのセットがコンポーネント内で提供されています。

カスタマイズに関しては、要件は様々なので、自分で書いた方が良いでしょう。

Read next

RocketMQ メッセージストレージ

分散キューは高い信頼性が要求されるため、データを永続的に保存する必要があります。 Apacheは別のMQ - ActiveMQのオープンソースの下でメッセージの永続性を行うには、JDBCの方法を選択することができます簡単なxmlの設定情報を介してJDBCメッセージストレージを実現することができます。として、単一のテーブル番号の通常のリレーショナルデータベース...

Dec 1, 2020 · 6 min read