vue 自定義popup組件並支持scroll組件

本來是使用第三方庫 vant的vue 組件庫 的popup,後來在popup中使用better-scroll插件的時候,出現並不兼容的情況,也就自己搭建一個popu插件,中間遇到很多問題,都會記錄一下,給自己一些總結!

popup組件實現思路

首先是模仿vant的popup組件的功能:vant-popup

1.popup組件開發與樣式編寫(一個遮罩層,一個顯示層)。

2.組件引用(目前是選擇的組件導入的方法,沒有選擇使用插件this.$popup(),主要是用戶可以通過組件自定義傳參,插件的方式不好實現)。

代碼

popup.vue

<template>
  <div >
<!--    遮罩層-->
    <transition name="gh-fade" >
      <div   class="gh-overlay "
             v-if="isShow"
             ref="overlay"
             @click="changShowStat(false)"
             style="z-index: 2019;"
             :style="overlayStyle">
      </div>
    </transition>
    <!--    內容層-->
    <transition :name="'gh-slide-'+position">
       <div  class="gh-popup "
             v-if="isShow"
             ref="popup"
            :class="[
              getPosition,
              {'gh-popup--round':round}
            ]"
            style="z-index:2020"
            :style="styles">
        <slot></slot>
        <i  v-if="closeable"
            @click="changShowStat(false)"
            tabindex="0"
            :class="{
              'gh-icon-cross': closeable && !closeIcon ,
            }"
           class="gh-icon  gh-popup__close-icon gh-popup__close-icon--top-right">
          <img v-if="closeIcon" class="gh-icon__image" :src="closeIcon" alt="">
        </i>
      </div>
    </transition>
  </div>
</template>

<script>
  export default {
    name: "Popup",
    props:{
      value:Boolean,
      overlayStyle:{
        type:Object
      },
      height:{
        type:String,
        default: 'none'
      },
      width:{
        type:String,
        default: 'none'
      },
      styles:{
        type:Object
      },
      position:{
        type:String
      },
      closeable:{
        type:Boolean,
        default:false
      },
      closeIcon:{
        type:String
      },
      round:{
        type:Boolean,
        default:false
      },
    },
    data(){
      return{
        isShow:this.value
      }
    },
    mounted(){
        this.$nextTick(() => {
            const body = document.querySelector("body");
            if (body.append) {
                body.append(this.$el);
            } else {
                body.appendChild(this.$el);
            }
        });
    },
    beforeUpdate(){
    },

  computed:{
        getPosition(){
            return 'gh-popup--'+this.position
        },
    },
    watch: {
      //  監聽value的值更新
      value(val) {
          this.isShow = val;
      }
    },
    methods:{
      changShowStat(val){
          this.$emit('input',false)
      },

    },
  }
</script>

<style scoped>

  .gh-overflow-hidden {
    overflow: hidden !important;
  }

  .gh-overlay {
    position: fixed;
    top: 0;
    left: 0;
    z-index: 1;
    width: 100%;
    height: 100%;
    background-color: rgba(0,0,0,.7);
  }
  @keyframes fadeInUp {
    from {
      opacity: 0;
      -webkit-transform: translate3d(0, 100%, 0);
      transform: translate3d(0, 100%, 0);
    }
    to {
      opacity: 1;
      -webkit-transform: translate3d(0, 0, 0);
      transform: translate3d(0, 0, 0);
    }
  }
  .fadeInUp {
    -webkit-animation-name: fadeInUp;
    animation-name: fadeInUp;
    animation-duration: 0.5s;
  }
  .gh-popup {
    position: fixed;
    max-height: 100%;
    overflow-y: auto;
    background-color: #fff;
  }
  .gh-popup--center {
    top: 50%;
    left: 50%;
    -webkit-transform: translate3d(-50%, -50%, 0);
    transform: translate3d(-50%, -50%, 0);
  }
  .gh-popup--top {
    top: 0;
    left: 0;
    width: 100%;
  }
  .gh-popup--bottom {
    bottom: 0;
    left: 0;
    width: 100%;
  }

  .gh-popup--left {
    top: 50%;
    left: 0;
    -webkit-transform: translate3d(0, -50%, 0);
    transform: translate3d(0, -50%, 0);
  }
  .gh-popup--right {
    top: 50%;
    right: 0;
    -webkit-transform: translate3d(0, -50%, 0);
    transform: translate3d(0, -50%, 0);
  }
  .gh-icon {
    position: relative;
    display: inline-block;
    font: normal normal normal 14px/1 ght-icon;
    font-size: inherit;
    text-rendering: auto;
    -webkit-font-smoothing: antialiased;
  }
  .gh-popup__close-icon {
    position: absolute;
    z-index: 1;
    color: #c8c9cc;
    font-size: 22px;
    cursor: pointer;
  }
  .gh-popup__close-icon--top-right {
    top: 16px;
    right: 16px;
  }
  .gh-icon-cross::before {
    content: "\2716";
  }
  .gh-icon__image {
    width: 1em;
    height: 1em;
    object-fit: contain;
  }
  .gh-icon::before {
    display: inline-block;
  }
  .gh-popup--round {
    border-radius: 20px 20px 0 0;
  }

/* 內容css start*/

  @-webkit-keyframes gh-slide-bottom-enter {
    from {
      -webkit-transform: translate3d(0, 100%, 0);
      transform: translate3d(0, 100%, 0);
    }
  }
  @keyframes gh-slide-bottom-enter {
    from {
      -webkit-transform: translate3d(0, 100%, 0);
      transform: translate3d(0, 100%, 0);
    }
  }
  @-webkit-keyframes gh-slide-bottom-leave {
    to {
      -webkit-transform: translate3d(0, 100%, 0);
      transform: translate3d(0, 100%, 0);
    }
  }
  @keyframes gh-slide-bottom-leave {
    to {
      -webkit-transform: translate3d(0, 100%, 0);
      transform: translate3d(0, 100%, 0);
    }
  }
  @-webkit-keyframes gh-slide-top-enter {
    from {
      -webkit-transform: translate3d(0, -100%, 0);
      transform: translate3d(0, -100%, 0);
    }
  }
  @keyframes gh-slide-top-enter {
    from {
      -webkit-transform: translate3d(0, -100%, 0);
      transform: translate3d(0, -100%, 0);
    }
  }
  @-webkit-keyframes gh-slide-top-leave {
    to {
      -webkit-transform: translate3d(0, -100%, 0);
      transform: translate3d(0, -100%, 0);
    }
  }
  @keyframes gh-slide-top-leave {
    to {
      -webkit-transform: translate3d(0, -100%, 0);
      transform: translate3d(0, -100%, 0);
    }
  }
  @-webkit-keyframes gh-slide-left-enter {
    from {
      -webkit-transform: translate3d(-100%, 0, 0);
      transform: translate3d(-100%, 0, 0);
    }
  }
  @keyframes gh-slide-left-enter {
    from {
      -webkit-transform: translate3d(-100%, -50%, 0);
      transform: translate3d(-100%, -50%, 0);
    }
  }
  @-webkit-keyframes gh-slide-left-leave {
    to {
      -webkit-transform: translate3d(-100%, -50%, 0);
      transform: translate3d(-100%, -50%, 0);
    }
  }
  @keyframes gh-slide-left-leave {
    to {
      -webkit-transform: translate3d(-100%, -50%, 0);
      transform: translate3d(-100%, -50%, 0);
    }
  }
  @-webkit-keyframes gh-slide-right-enter {
    from {
      -webkit-transform: translate3d(100%, -50%, 0);
      transform: translate3d(100%, -50%, 0);
    }
  }
  @keyframes gh-slide-right-enter {
    from {
      -webkit-transform: translate3d(100%, -50%, 0);
      transform: translate3d(100%, -50%, 0);
    }
  }
  @-webkit-keyframes gh-slide-right-leave {
    to {
      -webkit-transform: translate3d(100%, -50%, 0);
      transform: translate3d(100%, -50%, 0);
    }
  }
  @keyframes gh-slide-right-leave {
    to {
      -webkit-transform: translate3d(100%, -50%, 0);
      transform: translate3d(100%, -50%, 0);
    }
  }
  @-webkit-keyframes gh-fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  @keyframes gh-fade-in {
    from {
      opacity: 0;
    }
    to {
      opacity: 1;
    }
  }
  @-webkit-keyframes gh-fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }
  @keyframes gh-fade-out {
    from {
      opacity: 1;
    }
    to {
      opacity: 0;
    }
  }
  @-webkit-keyframes gh-rotate {
    from {
      -webkit-transform: rotate(0deg);
      transform: rotate(0deg);
    }
    to {
      -webkit-transform: rotate(360deg);
      transform: rotate(360deg);
    }
  }
  @keyframes gh-rotate {
    from {
      -webkit-transform: rotate(0deg);
      transform: rotate(0deg);
    }
    to {
      -webkit-transform: rotate(360deg);
      transform: rotate(360deg);
    }
  }
  .gh-fade-enter-active {
    -webkit-animation: 0.5s gh-fade-in;
    animation: 0.5s gh-fade-in;
  }
  .gh-fade-leave-active {
    -webkit-animation: 0.5s gh-fade-out;
    animation: 0.5s gh-fade-out;
  }
  .gh-slide-top-enter-active {
    -webkit-animation: gh-slide-top-enter 0.5s both ease;
    animation: gh-slide-top-enter 0.5s both ease;
  }
  .gh-slide-top-leave-active {
    -webkit-animation: gh-slide-top-leave 0.5s both ease;
    animation: gh-slide-top-leave 0.5s both ease;
  }
  .gh-slide-bottom-enter-active {
    -webkit-animation: gh-slide-bottom-enter 0.5s both ease;
    animation: gh-slide-bottom-enter 0.5s both ease;
  }
  .gh-slide-bottom-leave-active {
    -webkit-animation: gh-slide-bottom-leave 1s both ease;
    animation: gh-slide-bottom-leave 1s both ease;
  }
  .gh-slide-left-enter-active {
    -webkit-animation: gh-slide-left-enter 0.5s both ease;
    animation: gh-slide-left-enter 0.5s both ease;
  }
  .gh-slide-left-leave-active {
    -webkit-animation: gh-slide-left-leave 0.5s both ease;
    animation: gh-slide-left-leave 0.5s both ease;
  }
  .gh-slide-right-enter-active {
    -webkit-animation: gh-slide-right-enter 0.5s both ease;
    animation: gh-slide-right-enter 0.5s both ease;
  }
  .gh-slide-right-leave-active {
    -webkit-animation: gh-slide-right-leave 0.5s both ease;
    animation: gh-slide-right-leave 0.5s both ease;
  }


</style>

組件導入

//導入popup組件
  import Popup from "Popup";
//在components中註冊組件
    components:{
      Popup,
    },
       //組件的使用
    <popup v-model="isShow"  //用於控制組件是否顯示
           position="bottom"  //用於控制組件顯示位置
           :styles="{height:'50%'}"  //組件樣式
    >
    </popup>

組件設計到的功能點:

1.v-model 組件之間參數雙向綁定

    watch: {
      //  監聽value的值更新
      value(val) {
          this.isShow = val;
      }
    },

    
methods:{
//使用$emit對父組件的v-model,通過input進行數據綁定
      changShowStat(val){
          this.$emit('input',false)
      },

    },

2.組件自定義掛載(我這裏是掛載到了body下)

使用$nextTick函數,在組件更新時對組件進行自定義掛載,我看iview你們也有全局掛載,但實現思路不一樣,還在研究中。。

    mounted(){
        this.$nextTick(() => {
            const body = document.querySelector("body");
            if (body.append) {
                body.append(this.$el);
            } else {
                body.appendChild(this.$el);
            }
        });
    },

3.vue組件過渡動畫,因爲開始學vue沒多久,剛開始使用的js動態掛載css動畫,來實現,邏輯代碼寫了一大堆,中間遇到很多問題,比如在watch鉤子函數中,dom元素獲取不到問題,還爲此仔細學習了vue生命週期,最後實現了,還是出現點擊頻率過快,出現組件加載問題,說了很多廢話,最後學習了vue的過渡動畫 transition組件,來實現的。

樣式寫在vue組件的樣式裏面

參數props

參數 說明 類型 默認值
v-model 當前組件是否顯示 boolean false
overlay 是否顯示遮罩層 boolean true
position 彈出位置,可選值爲 top bottom right left string center
overlay-style 自定義遮罩層樣式 object -
round 是否顯示圓角 boolean false
styles 內容層樣式 boolean true
closeable 是否顯示關閉圖標 boolean false
close-icon  關閉圖標名稱或圖片鏈接 string cross

 

雖然花了3天時間學了不少東西:

1.v-model 組件間的參數雙向綁定

2.vue生命週期

3.vue $$nextTick函數

4.css3動畫

5.vue <transition>過渡組件

6.vue插件開發

7.第三方ui庫組件的引入

遇到的bug

1.props中數據只能單向傳輸,無法雙向綁定,不要在子組件中改變props參數

2.css加載順序問題,導致頁面效果出不來,加載順序是自上而下,渲染優先級是自下而上(類似java棧)

3.vantUI組件的popup不兼容better-scroll,所以才自己做了一個

4.頁面按鈕點擊無效,因爲使用了定位佈局,父組件沒有設置高度,導致無法點擊

5.當自定義動畫,使用js動態加載動畫時,因爲需要退出效果,往往在修改是否顯示的參數(value)的時候,需要延時0.5秒,等動畫顯示完成後,再修改value的值;這時候點擊過快會出現組件加載問題,考慮過使用防抖來優化,但是感覺治標不治本,後面採用了vue <transition>過渡組件。

    methods:{
      changShowStat(val){
          if(val){
              this.$emit('input',val)
          }else{
              setTimeout(() => {
                  this.$emit('input',val)
              }, 500)
          }
      },

6.dom獲取不到問題,你會發現能打印this.$refs 能夠找到dom,但是就是去獲取this.$refs.xxx的時候,顯示undefined ,主要是 DOM結構已經出來了,但是如果在DOM結構中的某個DOM節點使用了v-if、v-show或者v-for(即根據獲得的後臺數據來動態操作DOM,即響應式),那麼這些DOM是不會再mounted階段找到的。參考資料,主要是dom沒用掛載 最後自己也詳細回顧了一下生命週期,覺得這個搞不懂,vue就算沒入門;

繼續學習!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章