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

Vue基礎(chǔ)popover彈出框編寫(xiě)及bug問(wèn)題分析

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

引言

最近做了一套Vue 的UI組件框架,里面牽涉到的popover組件個(gè)人覺(jué)得很有意思,也是個(gè)人覺(jué)得做出來(lái)最好看的一個(gè)組件。

首先新建一個(gè)Vue項(xiàng)目,無(wú)需贅述了。

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

給組件命名為bl-popover

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

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

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

創(chuàng)建組件文件,實(shí)現(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)先寫(xiě)成這樣,符合我對(duì)使用結(jié)構(gòu)的印象,接著想要測(cè)試的話就注冊(cè)這個(gè)組件,也無(wú)需贅述了。

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

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

讓觸發(fā)器被點(diǎn)擊實(shí)現(xiàn)切換。但由于slot標(biāo)簽是不能接受任何東西的,所以我們把事件綁定在整個(gè)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>

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

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

絕對(duì)定位

slot標(biāo)簽外包裹標(biāo)簽即可選中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>

即可點(diǎn)擊之后顯示成這樣

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

Bug:監(jiān)聽(tīng)body問(wèn)題。

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

本想這樣處理:

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

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

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

  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');
          })
        })
      }
    }
  }
}

即可解決這個(gè)開(kāi)了就關(guān)的問(wèn)題。

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

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

所以不要監(jiān)聽(tīng)body,直接監(jiān)聽(tīng)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:再次打開(kāi)失敗。

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

這里來(lái)看控制臺(tái)。

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

第二次點(diǎn)擊外部

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

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

這是為什么呢,因?yàn)檫@時(shí)候有兩個(gè)事件監(jiān)聽(tīng)器在運(yùn)作,一個(gè)是popover上的,一個(gè)是document上的。順序是先調(diào)用popover上的,再調(diào)用document上的。

我們?cè)賮?lái)看看第四次點(diǎn)擊觸發(fā)器

再看看第五次,第六次

會(huì)發(fā)現(xiàn)關(guān)閉show出現(xiàn)越來(lái)越多次,這是為什么呢。這是因?yàn)槲覀凕c(diǎn)擊一次觸發(fā)器,執(zhí)行一次showChange方法,就會(huì)在document上新增一個(gè)addEventListener,而我們并沒(méi)有在時(shí)間結(jié)束之后刪除他,就越來(lái)越多,越來(lái)越多。

那么我們就需要在每次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)聽(tīng)器');
          }.bind(this))
        })
      }
    }
  }
}

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

此時(shí)前兩次點(diǎn)擊是

但是第三次點(diǎn)擊

說(shuō)明還是有bug,看起來(lái)這個(gè)刪除監(jiān)聽(tīng)器根本就沒(méi)有成功。

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

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

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

這是此時(shí)點(diǎn)擊了六次的結(jié)果,可以正常開(kāi)閉popover了。

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

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

這是因?yàn)槭录芭莸脑?,我們點(diǎn)擊popover或者觸發(fā)器,事件會(huì)冒泡到document上面去,還是會(huì)觸發(fā)。

我這時(shí)選擇了這個(gè)處理方法

<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)聽(tīng)器');
            this.show = false
            console.log('關(guān)閉show');
            document.removeEventListener('click',listener)
            console.log('刪除監(jiān)聽(tīng)器');
          }
          document.addEventListener('click',listener)
        })
      }
    }
  }
}
</script>

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

這樣就實(shí)現(xiàn)了一個(gè)最簡(jiǎn)單的popover。

其他Bug

Bug:外部有overflow:hidden,會(huì)遮擋popover。

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

會(huì)被擋住。

說(shuō)明這個(gè)問(wèn)題非常嚴(yán)重,代碼可能要全部砍倒重練。

而且單純地阻止冒泡也會(huì)帶來(lái)很多問(wèn)題,會(huì)打斷用戶的事件鏈。

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

<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)聽(tīng)器');
            this.show = false
            console.log('關(guān)閉show');
            document.removeEventListener('click',listener)
            console.log('刪除監(jiān)聽(tīng)器');
          }
          document.addEventListener('click',listener)
        })
      }
    }
  },
  mounted() {
    document.body.appendChild(this.$refs.contentWrapper)
  }
}
</script>

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

但是使用v-show就會(huì)讓contentWrapper一開(kāi)始就存在在頁(yè)面上,我并不想這樣。

<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)聽(tīng)器');
            this.show = false
            console.log('關(guān)閉show');
            document.removeEventListener('click',listener)
            console.log('刪除監(jiān)聽(tīng)器');
          }
          document.addEventListener('click',listener)
        })
      }
    }
  },
}
</script>

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

這時(shí)候我要想辦法讓這個(gè)彈出框像以前一樣顯示。首先要找到觸發(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在一個(gè)正確的位置了。

<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移出來(lái),因?yàn)?content-wrapper已經(jīng)不在.popover里了,而我們還在使用scoped。

Bug:位置其實(shí)不正確

我們?cè)谡麄€(gè)頁(yè)面上端再加一個(gè)div試試

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

這是因?yàn)椋^對(duì)定位并未根據(jù)觸發(fā)器為基準(zhǔn),而是根據(jù)他的父元素body元素為基準(zhǔn)的。

而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會(huì)打斷事件鏈

<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('打開(kāi)');
        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>

讓我們來(lái)判斷你點(diǎn)擊的是什么,然后再?zèng)Q定要做什么,就可以避免這個(gè)問(wèn)題了。

Bug:如果只點(diǎn)擊觸發(fā)器,會(huì)進(jìn)行重復(fù)監(jiān)聽(tīng)。

只點(diǎn)擊觸發(fā)器,會(huì)重復(fù)監(jiān)聽(tīng),而且不會(huì)實(shí)行removeEventListener。

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

確實(shí)會(huì)方便很多,但是,如果我頁(yè)面上有100個(gè)這個(gè)組件,那我打開(kāi)頁(yè)面就要加100個(gè)監(jiān)聽(tīng)器,那完蛋了。所以不可以這樣。

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

將close和open抽離出來(lái),大家都用這兩個(gè)控制開(kāi)閉。

<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兩個(gè)方法聚合了所有和開(kāi)閉彈出層有關(guān)的東西。

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

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

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

相關(guān)文章

最新評(píng)論