vue3を使用してAppleシステムのサイドメッセージプロンプト効果を模倣する

vue3を使用してAppleシステムのサイドメッセージプロンプト効果を模倣する

アニメーションプレビュー

最近、卒業プロジェクトに取り組んでいます。卒業プロジェクト システムに、Apple システムに似たサイド メッセージ プロンプト ボックスを追加したいと考えています。まずは効果を見てみましょう。

その他のUIライブラリ

フロントエンド開発に精通している学生であれば、このコンポーネントが Element UI では Notification、Bootstrap では Toasts と呼ばれていることに気付いたかもしれません。

始める

このコンポーネントを初めて見たとき、とてもクールだと思いました。今日は、これをどのように実装したかを段階的に紹介します。間違いや最適化できる部分があれば、コメントしてください。🥳(このコンポーネントはVue3に基づいて実装されています)

コンポーネントディレクトリ構造

乾杯

|

| -- index.js // コンポーネントを登録し、簡単に呼び出せるようにグローバル変数を定義します

|

| -- instance.js // 手動でインスタンスを作成する前後のロジック

|

| -- toasts.vue // メッセージプロンプト HTML 部分

|

| -- toastsBus.js // vue3 で $on と $emit を削除するソリューション

トースト

おおよそのDOM構造

<!-- ポップアップウィンドウ -->
<div class="トーストコンテナ">
    <!-- アイコン アイコン -->
    <テンプレート>
        ...
    </テンプレート>
    <!-- メインコンテンツ -->
    <div class="トーストコンテンツ">
        <!-- タイトルとカウントダウン -->
        <div class="toast-head">
            ...
        </div>
        <!-- 本文 -->
        <div class="toast-body">...</div>
        <!-- アクション ボタン -->
        <div class="toast-operate">
            ...
        </div>
    </div>
    <!-- 閉じる -->
    <div class="トーストを閉じる">
        <i class="fi fi-rr-cross-small"></i>
    </div>
</div>

インデックス

コンポーネントを登録し、グローバル変数を定義する

ここでコンポーネントを登録し、呼び出し用のグローバル変数を定義します。

'./instance' からトーストをインポートします
'./toasts.vue' から Toast をインポートします。

エクスポートデフォルト(アプリ)=> {
    // コンポーネントを登録します。app.component(Toast.name, Toast);
    // グローバル変数を登録し、$Toast({}) を呼び出すだけです。app.config.globalProperties.$Toast = toast;
}

インスタンス.js

インスタンスを手動でマウントする

🌟🌟🌟 記事全体の要点はこちら🌟🌟🌟

まず、コンポーネントをページに手動でマウントする方法を学びましょう。

'vue' から {createApp} をインポートします。
'./toasts' から Toasts をインポートします

const toasts = (オプション) => {
    // 親コンテナを作成します。let root = document.createElement('div');
    document.body.appendChild(ルート)
    // Toastsインスタンスを作成する let ToastsConstructor = createApp(Toasts, options)
    // 親要素をマウントする let instance = ToastsConstructor.mount(root)
    // インスタンス自体をvueに投げる
    インスタンスを返す
}
デフォルトのトーストをエクスポートします。

作成された各トーストの正しい配置

図に示すように、作成された各トーストは前のトーストの下に配置されます (ここでのギャップは 16 ピクセルです)。この効果を実現するには、既存のトーストの高さを知る必要があります。

// インスタンス.js

// ここで、現在有効なトーストを保存するための配列を定義する必要があります
インスタンス = []

const toasts = (オプション) => {
    ...
    // 作成後、インスタンスを配列に追加しますinstances.push(instance)
    
    // 高さをリセットします。let verticalOffset = 0
    // トラバースして、現在存在するトーストの高さとギャップの累積を取得します。instances.forEach(item => {
        垂直オフセット += item.$el.offsetHeight + 16
    })
    // 必要なギャップを蓄積する verticalOffset += 16
    // 現在のインスタンスの y 軸の長さを割り当てます。instance.toastPosition.y = verticalOffset
    ...
}
デフォルトのトーストをエクスポートします。

アクティブおよび時間指定シャットダウン機能を追加

まずここでビジネスを分析してみましょう:

  • 時間指定による終了: トーストの作成時に自動終了時間を指定すると、タイマーが終了すると自動的に閉じます。
  • アクティブクローズ: トーストを閉じるには閉じるボタンをクリックします。

これを基に、マウスがトースト内に入ったときにトーストの自動閉じを停止する(他のトーストには影響しません)ことや、マウスがトースト外に移動したときにトーストの自動閉じを再度有効にするなど、人間的な操作を追加できます。

<!-- toasts.vue -->
<テンプレート>
    <トランジション名="トースト" @after-leave="afterLeave" @after-enter="afterEnter">
        <div ref="container" class="toast-container" :style="toastStyle" v-show="visible" @mouseenter="clearTimer" @mouseleave="createTimer">
            ...
            <!-- 閉じる -->
            <div class="toast-close" @click="破壊">
                <i class="fi fi-rr-cross-small"></i>
            </div>
        </div>
    </トランジション>
</テンプレート>

<スクリプト>
'./toastsBus' から Bus をインポートします
'vue' から {ref、computed、onMounted、onBeforeUnmount} をインポートします。
エクスポートデフォルト{
    小道具: {
        // 自動シャットダウン時間(ミリ秒単位)
        自動クローズ: {
            タイプ: 数値、
            デフォルト: 4500
        }
    },
    セットアップ(props){
        // 表示するかどうか const visible = ref(false);  
        
        //トーストコンテナインスタンス const container = ref(null);
        // トースト自体の高さ const height = ref(0);
        
        // トーストの位置 const toastPosition = ref({
            x: 16,
            年: 16
        })
        const toastStyle = 計算された(()=>{
            戻る {
                上: `${toastPosition.value.y}px`,
                右: `${toastPosition.value.x}px`、
            }
        })
        
        //トーストID
        定数 id = ref('')
        
        //トーストがアニメーションを終えた後、function afterLeave(){
            // instance.js に閉じる必要があることを伝えます()
            バス.$emit('closed',id.value);
        }
        //トーストがアニメーションに入った後、function afterEnter(){
            高さ.値 = コンテナ.値.オフセット高さ
        }

        // タイマー const timer = ref(null);

        // マウスがトーストに入る
        関数clearTimer(){
             if(タイマー.値)
                clearTimeout(タイマー値)
        }
        //トーストからマウスを外す
        関数createTimer(){
           props.autoClose() の場合
                タイマー値 = setTimeout(() => {
                    表示値 = false
                }, props.autoClose)
            }
        }

        //破壊関数destruction(){
            表示値 = false
        }
        
        マウント時(()=>{
            タイマーを作成します。
        })

        マウント解除前に(()=>{
            if(タイマー.値)
                clearTimeout(タイマー値)
        })
        
        
        戻る {
            見える、
            容器、
            身長、
            トースト位置、
            トーストスタイル、
            id、
            休暇後、
            入力後、
            タイマー、
            クリアタイマー、
            作成タイマー、
            破壊
        }
    }
}
</スクリプト>

instance.js でトーストを閉じるロジックを分析してみましょう

  1. このトーストを survival 配列から削除し、配列を走査して、このトーストからトーストの位置を上方にシフトします。
  2. <body> から DOM 要素を削除します。
  3. インスタンスを破棄するには unmount() を呼び出します。

// インスタンス.js
'vue' から {createApp} をインポートします。
'./toasts' から Toasts をインポートします
'./toastsBus' から Bus をインポートします

インスタンス = []
シード = 1 とする

const toasts = (オプション) => {
    // インスタンスを手動でマウントする let ToastsConstructor = createApp(Toasts, options)
    インスタンスを ToastsConstructor.mount(root) にします。
    // インスタンスに一意の識別子を追加します。instance.id = id
    // インスタンスを表示する instance.visible = true
    
    ...
    
    // toasts.vue からの終了イベントをリッスンします。Bus.$on('closed', (id) => {
        // ここではすべての「closed」イベントが監視されるため、id が一致していることを確認する必要があります if (instance.id == id) {
            //削除ロジックを呼び出すremoveInstance(instance)
            // <body> 上の DOM 要素を削除します。 document.body.removeChild(root)
            //インスタンスを破棄します ToastsConstructor.unmount();
        }
    })
    
    インスタンスをプッシュする
    インスタンスを返す
}

デフォルトのトーストをエクスポートします。

// 削除ロジック const removeInstance = (instance) => {
    if (!インスタンス) 戻り値
    len = インスタンスの長さ
    // 破棄する必要があるインデックスを検索します const index = instances.findIndex(item => {
        item.id === instance.id を返します
    })
    // 配列からインスタンスを削除します。splice(index, 1)
    // 現在の配列にまだ残っているトーストがある場合は、次のトーストをトラバースして上に移動し、変位を再計算する必要があります if (len <= 1) return
    // 削除されたインスタンスの高さを取得します。const h = instance.height
    // 削除されたインスタンスの後に添え字が付けられたトーストを走査します
    (i = インデックスとします; i < len - 1; i++) {
        // 式: 生き残ったインスタンスは、削除された高さとそのギャップの高さをその y 軸オフセットから減算します。instances[i].toastPosition.y = parseInt(instances[i].toastPosition.y - h - 16)
    }
}

完全なコード

インデックス

'./instance' からトーストをインポートします
'./toasts.vue' から Toast をインポートします。

エクスポートデフォルト(アプリ)=> {
    Toast コンポーネントの名前。
    app.config.globalProperties.$Toast = トースト;
}

トーストBus.js

'tiny-emitter/instance' からエミッターをインポートします。

エクスポートデフォルト{
    $on: (...args) => エミッター.on(...args),
    $once: (...args) => エミッター.once(...args),
    $off: (...args) => エミッター.off(...args),
    $emit: (...引数) => エミッター.emit(...引数)
}

インスタンス.js

'vue' から {createApp} をインポートします。
'./toasts' から Toasts をインポートします
'./toastsBus' から Bus をインポートします

インスタンス = []
シード = 1 とする

const toasts = (オプション) => {
    // 親コンテナを作成する const id = `toasts_${seed++}`
    root を document.createElement('div') にします。
    root.setAttribute('データID', id)
    document.body.appendChild(ルート)
    ToastsConstructor = createApp(Toasts, オプション) を作成します。
    インスタンスを ToastsConstructor.mount(root) にします。
    インスタンスID = ID
    インスタンス.visible = true
        // 高さをリセットします。let verticalOffset = 0
    インスタンスごとに(アイテム => {
        垂直オフセット += item.$el.offsetHeight + 16
    })
    垂直オフセット += 16

    インスタンス.toastPosition.y = 垂直オフセット

    バス.$on('closed', (id) => {
        (インスタンスID == ID)の場合{
            インスタンスを削除します
            document.body.removeChild(ルート)
            ToastsConstructor.unmount();
        }
    })
    インスタンスをプッシュする
    インスタンスを返す
}

デフォルトのトーストをエクスポートします。

const removeInstance = (インスタンス) => {
    if (!インスタンス) 戻り値
    len = インスタンスの長さ
    const インデックス = インスタンス.findIndex(item => {
        item.id === instance.id を返します
    })
    インスタンス.splice(インデックス, 1)
    長さ<= 1の場合、戻り値
    定数 h = インスタンスの高さ
    (i = インデックスとします; i < len - 1; i++) {
        インスタンス[i].toastPosition.y = parseInt(インスタンス[i].toastPosition.y - h - 16)
    }
}

トースト

カスタマイズ可能なアイコンや画像、キャンセル閉じるボタン、自動閉じる時間の設定、自動閉じる機能の無効化などの詳細を少し追加します。

<テンプレート>
<トランジション名="トースト" @after-leave="afterLeave" @after-enter="afterEnter">
  <!-- ポップアップウィンドウ -->
  <div ref="container" class="toast-container" :style="toastStyle" v-show="visible" @mouseenter="clearTimer" @mouseleave="createTimer">
    <!-- アイコン -->
    <テンプレート v-if="type || type != 'カスタム' || type != 'img'">
        <div class="toast-icon success" v-if="type==='success'">
            <i class="fi fi-br-check"></i>
        </div>
        <div class="toast-icon warning" v-if="type==='warning'">
            ?
        </div>
        <div class="toast-icon info" v-if="type==='info'">
            <i class="fi fi-sr-bell-ring"></i>
        </div>
        <div class="toast-icon error" v-if="type==='error'">
            <i class="fi fi-br-cross-small"></i>
        </div>
    </テンプレート>
    <div :style="{'backgroundColor': customIconBackground}" class="toast-icon" v-if="type==='custom'" v-html="customIcon"></div>
    <img class="toast-custom-img" :src="customImg" v-if="type==='img'"/>
    <!-- コンテンツ -->
    <div class="トーストコンテンツ">
        <!-- ヘッド -->
        <div class="toast-head" v-if="title">
            <!-- タイトル -->
            <span class="toast-title">{{title}}</span>
            <!-- 時間 -->
            <span class="toast-countdown">{{countDown}}</span>
        </div>
        <!-- 本文 -->
        <div class="toast-body" v-if="message" v-html="message"></div>
        <!-- 操作 -->
        <div class="toast-operate">
            <a class="トーストボタンの確認" 
               :class="[{'成功':type==='成功'},
                        {'警告':type==='警告'},
                        {'情報':type==='情報'},
                        {'error':type==='error'}]">{{confirmText}}</a>
        </div>
    </div>
    <!-- 閉じる -->
    <div v-if="closeIcon" class="toast-close" @click="破壊">
        <i class="fi fi-rr-cross-small"></i>
    </div>
  </div>
  </トランジション>
</テンプレート>

<スクリプト>
'./toastsBus' から Bus をインポートします
'vue' から {ref、computed、onMounted、onBeforeUnmount} をインポートします。
エクスポートデフォルト{
    小道具: {
        タイトル: 文字列、
        閉じるアイコン: {
            タイプ: ブール値、
            デフォルト: true
        },
        メッセージ: 文字列、
        タイプ: {
            タイプ: 文字列、
            バリデータ: function(val) {
                ['success', 'warning', 'info', 'error', 'custom', 'img'].includes(val); を返します。
            }
        },
        確認テキスト: 文字列、
        カスタムアイコン: 文字列、
        customIconBackground: 文字列、
        customImg: 文字列、
        自動クローズ: {
            タイプ: 数値、
            デフォルト: 4500
        }
    },
    セットアップ(props){
        // 表示 const visible = ref(false);

        //コンテナインスタンス const container = ref(null);

        // 高さ const height = ref(0);

        // 位置 const toastPosition = ref({
            x: 16,
            年: 16
        })
        const toastStyle = 計算された(()=>{
            戻る {
                上: `${toastPosition.value.y}px`,
                右: `${toastPosition.value.x}px`、
            }
        })

        // カウントダウン const countDown = computed(()=>{
            '2秒前'を返す
        })

        定数 id = ref('')

        // 関数を終了した後 afterLeave(){
            バス.$emit('closed',id.value);
        }
        // 入力後 function afterEnter(){
            高さ.値 = コンテナ.値.オフセット高さ
        }

        // タイマー const timer = ref(null);

        // マウスが関数clearTimer(){に入る
             if(タイマー.値)
                clearTimeout(タイマー値)
        }
        //マウスアウト関数createTimer(){
           props.autoClose() の場合
                タイマー値 = setTimeout(() => {
                    表示値 = false
                }, props.autoClose)
            }
        }

        //破壊関数destruction(){
            表示値 = false
        }

        マウント時(()=>{
            タイマーを作成します。
        })

        マウント解除前に(()=>{
            if(タイマー.値)
                clearTimeout(タイマー値)
        })

        戻る {
            見える、
            トースト位置、
            トーストスタイル、
            カウントダウン、
            休暇後、
            入力後、
            クリアタイマー、
            作成タイマー、
            タイマー、
            破壊、
            容器、
            身長、
            id
        }
    }
}
</スクリプト>

<style lang="scss" スコープ>
//外部コンテナ.toast-container{
    幅: 330ピクセル;
    ボックスシャドウ: rgba(0, 0, 0, 0.1) 0px 2px 12px 0px;
    背景色: rgba(#F7F7F7, .6);
    境界線: 1px 実線 #E5E5E5;
    パディング: 14px 13px;
    zインデックス: 1001;
    位置: 固定;
    上: 0;
    右: 0;
    境界線の半径: 10px;
    背景フィルター: ぼかし(15px);
    ディスプレイ: フレックス;
    アイテムの位置を揃える: ストレッチ;
    遷移: すべて .3 秒の緩和;
    変更されます: 上、左;
}
// -  -  -  -  -  -  - アイコン -  -  -  -  -  -  - 
.toast-icon、.toast-close{
    フレックス収縮: 0;
}
.トーストアイコン{
    幅: 30ピクセル;
    高さ: 30px;
    境界線の半径: 100%;
    ディスプレイ: インラインフレックス;
    アイテムの位置を中央揃えにします。
    コンテンツの中央揃え: 中央;
}
// 正しい.toast-icon.success{
    背景色: rgba(#2BB44A, .15);
    色: #2BB44A;
}
//Exception.toast-icon.warning{
    背景色: rgba(#ffcc00, .15);
    色: #F89E23;
    フォントの太さ: 600;
    フォントサイズ: 18px;
}
// エラー.toast-icon.error{
    フォントサイズ: 18px;
    背景色: rgba(#EB2833, .1);
    色: #EB2833;
}
// 情報.トーストアイコン.info{
    背景色: rgba(#3E71F3, .1);
    色: #3E71F3;
}
// カスタム画像.toast-custom-img{
    幅: 40px;
    高さ: 40px;
    境界線の半径: 10px;
    オーバーフロー: 非表示;
    フレックス収縮: 0;
}
//  -  -  -  -  -  - - コンテンツ  -  -  -  -  - -
.トーストコンテンツ{
    パディング: 0 8px 0 13px;
    フレックス: 1;
}
// -  -  -  -  -  -  -  頭  -  -  -  -  -  -  - 
.トーストヘッド{
    ディスプレイ: フレックス;
    アイテムの位置を中央揃えにします。
    コンテンツの両端揃え: スペースの間;
}
//タイトル
.トーストタイトル{
    フォントサイズ: 16px;
    行の高さ: 24px;
    色: #191919;
    フォントの太さ: 600;
    オーバーフロー: 非表示;
    テキストオーバーフロー: 省略記号;
    空白: ラップなし;
}
// 時間
.トーストカウントダウン{
    フォントサイズ: 12px;
    色: #929292;
    行の高さ: 18.375px;
}
// -  -  -  -  -  -  -  体  -  -  -  -  - -
.トーストボディ{
    色: #191919;
    行の高さ: 21px;
    パディング上部: 5px;
}
// -  -  -  -  -  近い  -  -  - -
.トーストを閉じる{
    パディング: 3px;
    カーソル: ポインタ;
    フォントサイズ: 18px;
    幅: 24px;
    高さ: 24px;
    境界線の半径: 8px;
    ディスプレイ: インラインフレックス;
    アイテムの位置を中央揃えにします。
    コンテンツの中央揃え: 中央;
}
.トースト-close:hover{
    背景色: rgba(#E4E4E4, .5);
}
// --------- 操作 ----------
.トーストボタンの確認{
    フォントの太さ: 600;
    色: #3E71F3;
}
.toast-button-confirm:hover{
    色: #345ec9;
}
// 成功。トーストボタンの確認が成功{
    色: #2BB44A;
}
.toast-button-confirm.success:hover{
    色: #218a3a;
}
//Exception.toast-button-confirm.warning{
    色: #F89E23;
}
.toast-button-confirm.warning:hover{
    色: #df8f1f;
}
// 情報.トーストボタン確認.info{
    色: #3E71F3;
}
.toast-button-confirm.info:hover{
    色: #345ec9;
}
// エラー.toast-button-confirm.error{
    色: #EB2833;
}
.toast-button-confirm.error:hover{
    色: #c9101a;
}


/* アニメーション */
.トーストから入る、
.トーストを残す{
  変換: translateX(120%);
}
.v-離れる、
.トースト-enter-to{
  変換: translateX(00%);
}
</スタイル>

メイン.js

'vue' から {createApp} をインポートします。
'./App.vue' からアプリをインポートします。

const app = createApp(App)

'@/assets/font/UIcons/font.css' をインポートします

// トーストをインストールする
'./components/toasts' からトーストをインポートします

app.use(トースト).mount('#app') を実行します。

使用

<テンプレート>
    <button @click="clickHandle">送信</button>
</テンプレート>

<スクリプト>
'vue' から { getCurrentInstance } をインポートします。
エクスポートデフォルト{
  設定(){
    定数インスタンス = getCurrentInstance()
    関数clickHandle(){
      // ここで vue3 のグローバル変数を呼び出すのは残念です。他に何か良いアイデアがあるかどうかはわかりません。instance.appContext.config.globalProperties.$Toast({
        タイプ: '情報'、
        タイトル: 「これはタイトルです」
        メッセージ: 'この記事は、マウント関数の主なロジックを整理し、基本的な処理フローを明らかにすることを目的としています (Vue 3.1.1 バージョン)。 '
      })
    }
    戻る {
      クリックハンドル
    }
  }
}
</スクリプト>

アイコンフォントを取得する

フラットコン

要約する

これで、vue3 を使用して Apple システムのサイドメッセージプロンプト効果を模倣する方法に関するこの記事は終了です。vue3 を使用して Apple のサイドメッセージプロンプトコンテンツを模倣する関連情報については、123WORDPRESS.COM の以前の記事を検索するか、次の関連記事を引き続き参照してください。今後とも 123WORDPRESS.COM を応援してください。

以下もご興味があるかもしれません:
  • Vue3管理システムを使用して動的ルーティングと動的サイドメニューバーを実装する方法
  • Vue3.0 レスポンシブ システムのソース コードの行ごとの分析
  • Vue3.x 最小プロトタイプシステムの説明

<<:  PSを使用して2分でxhtml+cssウェブサイトのホームページを作成します

>>:  HTML でファイルをアップロードするときに使用する <input type="file"> 要素のスタイルをカスタマイズします。

推薦する

MySQL マスタースレーブ同期メカニズムと同期遅延問題追跡プロセス

序文DBA として、仕事中に MySQL マスターとスレーブの同期遅延の問題に遭遇することがよくあり...

MySQL インデックス プッシュダウン (ICP) の簡単な理解と例

序文Index Condition Pushdown (ICP) は、MySQL 5.6 の新機能で...

CSS3は、ズームと回転を実現するためにscale()とrotate()を使用します。

1. scale() メソッドズームとは「縮小」と「拡大」を意味します。 CSS3 では、scal...

ウェブページ作成のテスト問題を全て解けますか?

Web ページのデザインに関する質問です。すべてに答えられるでしょうか? 1. 単一選択の質問 (...

JavaScript はフロントエンド Web ページでカウントダウンを実装します

ネイティブJavaScriptを使用してカウントダウンを簡単に実装します。参考までに、具体的な内容は...

Linux システムでのスケジュールされたタスクの紹介

目次1. 計画タスクをカスタマイズする2. 時間を同期する3. 練習する4. セキュリティの問題1....

MySQL query_cache_type パラメータと使用方法の詳細

MySQL クエリ キャッシュを設定する目的は次のとおりです。クエリ結果をキャッシュしておくと、次回...

UniappはBaidu Voiceを使用して録音をテキストに変換する機能を実現

3日間さまざまな困難に遭遇した後、ようやくこの機能を実現しました。正常に実装できる方法を見つける前に...

JSはビデオの再生速度を制御するための簡単なサンプルコードを実装します

導入以前、ある問題に気づきました。学習ビデオを視聴しているとき、動きが遅すぎる、先生が黒板に書くのに...

UTF-8 および GB2312 ウェブエンコーディング

最近、多くの学生から Web ページのエンコーディングについて質問を受けています。gb2312 と ...

Docker に ElasticSearch をインストールする方法を 1 つの記事で解説

目次序文1. Dockerをインストールする2. ElasticSearchをインストールする3. ...

MySQL スケジュールされたデータベース バックアップ操作の例

この記事では、MySQL のスケジュールされたデータベース バックアップ操作の例について説明します。...

MySQL の null (IFNULL、COALESCE、NULLIF) に関する知識ポイントのまとめ

この記事では、MySQL の null (IFNULL、COALESCE、NULLIF) に関連する...

Vue で @person 関数を実装する方法

この記事ではvueを使用し、マウスクリックイベントといくつかの小さなページの最適化を追加します。 基...

MYSQL の binlog 最適化に関する考察の要約

質問質問 1: トランザクションをコミットするときに REDO ログをフラッシュすることによって発生...