How to watch the height change (DOM) of an element in vuejs

How to watch the height change (DOM) of an element in vuejs. It’s possible? If not, what are the alternatives?

This is related to the topic:
https://forum.vuejs.org/t/perform-action-after-loading-all-children/19689/3

But since I have two days that nobody answers I’m asking the individual question.

On mounted I’m trying to add the following watcher:

    mounted: function () {
      this.$watch(
        function () {
          return this.$el.lastChild.lastChild.lastChild.firstChild.clientHeight
        },
        function (oldVal, newVal) {
          console.log(oldVal, ' - ', newVal)
          return ''
        }
      )
      console.log('mounted')
      this.$refs.windowResize.$on('resize', this.resize)
    },

Note: I’ve tried the mutationObserver, but it did not work, for what I want.

2 Likes

So you want to watch the element for all height changes - added content, style changes, window resize etc.?
I don’t think that’s possible, unfortunately the DOM doesn’t have any system for this. Also, Vue can only watch objects that have implemented Vue reactivity.

You’ll just have to think about each thing that may cause a resize and add a watcher/event listener for that. Or you could set an interval that checks the element - kind of a “brute force” solution but probably not that bad actually.

Btw you should use a ref instead of this.$el.lastChild.lastChild.lastChild.firstChild, much simpler :slight_smile:

This is the problem, as you can see in the reported topic, I’m dealing with third-party component, in this case the q-carousel of the quasar framework.

What generates dots height resizing are resizing the screen width or increasing the amount of slides, which only happens if the screen resize or props is changed.

The issue is that I do not have any $refs for that specific part of the q-carousel component, so I do not have access without being via DOM, what happens is that when I run this on mounted or on $nextTick as the rendering does not yet was passed to the real DOM and therefore height returns me 1px, and this breaks all other necessary checks.

Due to these limitations the height verification of this particular div is the only way I can see to detect this change. Obviously this can be done using a setInterval, but I do not think that’s interesting.

It does not necessarily have to be an observer in clientHeight, if there is an event inside the vue core that returns me when the rendering was completed, this also works, because at that time the height has been processed.

I need an alert when rendering, because the onload event only works the first time I load page, as these changes are dynamic, if I change something later it does not generate the onload event.

Hi,

i was trying to solve this problem last week and the only possible way was to call any function selecting the height of a dom element from async mounted() {
await this.nextTick();
measure the height of the element using document.querySelector(ele) and update data or vuex prop
//call function here
}

a watcher will function well afterwards

i hope this is of help

the height of the element

Hmm, you could use updated() to check the element after every update. The problem with that though is that if you save the height to data, that would trigger another update, causing an infinite loop.

Edit: Actually, it won’t trigger an infinite loop, because on the second update Vue would see that the height variable was set to the same value as it was before, and wouldn’t trigger another update. So maybe you can use updated() then :slight_smile:

If the height only changes when certain variables change, you could set up a watcher for those variables, like this:

watch: {
  content() {
    this.$nextTick(()=>{
      this.height = this.$el.lastChild.lastChild.lastChild.firstChild.clientHeight
    })
  }
}
1 Like

Thank you. Both solve the problem, but async await is more compact and makes me save 3 watchers.

I thought I’d solved the problem, but both tips are just a complement to the solution.

I’ve been reading a few things about vuejs and found that nextTick references a nodejs object, and from what I read in the documentation this does not ensure that the document is actually rendered when executing this.

What it does, roughly is to interrupt the execution queue and call the function passed by parameter, and this may work and may not work because it has no way of knowing what the next action is in the queue running on nodejs, even more considering that there are several other components running in a similar way and disputing the same resources.

I’m implementing a patch here, but this is not efficient when finished posting here.

I suggest that the staff of the vuejs find some way to do the detection of the end of the rendering of that specific component, which would be equivalent to the onload of the old pages, only now adapted the reactivity of the javascript.

This is sometimes necessary.

Now yes actually solved, with the help of what you said + a little promisses I managed to do it in a way that is rationally efficient.

Any suggestions for improvement, let me know.

Code:

<template>
  <div
    class="row justify-center"
  >
    <q-window-resize-observable
      ref="resizeWindow"/>
    <l-slide-cards
      v-if="qtdSlides <= 1"
      :load="true"
      :cards="cardsPart"/>
    <q-carousel
      v-else
      infinite
      arrows
      dots
      @slide="swipe"
      :id='_uid'
      :class="[
        'carouselCards',
        {'hideDots': firstLoad || !dots}
      ]"
    >
      <l-slide-cards
        slot="slide"
        v-for="i in qtdSlides"
        :key="i"
        :load="(slide == i || prev == i || next == i)"
        :cards="cardsPart"
        class="col"/>
      <div
        v-if="!firstLoad && !dots"
        class="carouselCardsPagination"
      >
        {{slide}} de {{qtdSlides}}
      </div>
    </q-carousel>
  </div>
</template>

<script>
  import {
    QCarousel,
    dom,
    QWindowResizeObservable,
    QPagination
  } from 'quasar'
  import LSlideCards from './slideCards'
  export default {
    components: {
      QCarousel,
      LSlideCards,
      QWindowResizeObservable,
      QPagination
    },
    props: {
      cards: {
        type: Array,
        required: false,
        default: function () {
          let c = Array(360).fill(null).map(function () {
            return {}
          })
          c._objPadrao = true
          return c
        }
      }
    },
    data () {
      return {
        slide: 1,
        width: dom.width(window),
        firstLoad: true,
        slideHeight: 0
      }
    },
    computed: {
      normalizedCards: function () {
        let c = []
        c._fill = function (length) {
          let size = length === 0 ? 100 : length
          return Array(size).fill(null).map(function () {
            return {}
          })
        }

        let cards = this.normalizeProp(this.cards, c, true)
        return this.addReactivity(cards)
      },
      qtdSlides: {
        get: function () {
          if (this.width > 991) {
            return Math.ceil(this.normalizedCards.length / 4)
          }
          else if (this.width > 767) {
            return Math.ceil(this.normalizedCards.length / 2)
          }
          else {
            return this.normalizedCards.length
          }
        },
        set: function (width) {
          this.width = width
        }
      },
      cardsPart: function () {
        return this.normalizedCards.slice(this.cardPosIni, this.cardPosFinal)
      },
      cardsPerSlide: function () {
        return Math.ceil(this.normalizedCards.length / this.qtdSlides)
      },
      cardPosIni: function () {
        return (this.slide - 1) * this.cardsPerSlide
      },
      cardPosFinal: function () {
        return ((this.slide - 1) + 1) * this.cardsPerSlide
      },
      prev: function () {
        return this.slide === 1 ? this.qtdSlides : (this.slide - 1)
      },
      next: function () {
        return this.slide === this.qtdSlides ? 1 : (this.slide + 1)
      },
      dots: function () {
        console.log(this.slideHeight <= 48)
        return this.slideHeight <= 48
      }
    },
    methods: {
      swipe: function (slide) {
        this.slide = slide + 1
      },
      resize: function (size) {
        console.log('resize')
        this.qtdSlides = size.width
      },
      promiseEachArray: function (arr, size) {
        return new Promise((resolve, reject) => {
          let len = arr.length
          for (let i = 0; i < len; i++) {
            if (arr[i].clientWidth > size) {
              reject(new Error('erro eachArray'))
            }
          }
          resolve(true)
        })
      },
      promiseSleep: function (timeout) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(true)
          }, timeout)
        })
      },
      promiseRender: function (arr, size) {
        let _this = this
        return new Promise(async (resolve, reject) => {
          let out = false
          do {
            try {
              await _this.$nextTick()
              console.log('nextTick')
              await _this.promiseEachArray(arr, size)
              out = true
              resolve(true)
            }
            catch (error) {
              console.log('catch')
              out = false
              try {
                await _this.promiseSleep(100)
                console.log('sleep')
              }
              catch (error) {}
            }
          } while (out)
          reject(new Error('erro promiseRender'))
        })
      },
      waitRender: async function () {
        try {
          await this.promiseRender(this.$el.lastChild.lastChild.children[3].firstChild.children, 24)
        }
        catch (error) {}
        finally {
          this.slideHeight = this.$el.lastChild.lastChild.children[3].firstChild.clientHeight
          console.log(this.slideHeight)
          if (this.firstLoad) {
            this.firstLoad = false
          }
        }
      }
    },
    watch: {
      width: function () {
        console.log('watcher')
        this.waitRender()
      },
      qtdSlides: function () {
        console.log('watcher')
        this.waitRender()
      }
    },
    created: function () {
      console.log('created')
    },
    mounted: function () {
      console.log('mounted')
      this.waitRender()
      console.log(this.slideHeight)
      this.$refs.resizeWindow.$on('resize', this.resize)
    },
    updated: function () {
      console.log('updated')
    }
  }
</script>

<style lang="stylus">
  @import '~src/themes/app.variables.styl';
  .carouselCards .q-carousel-track{
    height: auto!important;
  }
  .carouselCards .q-carousel-dots .q-icon{
    color: $secondary!important;
  }
  .hideDots .q-carousel-inner .q-carousel-toolbar{
    position: absolute!important;
    top: 0!important;
    visibility: hidden!important;
  }
  .carouselCards .q-carousel-toolbar{
    position: static!important;
    visibility: visible!important;
  }
  .carouselCards .q-carousel-toolbar,
  .carouselCards .q-carousel-dots,
  .carouselCards .q-carousel-dots .q-icon{
    opacity: 1!important;
  }
  @media screen and (max-width: 1199px){
    .carouselCards .q-carousel-toolbar{
      padding-left: 30px;
      padding-right: 30px;
    }
    .carouselCards .carouselCardsPagination{
      padding: 8px 30px!important;
    }
  }
  @media screen and (min-width: 1200px){
    .carouselCards .q-carousel-toolbar{
      padding-left: calc((100% - 1140px)/2)!important;
      padding-left: -webkit-calc((100% - 1140px)/2)!important;
      padding-left: -moz-calc((100% - 1140px)/2)!important;
      padding-left: -o-calc((100% - 1140px)/2)!important;
      padding-left: -ms-calc((100% - 1140px)/2)!important;

      padding-right: calc((100% - 1140px)/2)!important;
      padding-right: -webkit-calc((100% - 1140px)/2)!important;
      padding-right: -moz-calc((100% - 1140px)/2)!important;
      padding-right: -o-calc((100% - 1140px)/2)!important;
      padding-right: -ms-calc((100% - 1140px)/2)!important;
    }
    .carouselCards .carouselCardsPagination{
      padding: 8px calc((100% - 1140px)/2)!important;
      padding: 8px -webkit-calc((100% - 1140px)/2)!important;
      padding: 8px -moz-calc((100% - 1140px)/2)!important;
      padding: 8px -o-calc((100% - 1140px)/2)!important;
      padding: 8px -ms-calc((100% - 1140px)/2)!important;
    }
  }

  @media screen and (max-width: 1219px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 26px!important;
      height: 26px!important;
      font-size: 18px!important;
    }
  }
  @media screen and (min-width: 1220px) and (max-width: 1283px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 32px!important;
      height: 32px!important;
      font-size: 21px!important;
    }
  }
  @media screen and (min-width: 1284px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 42px!important;
      height: 42px!important;
      font-size: 32px!important;
    }
  }

  .carouselCards .q-carousel-left-button,
  .carouselCards .q-carousel-right-button{
    padding: 0!important;
  }
  @media screen and (max-width: 1199px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: 30px!important;
      width: 30px!important;
    }
  }
  @media screen and (min-width: 1200px) and (max-width: 1283px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: calc((100% - 1140px)/2)!important;
      max-width: -webkit-calc((100% - 1140px)/2)!important;
      max-width: -moz-calc((100% - 1140px)/2)!important;
      max-width: -o-calc((100% - 1140px)/2)!important;
      max-width: -ms-calc((100% - 1140px)/2)!important;

      width: calc((100% - 1140px)/2)!important;
      width: -webkit-calc((100% - 1140px)/2)!important;
      width: -moz-calc((100% - 1140px)/2)!important;
      width: -o-calc((100% - 1140px)/2)!important;
      width: -ms-calc((100% - 1140px)/2)!important;
    }
  }
  @media screen and (min-width: 1284px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: 70px!important;
      width: 70px!important;
    }
  }
  .carouselCards .q-carousel-left-button .q-icon,
  .carouselCards .q-carousel-left-button .q-icon:active,
  .carouselCards .q-carousel-left-button .q-icon:visited,
  .carouselCards .q-carousel-left-button .q-icon:focus,
  .carouselCards .q-carousel-right-button .q-icon,
  .carouselCards .q-carousel-right-button .q-icon:active,
  .carouselCards .q-carousel-right-button .q-icon:visited,
  .carouselCards .q-carousel-right-button .q-icon:focus{
    opacity: 0.75!important;
    padding: 0!important;
    color: $primary!important;
    background-color: $secondary!important;
  }
  .carouselCards .q-carousel-left-button .q-icon:hover,
  .carouselCards .q-carousel-right-button .q-icon:hover{
    opacity: 1!important;
  }
  .carouselCards .carouselCardsPagination{
    font-family: $fprimary!important;
    font-size: 18px!important;
    color: $tsecondary!important;
    text-align: center!important;
  }
</style>

I have a similar problem but your solution is verbose making it difficult for me to fathom. If you’ve got some time, can you kindly explain your code?

I’ll show you later, are you using the framework quasar too? Do you have a small code sample of what you are doing?

My code is big, because this is really a big component, it’s a custom q-cards slider, which in turn has custom components inside.

But if your context is smaller, it makes it easier.

Explanation:

This component is a customization of the q-carousel of the quasar framework.

It is a slider that contains slides, each slide has a variable amount of cards, each card has some components inside, but keep the level of abstraction only up to the cards.

The slider fill-in data is passed via props and passed to the child components.

Each slider has a variable amount of dots on the bottom that corresponds to the number of slides.

In addition to configuring slider functionality, the problem that this code solves is:

In my application I have defined that I will accept up to 2 lines of dots, after which the number of slides is represented via text.

But to do this I first need to find the height of the div containing the dots. The quasar component does not contain any $ ref I can access to get this. But the main problem is that the hook mounted does not ensure that the page is fully loaded, just the VUEJS object.

So I had to create a way to wait for the end of this rendering.

<template>
  <div
    class="row justify-center"
  >
    <q-window-resize-observable
      ref="resizeWindow"/>
    <l-slide-cards
      v-if="qtdSlides <= 1"
      :load="true"
      :cards="cardsPart"/>
    <q-carousel
      v-else
      infinite
      arrows
      dots
      @slide="swipe"
      :id='_uid'
      :class="[
        'carouselCards',
        {'hideDots': firstLoad || !dots}
      ]"
    >
      <l-slide-cards
        slot="slide"
        v-for="i in qtdSlides"
        :key="i"
        :load="(slide == i || prev == i || next == i)"
        :cards="cardsPart"
        class="col"/>
      <div
        v-if="!firstLoad && !dots"
        class="carouselCardsPagination"
      >
        {{slide}} de {{qtdSlides}}
      </div>
    </q-carousel>
  </div>
</template>

<script>
  import {
    QCarousel,
    dom,
    QWindowResizeObservable,
    QPagination
  } from 'quasar'
  import LSlideCards from './slideCards' // My slide with cards
  export default {
    components: {
      QCarousel,
      LSlideCards,
      QWindowResizeObservable,
      QPagination
    },
    props: {
      cards: { // content of the slider and received via prop in the form of array objects
        type: Array,
        required: false,
        default: function () { // if the array is not filled it returns an array of empty objects
          let c = Array(100).fill(null).map(function () {
            return {}
          })
          c._objPadrao = true
          return c
        }
      }
    },
    data () {
      return {
        slide: 1, // current slide (visible)
        width: dom.width(window), //screen width
        firstLoad: true, // identifies whether it is the first page load
        slideHeight: 0 // identifies the height of the div containing the dots
      }
    },
    computed: {
      normalizedCards: function () {
        /*
         verifies whether the informed array follows the desired structure,
         otherwise it corrects.
        */
        let c = []
        c._fill = function (length) {
          let size = length === 0 ? 100 : length
          return Array(size).fill(null).map(function () {
            return {}
          })
        }

        return this.normalizeProp(this.cards.slice(0, 1000), c, true)
      },
      qtdSlides: { // calculates the number of slides in the slider based on the number of cards
        get: function () {
          if (this.width > 991) { // on screens larger than 991px, each slide has 4 cards
            return Math.ceil(this.normalizedCards.length / 4)
          }
          else if (this.width > 767) {// on screens smaller than 991px and larger than 767px, each slide has 2 cards
            return Math.ceil(this.normalizedCards.length / 2)
          }
          else {// on screens smaller than 767, each slide has 1 card
            return this.normalizedCards.length
          }
        },
        set: function (width) {
          this.width = width
        }
      },
      cardsPart: function () { // get the objects of the cards used in the current selection
        return this.normalizedCards.slice(this.cardPosIni, this.cardPosFinal)
      },
      cardsPerSlide: function () { // through the amount of slides we know the number of cards in each slide
        return Math.ceil(this.normalizedCards.length / this.qtdSlides)
      },
      /*
         As the amount of slides is dynamic this can become
         very large fast, so it's best to only load the slide
         visible, the previous and the next
      */
      cardPosIni: function () { // computes index where starts the current selection of cards in the array
        return (this.slide - 1) * this.cardsPerSlide
      },
      cardPosFinal: function () { // computes index where the current selection of cards in the array ends
        return ((this.slide - 1) + 1) * this.cardsPerSlide
      },
      prev: function () { // get the previous slide (if the first one is the last one)
        return this.slide === 1 ? this.qtdSlides : (this.slide - 1)
      },
      next: function () { //get the next slide (if the last one is the first one)
        return this.slide === this.qtdSlides ? 1 : (this.slide + 1)
      },
      dots: function () { // the dots of the q-carousel have 24px, up to 2 lines of dots are tolerated, above that text
        return this.slideHeight <= 48
      }
    },
    methods: {
      swipe: function (slide) { // in the event of swipe assigns the change of the index of the visible slide
        this.slide = slide + 1
      },
      resize: function (size) { // when the window resizes modifies the width value that causes change in qtdSlides
        this.qtdSlides = size.width
      },
      promiseEachArray: function (arr, size) {
        /*
         Traverses the array of vnode objects (html elements)
         and verifies that the width of all is 24px, this indicates that all
         dots have been rendered, before that the dots are words, so
         the width is larger
        */
        return new Promise((resolve, reject) => {
          let len = arr.length
          for (let i = 0; i < len; i++) {
            if (arr[i].clientWidth > size) {
              reject(new Error('erro eachArray'))
            }
          }
          resolve(true)
        })
      },
      promiseSleep: function (timeout) { // wait timeout milliseconds
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            resolve(true)
          }, timeout)
        })
      },
      promiseRender: function (arr, size) {
        /*
         Receives the address of the array of vnode objects
         and standard dots height after rendering
        */
        let _this = this
        return new Promise(async (resolve, reject) => {
          let out = false
          do {
            try {
              await _this.$nextTick() // wait for the end of the next action in the row of nodejs (not necessarily the end of the render), otherwise, catch
              await _this.promiseEachArray(arr, size) // waits for verification if all dots have been rendered, otherwise catch
              out = true
              resolve(true)
            }
            catch (error) {
              out = false
              try {
                /*
                   If it is not rendered, wait 100ms
                   and check again
                */
                await _this.promiseSleep(100)
              }
              catch (error) {}
            }
          } while (out)
          reject(new Error('erro promiseRender'))
        })
      },
      waitRender: async function () {
        try {
          /*
           passes the address of the dots array and the default height of the dots and
           waits until the end of rendering
          */
          await this.promiseRender(this.$el.lastChild.lastChild.children[3].firstChild.children, 24)
        }
        catch (error) {}
        finally {
          /*
             Okay, dots rendering was done
             takes the height of div hosting the dots
          */
          this.slideHeight = this.$el.lastChild.lastChild.children[3].firstChild.clientHeight
          if (this.firstLoad) { // first loading case changes status
            this.firstLoad = false
          }
        }
      }
    },
    watch: {
      /*
         Only two things change the amount of dots
         1) resize in screen width
         2) Change in the array of slides passed as prop by the parent of this component
      */
      width: function () { // 1)
        this.waitRender()
      },
      qtdSlides: function () { // 2)
        this.waitRender()
      }
    },
    mounted: function () {
      /* the verification should be performed here,
       because the mounted does not ensure that the rendering is finished,
       only the assembly of the object from the point of view of the vue */
      this.waitRender()
      // Resize event must be added here to avoid incorrect triggers during rendering
      this.$refs.resizeWindow.$on('resize', this.resize)
    }
  }
</script>

<style lang="stylus">
  @import '~src/themes/app.variables.styl';
  .carouselCards .q-carousel-track{
    height: auto!important;
  }
  .carouselCards .q-carousel-dots .q-icon{
    color: $secondary!important;
  }
  .hideDots .q-carousel-inner .q-carousel-toolbar{
    position: absolute!important;
    top: 0!important;
    visibility: hidden!important;
  }
  .carouselCards .q-carousel-toolbar{
    position: static!important;
    visibility: visible!important;
  }
  .carouselCards .q-carousel-toolbar,
  .carouselCards .q-carousel-dots,
  .carouselCards .q-carousel-dots .q-icon{
    opacity: 1!important;
  }
  @media screen and (max-width: 1199px){
    .carouselCards .q-carousel-toolbar{
      padding-left: 30px;
      padding-right: 30px;
    }
    .carouselCards .carouselCardsPagination{
      padding: 8px 30px!important;
    }
  }
  @media screen and (min-width: 1200px){
    .carouselCards .q-carousel-toolbar{
      padding-left: calc((100% - 1140px)/2)!important;
      padding-left: -webkit-calc((100% - 1140px)/2)!important;
      padding-left: -moz-calc((100% - 1140px)/2)!important;
      padding-left: -o-calc((100% - 1140px)/2)!important;
      padding-left: -ms-calc((100% - 1140px)/2)!important;

      padding-right: calc((100% - 1140px)/2)!important;
      padding-right: -webkit-calc((100% - 1140px)/2)!important;
      padding-right: -moz-calc((100% - 1140px)/2)!important;
      padding-right: -o-calc((100% - 1140px)/2)!important;
      padding-right: -ms-calc((100% - 1140px)/2)!important;
    }
    .carouselCards .carouselCardsPagination{
      padding: 8px calc((100% - 1140px)/2)!important;
      padding: 8px -webkit-calc((100% - 1140px)/2)!important;
      padding: 8px -moz-calc((100% - 1140px)/2)!important;
      padding: 8px -o-calc((100% - 1140px)/2)!important;
      padding: 8px -ms-calc((100% - 1140px)/2)!important;
    }
  }

  @media screen and (max-width: 1219px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 26px!important;
      height: 26px!important;
      font-size: 18px!important;
    }
  }
  @media screen and (min-width: 1220px) and (max-width: 1283px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 32px!important;
      height: 32px!important;
      font-size: 21px!important;
    }
  }
  @media screen and (min-width: 1284px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 42px!important;
      height: 42px!important;
      font-size: 32px!important;
    }
  }

  .carouselCards .q-carousel-left-button,
  .carouselCards .q-carousel-right-button{
    padding: 0!important;
  }
  @media screen and (max-width: 1199px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: 30px!important;
      width: 30px!important;
    }
  }
  @media screen and (min-width: 1200px) and (max-width: 1283px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: calc((100% - 1140px)/2)!important;
      max-width: -webkit-calc((100% - 1140px)/2)!important;
      max-width: -moz-calc((100% - 1140px)/2)!important;
      max-width: -o-calc((100% - 1140px)/2)!important;
      max-width: -ms-calc((100% - 1140px)/2)!important;

      width: calc((100% - 1140px)/2)!important;
      width: -webkit-calc((100% - 1140px)/2)!important;
      width: -moz-calc((100% - 1140px)/2)!important;
      width: -o-calc((100% - 1140px)/2)!important;
      width: -ms-calc((100% - 1140px)/2)!important;
    }
  }
  @media screen and (min-width: 1284px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: 70px!important;
      width: 70px!important;
    }
  }
  .carouselCards .q-carousel-left-button .q-icon,
  .carouselCards .q-carousel-left-button .q-icon:active,
  .carouselCards .q-carousel-left-button .q-icon:visited,
  .carouselCards .q-carousel-left-button .q-icon:focus,
  .carouselCards .q-carousel-right-button .q-icon,
  .carouselCards .q-carousel-right-button .q-icon:active,
  .carouselCards .q-carousel-right-button .q-icon:visited,
  .carouselCards .q-carousel-right-button .q-icon:focus{
    opacity: 0.75!important;
    padding: 0!important;
    color: $primary!important;
    background-color: $secondary!important;
  }
  .carouselCards .q-carousel-left-button .q-icon:hover,
  .carouselCards .q-carousel-right-button .q-icon:hover{
    opacity: 1!important;
  }
  .carouselCards .carouselCardsPagination{
    font-family: $fprimary!important;
    font-size: 18px!important;
    color: $tsecondary!important;
    text-align: center!important;
  }
</style>

Thanks so much for your time. If I get you right, the div you want to get the height of may or may not be loaded when the mounted hook is triggered. So you have set up a way of checking after every DOM update if the dots have been rendered and waiting 100ms before checking again.

In my case, I wanted to watch the size of an image to know when it changed from 0 to its actual size as a way of knowing it had loaded. The mounted and updated hooks detected the img element but did not detect when the size changed. using watch and computed were also ineffective in determining the size change. I ended up using an interval which checks image size every 10ms and stops when the size is not 0.

Looking at your solution, I think $nextTick() and a timeout will be a better alternative to the interval. I’m going to give it a try

In short this is, but the checks only occur in the first load and later when screen resize occurs and when the parent component passes a different amount of slides via prop.

1 Like

Thanks for asking, I found a mistake here, I’m fixing.

The logic remains the same, but there were errors in the while loop stopping conditions.

Tolerances of 1px were added for more and less in the method that checks the width of the array, because in some cases the dots have 24. (some Decimal) px and this becomes 25px in the clientWidth.

Empty array checking was added in this same method.

And a very important observation when working with promisses, use return in their resolve and reject methods, because they change the promisse status, but do not abort the execution of the method where they are included, so they will execute the code until the end or until the next return.

Due to this problem, my array was being fully covered, even when errors occur.

The nextTrick has been removed, as it does not influence the data I want to change, but causes the beforeMount and Mounted hooks to be fired.

A check for the wait render to be executed only once on the hook mounted has been added.

Fixed code:

<template>
  <div
    class="row justify-center"
  >
    <q-window-resize-observable
      ref="resizeWindow"/>
    <l-slide-cards
      v-if="qtdSlides <= 1"
      :load="true"
      :cards="cardsPart"/>
    <q-carousel
      v-else
      infinite
      arrows
      dots
      @slide="swipe"
      :id='_uid'
      :class="[
        'carouselCards',
        {'hideDots': firstLoad || !dots}
      ]"
    >
      <l-slide-cards
        slot="slide"
        v-for="i in qtdSlides"
        :key="i"
        :load="(slide == i || prev == i || next == i)"
        :cards="cardsPart"
        class="col"/>
      <div
        v-if="!firstLoad && !dots"
        class="carouselCardsPagination"
      >
        {{slide}} de {{qtdSlides}}
      </div>
    </q-carousel>
  </div>
</template>

<script>
  import {
    QCarousel,
    dom,
    QWindowResizeObservable,
    QPagination
  } from 'quasar'
  import LSlideCards from './slideCards'
  export default {
    components: {
      QCarousel,
      LSlideCards,
      QWindowResizeObservable,
      QPagination
    },
    props: {
      cards: {
        type: Array,
        required: false,
        default: function () {
          let c = Array(368).fill(null).map(function () {
            return {}
          })
          c._objPadrao = true
          return c
        }
      }
    },
    data () {
      return {
        slide: 1,
        width: dom.width(window),
        firstLoad: true,
        slideHeight: 0
      }
    },
    computed: {
      normalizedCards: function () {
        let c = []
        c._fill = function (length) {
          let size = length === 0 ? 100 : length
          return Array(size).fill(null).map(function () {
            return {}
          })
        }

        return this.normalizeProp(this.cards.slice(0, 1000), c, true)
      },
      qtdSlides: {
        get: function () {
          if (this.width > 991) {
            return Math.ceil(this.normalizedCards.length / 4)
          }
          else if (this.width > 767) {
            return Math.ceil(this.normalizedCards.length / 2)
          }
          else {
            return this.normalizedCards.length
          }
        },
        set: function (width) {
          this.width = width
        }
      },
      cardsPart: function () {
        return this.normalizedCards.slice(this.cardPosIni, this.cardPosFinal)
      },
      cardsPerSlide: function () {
        return Math.ceil(this.normalizedCards.length / this.qtdSlides)
      },
      cardPosIni: function () {
        return (this.slide - 1) * this.cardsPerSlide
      },
      cardPosFinal: function () {
        return ((this.slide - 1) + 1) * this.cardsPerSlide
      },
      prev: function () {
        return this.slide === 1 ? this.qtdSlides : (this.slide - 1)
      },
      next: function () {
        return this.slide === this.qtdSlides ? 1 : (this.slide + 1)
      },
      dots: function () {
        return (this.slideHeight <= 48)
      }
    },
    methods: {
      swipe: function (slide) {
        this.slide = slide + 1
      },
      resize: function (size) {
        this.qtdSlides = size.width
      },
      promiseEachArray: function (arr, size) {
        return new Promise(function (resolve, reject) {
          let len = arr.length
          if (len === 0) return reject(new Error('erro eachArray [array vazio]'))
          for (let i = 0; i < len; i++) {
            // added tolerance because in some cases it renders like 24.(some Decimal)px
            if (arr[i].clientWidth > (size + 1) || arr[i].clientWidth < (size - 1)) {
              /*
                 necessary return because despite execution of the reject
                 change the status of the promise the code of that function continues
                 running until the end or until you find a return.
              */
              return reject(new Error('erro eachArray [ainda renderizando]'))
            }
          }
          return resolve(true)
        })
      },
      promiseSleep: function (timeout) {
        return new Promise((resolve, reject) => {
          setTimeout(() => {
            return resolve(true)
          }, timeout)
        })
      },
      promiseRender: function (arr, size) {
        let _this = this
        return new Promise(async (resolve, reject) => {
          let repeat = false
          do {
            try {
              /*
                 do not use nextTrick here, because it does not include anything
                 which needs to be changed here and still breaks
                 hooks beforeMount and Mounted repeatedly
              */
              await _this.promiseEachArray(arr, size)
              return resolve(true)
            }
            catch (error) {
              repeat = true
              try {
                /*
                   If it is not rendered, wait 100ms
                   and check again
                */
                await _this.promiseSleep(100)
              }
              catch (error) {}
            }
          } while (repeat)
          return reject(new Error('erro promiseRender'))
        })
      },
      waitRender: async function () {
        try {
          await this.promiseRender(this.$el.lastChild.lastChild.children[3].firstChild.children, 24)
        }
        catch (error) {}
        finally {
          this.slideHeight = this.$el.lastChild.lastChild.children[3].firstChild.clientHeight
          if (this.firstLoad) {
            this.firstLoad = false
          }
        }
      }
    },
    watch: {
      width: function () {
        this.waitRender()
      },
      qtdSlides: function () {
        this.waitRender()
      }
    },
    mounted: function () {
      if (this.firstLoad) {
        this.waitRender('mounted')
        this.$refs.resizeWindow.$on('resize', this.resize)
      }
    }
  }
</script>

<style lang="stylus">
  @import '~src/themes/app.variables.styl';
  .carouselCards .q-carousel-track{
    height: auto!important;
  }
  .carouselCards .q-carousel-dots .q-icon{
    color: $secondary!important;
  }
  .hideDots .q-carousel-inner .q-carousel-toolbar{
    position: absolute!important;
    top: 0!important;
    visibility: hidden!important;
  }
  .carouselCards .q-carousel-toolbar{
    position: static!important;
    visibility: visible!important;
  }
  .carouselCards .q-carousel-toolbar,
  .carouselCards .q-carousel-dots,
  .carouselCards .q-carousel-dots .q-icon{
    opacity: 1!important;
  }
  @media screen and (max-width: 1199px){
    .carouselCards .q-carousel-toolbar{
      padding-left: 30px;
      padding-right: 30px;
    }
    .carouselCards .carouselCardsPagination{
      padding: 8px 30px!important;
    }
  }
  @media screen and (min-width: 1200px){
    .carouselCards .q-carousel-toolbar{
      padding-left: calc((100% - 1140px)/2)!important;
      padding-left: -webkit-calc((100% - 1140px)/2)!important;
      padding-left: -moz-calc((100% - 1140px)/2)!important;
      padding-left: -o-calc((100% - 1140px)/2)!important;
      padding-left: -ms-calc((100% - 1140px)/2)!important;

      padding-right: calc((100% - 1140px)/2)!important;
      padding-right: -webkit-calc((100% - 1140px)/2)!important;
      padding-right: -moz-calc((100% - 1140px)/2)!important;
      padding-right: -o-calc((100% - 1140px)/2)!important;
      padding-right: -ms-calc((100% - 1140px)/2)!important;
    }
    .carouselCards .carouselCardsPagination{
      padding: 8px calc((100% - 1140px)/2)!important;
      padding: 8px -webkit-calc((100% - 1140px)/2)!important;
      padding: 8px -moz-calc((100% - 1140px)/2)!important;
      padding: 8px -o-calc((100% - 1140px)/2)!important;
      padding: 8px -ms-calc((100% - 1140px)/2)!important;
    }
  }

  @media screen and (max-width: 1219px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 26px!important;
      height: 26px!important;
      font-size: 18px!important;
    }
  }
  @media screen and (min-width: 1220px) and (max-width: 1283px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 32px!important;
      height: 32px!important;
      font-size: 21px!important;
    }
  }
  @media screen and (min-width: 1284px){
    .carouselCards .q-carousel-left-button .q-icon,
    .carouselCards .q-carousel-right-button .q-icon{
      width: 42px!important;
      height: 42px!important;
      font-size: 32px!important;
    }
  }

  .carouselCards .q-carousel-left-button,
  .carouselCards .q-carousel-right-button{
    padding: 0!important;
  }
  @media screen and (max-width: 1199px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: 30px!important;
      width: 30px!important;
    }
  }
  @media screen and (min-width: 1200px) and (max-width: 1283px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: calc((100% - 1140px)/2)!important;
      max-width: -webkit-calc((100% - 1140px)/2)!important;
      max-width: -moz-calc((100% - 1140px)/2)!important;
      max-width: -o-calc((100% - 1140px)/2)!important;
      max-width: -ms-calc((100% - 1140px)/2)!important;

      width: calc((100% - 1140px)/2)!important;
      width: -webkit-calc((100% - 1140px)/2)!important;
      width: -moz-calc((100% - 1140px)/2)!important;
      width: -o-calc((100% - 1140px)/2)!important;
      width: -ms-calc((100% - 1140px)/2)!important;
    }
  }
  @media screen and (min-width: 1284px){
    .carouselCards .q-carousel-left-button,
    .carouselCards .q-carousel-right-button{
      max-width: 70px!important;
      width: 70px!important;
    }
  }
  .carouselCards .q-carousel-left-button .q-icon,
  .carouselCards .q-carousel-left-button .q-icon:active,
  .carouselCards .q-carousel-left-button .q-icon:visited,
  .carouselCards .q-carousel-left-button .q-icon:focus,
  .carouselCards .q-carousel-right-button .q-icon,
  .carouselCards .q-carousel-right-button .q-icon:active,
  .carouselCards .q-carousel-right-button .q-icon:visited,
  .carouselCards .q-carousel-right-button .q-icon:focus{
    opacity: 0.75!important;
    padding: 0!important;
    color: $primary!important;
    background-color: $secondary!important;
  }
  .carouselCards .q-carousel-left-button .q-icon:hover,
  .carouselCards .q-carousel-right-button .q-icon:hover{
    opacity: 1!important;
  }
  .carouselCards .carouselCardsPagination{
    font-family: $fprimary!important;
    font-size: 18px!important;
    color: $tsecondary!important;
    text-align: center!important;
  }
</style>