欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Vue基礎(chǔ)popover彈出框編寫及bug問題分析

 更新時間:2022年09月07日 09:22:03   作者:泠泠棲枝  
這篇文章主要為大家介紹了Vue基礎(chǔ)popover彈出框編寫及bug問題分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

引言

最近做了一套Vue 的UI組件框架,里面牽涉到的popover組件個人覺得很有意思,也是個人覺得做出來最好看的一個組件。

首先新建一個Vue項目,無需贅述了。

制定結(jié)構(gòu)

給組件命名為bl-popover

<bl-popover>
  <template slot="content">
    這是內(nèi)容,這是內(nèi)容,這是內(nèi)容。
    這是內(nèi)容,這是內(nèi)容,這是內(nèi)容。
  </template>
  <button>點擊,顯示內(nèi)容</button>
</bl-popover>

這種結(jié)構(gòu)也許不錯。

contentslot包裹popover里需要顯示的內(nèi)容,而原始默認slot里包裹popover觸發(fā)器。

創(chuàng)建組件文件,實現(xiàn)基本功能

src目錄創(chuàng)建popover.vue文件。

<template>
  <div class="popover">
    <slotname="content"></slot>
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'Popover',
}
</script>
<style lang="scss" scoped>
.popover{
   display:inline-block
}
</style>

文件內(nèi)部結(jié)構(gòu)先寫成這樣,符合我對使用結(jié)構(gòu)的印象,接著想要測試的話就注冊這個組件,也無需贅述了。

設(shè)置為display:inline-block可不用占滿一整行。

我們需要用觸發(fā)器來顯示和隱藏popover,所以在data里設(shè)置一個show屬性。

讓觸發(fā)器被點擊實現(xiàn)切換。但由于slot標簽是不能接受任何東西的,所以我們把事件綁定在整個div上。

就變成了

<template>
  <div class="popover" @click="showChange">
    <slotname="content" v-if="show"></slot>
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data(){
  return{
  show:false
    }
  },
  methods:{
  showChange(){
    this.show = !this.show
    }
  }
}
</script>
<style lang="scss" scoped>
.popover{
   display:inline-block
}
</style>

此時即可實現(xiàn)點擊button就可顯示popover。

這時候我們需要做的就是將popover變?yōu)榻^對定位。

絕對定位

slot標簽外包裹標簽即可選中slot。

<template>
  <div class="popover" @click="showChange">
   <div class="content-wrapper" v-if="show">
    <slotname="content"></slot>
   </div>
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data(){
  return{
  show:false
    }
  },
  methods:{
  showChange(){
    this.show = !this.show
    }
  }
}
</script>
<style lang="scss" scoped>
.popover{
  display:inline-block;
  position: relative;
  .content-wrapper{
    position:absolute;
    bottom:100%;
    left:0;
    padding: 6px;
    border: 1px solid #ebeef5;
    box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
    background: white;
  }
}
</style>

即可點擊之后顯示成這樣

如何點擊外部關(guān)閉

Bug:監(jiān)聽body問題。

name我該如何關(guān)閉這個popover呢?是點其他地方關(guān)閉嗎?

本想這樣處理:

  methods:{
  showChange(){
    this.show = !this.show
    if(this.show===true){
      document.body.addEventListener('click',()=>{
      this.show = false
        })
      }
    }
  }

但事實是,這樣連popover都無法打開了。這是由于原生JS的事件冒泡機制。 this.show = !this.show this.show = false是在一次點擊下全部完成了,所以他就直接給關(guān)了,根本看不見。

故這里我們將他改為異步,就不會一口氣都走完了。

  methods:{
  showChange(){
      this.show = !this.show
      if(this.show===true) {
        console.log('切換show');
        setTimeout(()=>{
          document.body.addEventListener('click', () => {
            this.show = false
            console.log('關(guān)閉show');
          })
        })
      }
    }
  }
}

即可解決這個開了就關(guān)的問題。

但是還有其他的問題。 實際上,body的大小只有藍色邊框內(nèi)的部分

也就是點擊藍色邊框之外的部分,是關(guān)不掉這個popover的。

所以不要監(jiān)聽body,直接監(jiān)聽document就好。

  methods:{
  showChange(){
      this.show = !this.show
      if(this.show===true) {
        console.log('切換show');
        setTimeout(()=>{
          document.addEventListener('click', () => {
            this.show = false
            console.log('關(guān)閉show');
          })
        })
      }
    }
  }
}

Bug:再次打開失敗。

解決了點擊外部失效的問題,我發(fā)現(xiàn),點擊打開popover,再點擊外部關(guān)閉,就無法再次打開popover了。

這里來看控制臺。

第一次點擊觸發(fā)器

第二次點擊外部

第三次點擊觸發(fā)器

會發(fā)現(xiàn)第三次點擊直接走完了切換和關(guān)閉。

這是為什么呢,因為這時候有兩個事件監(jiān)聽器在運作,一個是popover上的,一個是document上的。順序是先調(diào)用popover上的,再調(diào)用document上的。

我們再來看看第四次點擊觸發(fā)器

再看看第五次,第六次

會發(fā)現(xiàn)關(guān)閉show出現(xiàn)越來越多次,這是為什么呢。這是因為我們點擊一次觸發(fā)器,執(zhí)行一次showChange方法,就會在document上新增一個addEventListener,而我們并沒有在時間結(jié)束之后刪除他,就越來越多,越來越多。

那么我們就需要在每次popover關(guān)閉之后,刪除他。

  methods:{
  showChange(){
      this.show = !this.show
      if(this.show===true) {
        console.log('切換show');
        setTimeout(()=>{
          document.addEventListener('click', function listener{
            this.show = false
            console.log('關(guān)閉show');
            document.removeEventListener('click',listener)
            console.log('刪除監(jiān)聽器');
          }.bind(this))
        })
      }
    }
  }
}

這里我們需要removeEventListener,所以監(jiān)聽器需要有個函數(shù)名,我起名為listener,但不是箭頭函數(shù)了,this.show的this就不是指向Vue實例了,而是調(diào)用這個監(jiān)聽器的document了。所以需要使用bind把this綁定一下。

此時前兩次點擊是

但是第三次點擊

說明還是有bug,看起來這個刪除監(jiān)聽器根本就沒有成功。

這里的原因比較復雜。我為了讓listener內(nèi)的this還是指向Vue實例,使用了bind,但其實使用了bind之后的listener并不是原本的listener了,而是綁定后返回的一個新的函數(shù)。所以并沒有刪掉原本的listener。所以在這里要避免使用bind。

methods:{
  showChange(){
    this.show = !this.show
    if(this.show===true) {
      console.log('切換show');
      setTimeout(()=>{
        let listener = () =>{
          console.log('新增事件監(jiān)聽器');
          this.show = false
          console.log('關(guān)閉show');
          document.removeEventListener('click',listener)
          console.log('刪除監(jiān)聽器');
        }
        document.addEventListener('click',listener)
      })
    }
  }
}

新建了一個箭頭函數(shù),就避免了使用bind

這是此時點擊了六次的結(jié)果,可以正常開閉popover了。

Bug:點擊popover氣泡本身也會關(guān)閉popover

雖然開閉正常了,但是點擊氣泡本身,我本身不希望他隱藏,可他還是關(guān)閉了。

這是因為事件冒泡的原因,我們點擊popover或者觸發(fā)器,事件會冒泡到document上面去,還是會觸發(fā)。

我這時選擇了這個處理方法

<template>
  <div class="popover" @click.stop="showChange">
    <div class="content-wrapper" v-if="show" @click.stop>
      <slot name="content"></slot>
    </div>
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data(){
    return{
      show:false
    }
  },
  methods:{
    showChange(){
      this.show = !this.show
      if(this.show===true) {
        console.log('切換show');
        this.$nextTick(()=>{
          let listener = () =>{
            console.log('新增事件監(jiān)聽器');
            this.show = false
            console.log('關(guān)閉show');
            document.removeEventListener('click',listener)
            console.log('刪除監(jiān)聽器');
          }
          document.addEventListener('click',listener)
        })
      }
    }
  }
}
</script>

在可以被點擊的地方使用了.stop阻止冒泡,可以發(fā)現(xiàn),點擊氣泡不會被關(guān)閉了,并且document上的事件監(jiān)聽器也沒有產(chǎn)生或觸發(fā)。

這樣就實現(xiàn)了一個最簡單的popover。

其他Bug

Bug:外部有overflow:hidden,會遮擋popover。

我在popover組件的外部套一個div,設(shè)置overflow:hidden,會發(fā)生這樣的情況

會被擋住。

說明這個問題非常嚴重,代碼可能要全部砍倒重練。

而且單純地阻止冒泡也會帶來很多問題,會打斷用戶的事件鏈。

那我選擇讓這個彈出框氣泡移到body上,就可以避免這個問題。

<template>
  <div class="popover" @click.stop="showChange">
    <div ref="contentWrapper" class="content-wrapper" v-show="show" @click.stop>
      <slot name="content"></slot>
    </div>
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data(){
    return{
      show:false
    }
  },
  methods:{
    showChange(){
      this.show = !this.show
      if(this.show===true) {
        console.log('切換show');
        this.$nextTick(()=>{
          let listener = () =>{
            console.log('新增事件監(jiān)聽器');
            this.show = false
            console.log('關(guān)閉show');
            document.removeEventListener('click',listener)
            console.log('刪除監(jiān)聽器');
          }
          document.addEventListener('click',listener)
        })
      }
    }
  },
  mounted() {
    document.body.appendChild(this.$refs.contentWrapper)
  }
}
</script>

可以看到,為了能讓v-if===false的情況下,也能檢查的到contentWrapper,我把v-if換成了v-show,因為v-show只是切換display:none,影響的是元素的顯示隱藏。而v-if影響的是元素是否被render到DOM樹上。

但是使用v-show就會讓contentWrapper一開始就存在在頁面上,我并不想這樣。

<template>
  <div class="popover" @click.stop="showChange">
    <div ref="contentWrapper" class="content-wrapper" v-if="show" @click.stop>
      <slot name="content"></slot>
    </div>
    <slot></slot>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data(){
    return{
      show:false
    }
  },
  methods:{
    showChange(){
      this.show = !this.show
      if(this.show===true) {
        console.log('切換show');
        this.$nextTick(()=>{
          document.body.appendChild(this.$refs.contentWrapper)
          let listener = () =>{
            console.log('新增事件監(jiān)聽器');
            this.show = false
            console.log('關(guān)閉show');
            document.removeEventListener('click',listener)
            console.log('刪除監(jiān)聽器');
          }
          document.addEventListener('click',listener)
        })
      }
    }
  },
}
</script>

這樣,讓我點擊觸發(fā)器的時候,再將彈出框移動到body上,也可以。記住這一步要放在nextTick里,不然還是可能找不到contentWrapper。

這時候我要想辦法讓這個彈出框像以前一樣顯示。首先要找到觸發(fā)器的位置。

methods:{
  showChange(){
    this.show = !this.show
    if(this.show===true) {
      this.$nextTick(()=>{
        document.body.appendChild(this.$refs.contentWrapper)
        let{left,top,width,height} = this.$refs.triggerWrapper.getBoundingClientRect()
        console.log(left, top, width, height);
        let listener = () =>{
          this.show = false
          document.removeEventListener('click',listener)
        }
        document.addEventListener('click',listener)
      })
    }
  }
},

這樣就可以讓contentWrapper在一個正確的位置了。

<template>
  <div class="popover" @click.stop="showChange">
    <div ref="contentWrapper" class="content-wrapper" v-if="show" @click.stop>
      <slot name="content"></slot>
    </div>
    <span ref="triggerWrapper">
    <slot></slot>
      </span>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data(){
    return{
      show:false
    }
  },
  methods:{
    showChange(){
      this.show = !this.show
      if(this.show===true) {
        this.$nextTick(()=>{
          document.body.appendChild(this.$refs.contentWrapper)
          let{left,top} = this.$refs.triggerWrapper.getBoundingClientRect()
          this.$refs.contentWrapper.style.top = top +'px'
          this.$refs.contentWrapper.style.left = left + 'px'
          let listener = () =>{
            this.show = false
            document.removeEventListener('click',listener)
          }
          document.addEventListener('click',listener)
        })
      }
    }
  },
}
</script>
<style lang="scss" scoped>
.popover{
  display:inline-block;
  position: relative;
}
.content-wrapper{
  position:absolute;
  padding: 6px;
  border: 1px solid #ebeef5;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  background: white;
  transform: translateY(-100%);
}
</style>

注意要把.content-wrapper移出來,因為.content-wrapper已經(jīng)不在.popover里了,而我們還在使用scoped。

Bug:位置其實不正確

我們在整個頁面上端再加一個div試試

會發(fā)現(xiàn)位置根本不對。

這是因為,絕對定位并未根據(jù)觸發(fā)器為基準,而是根據(jù)他的父元素body元素為基準的。

而body頂部到視窗的差值,就是scrollY

橫向也一樣的道理。 所以改成

methods:{
  showChange(){
    this.show = !this.show
    if(this.show===true) {
      this.$nextTick(()=>{
        document.body.appendChild(this.$refs.contentWrapper)
        let{left,top} = this.$refs.triggerWrapper.getBoundingClientRect()
        this.$refs.contentWrapper.style.top = top +scrollY +'px'
        this.$refs.contentWrapper.style.left = left + scrollX + 'px'
        let listener = () =>{
          this.show = false
          document.removeEventListener('click',listener)
        }
        document.addEventListener('click',listener)
      })
    }
  }
},

就正常了。

Bug:.stop會打斷事件鏈

<template>
  <div class="popover" @click="showChange">
    <div v-if="show" ref="contentWrapper" class="content-wrapper">
      <slot name="content"></slot>
    </div>
    <span ref="triggerWrapper">
    <slot></slot>
      </span>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data() {
    return {
      show: false
    }
  },
  methods: {
    position() {
      document.body.appendChild(this.$refs.contentWrapper)
      let {left, top} = this.$refs.triggerWrapper.getBoundingClientRect()
      this.$refs.contentWrapper.style.top = top + scrollY + 'px'
      this.$refs.contentWrapper.style.left = left + scrollX + 'px'
    },
    eventListener() {
      let listener = (event) => {
        if (!this.$refs.contentWrapper.contains(event.target)) {
          this.show = false
          document.removeEventListener('click', listener)
        }
      }
      document.addEventListener('click', listener)
    },
    showChange(event) {
      if (this.$refs.triggerWrapper.contains(event.target)) {
        this.show = !this.show
        console.log('打開');
        if (this.show === true) {
          this.$nextTick(() => {
            this.position()
            this.eventListener()
          })
        }
      }
    }
  },
}
</script>
<style lang="scss" scoped>
.popover {
  display: inline-block;
  position: relative;
}
.content-wrapper {
  position: absolute;
  padding: 6px;
  border: 1px solid #ebeef5;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  background: white;
  transform: translateY(-100%);
}
</style>

讓我們來判斷你點擊的是什么,然后再決定要做什么,就可以避免這個問題了。

Bug:如果只點擊觸發(fā)器,會進行重復監(jiān)聽。

只點擊觸發(fā)器,會重復監(jiān)聽,而且不會實行removeEventListener。

那我們把document.addEventListener放在created里是不是會方便很多呢?

確實會方便很多,但是,如果我頁面上有100個這個組件,那我打開頁面就要加100個監(jiān)聽器,那完蛋了。所以不可以這樣。

這個時候我要進行的是一個高內(nèi)聚的設(shè)計模式。

將close和open抽離出來,大家都用這兩個控制開閉。

<template>
  <div ref="popover" class="popover" @click="showChange">
    <div v-if="show" ref="contentWrapper" class="content-wrapper">
      <slot name="content"></slot>
    </div>
    <span ref="triggerWrapper">
    <slot></slot>
      </span>
  </div>
</template>
<script>
export default {
  name: 'Popover',
  data() {
    return {
      show: false
    }
  },
  methods: {
    position() {
      document.body.appendChild(this.$refs.contentWrapper)
      let {left, top} = this.$refs.triggerWrapper.getBoundingClientRect()
      this.$refs.contentWrapper.style.top = top + scrollY + 'px'
      this.$refs.contentWrapper.style.left = left + scrollX + 'px'
    },
    listener(event) {
      if (this.$refs.popover &&
          this.$refs.popover.contains(event.target) || this.$refs.popover === event.target) {
        return;
      }
      if (this.$refs.contentWrapper &&
          this.$refs.contentWrapper.contains(event.target) || this.$refs.contentWrapper === event.target) {
        return;
      }
      this.close()
    },
    close() {
      this.show = false
      document.removeEventListener('click', this.listener)
    },
    open() {
      this.show = true
      this.$nextTick(() => {
        this.position()
        document.addEventListener('click', this.listener)
      })
    },
    showChange(event) {
      if (this.$refs.triggerWrapper.contains(event.target)) {
        if (this.show === true) {
          this.close()
        } else {
          this.open()
        }
      }
    }
  },
}
</script>
<style lang="scss" scoped>
.popover {
  display: inline-block;
  position: relative;
}
.content-wrapper {
  position: absolute;
  padding: 6px;
  border: 1px solid #ebeef5;
  box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
  background: white;
  transform: translateY(-100%);
}
</style>

這樣高聚合了之后,將closeopen兩個方法聚合了所有和開閉彈出層有關(guān)的東西。

這樣就完成了一個基礎(chǔ)的,可點擊在上方出現(xiàn)的popover。

剩下的,Hover觸發(fā),四個方向觸發(fā),具體樣式,也是依葫蘆畫瓢,這里就不多贅述了。

以上就是Vue基礎(chǔ)popover彈出框編寫及bug問題分析的詳細內(nèi)容,更多關(guān)于Vue popover彈出框的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

最新評論