fraserky
fraserky

Reputation: 93

How do I render my element using v-show correctly?

I have a carousel which only shows when a user opens my accordion menu.

I am using v-show to hide the carousel until it's opened. However, it doesn't render the carousel correctly unless I resize the browser window, the first slide shows but to click through the other slides don't show up.

Web inspect shows on load the translate: transform() empty for slides until the browser window is resized.

I think this is something to do with v-show and how it renders in the DOM, I have seen people using .$nextTick or .$watch to fix similar issues but I don't understand them enough to apply them to my code.

Is there a way for me to solve this issue using v-show with .nextTick() or .watch()?

<div :key="project.id">
  <!-- project header -->
  <li
    class="project accordion columns is-flex vcenter"
    @click="toggleItem"
    style="margin-left: 0; margin-right: 0;"
  >
    <h3 class="project-title column is-one-quarter">{{ project.Name }}</h3>
    <h4 class="project-summary column is-two-thirds">
      {{ project.Summary }}
    </h4>
    <span class="column is-one-half is-gapless project-icon">
    <img
      src="../static/SVG/Circle.svg"
      alt="circle icon for branding & identity"
    />
  </span>
</li>

My hidden content is here, it only shows when the accordion is open using v-show.

<div class="showcase-content columns" v-show="show">
  <p class="project-description column is-one-third">
    {{ project.Description }}
  </p>
  <agile :fade="true">
    <div
      class="image-box column is-two-thirds"
      v-for="image in project.image"
      :key="image.url"
    >
      <img :src="buildImageUrl(image.url)" alt="" />
    </div>
  </agile>
</div>

EDIT I was originally injecting some my accordion in and have refactored the accordion and my code, on the suspicion that injection might not be reactive but the same issue persists. The code on this question is my current code.

<script>
export default {
  name: 'ProjectItem',
  data: function () {
    return {
      show: false,
    }
  },
  props: ['project'],
  methods: {
    toggleItem: function () {
      this.show = !this.show
    },
    buildImageUrl(image) {
      if (!image) return '../static/images/SamB.png'
      return `http://localhost:1337${image}`
    },
  },
}
</script>

Upvotes: 3

Views: 644

Answers (1)

tao
tao

Reputation: 90013

Note: leaving this part of the answer in as it might be useful for anyone with a similar problem. However, it was added when the question didn't specify the slider library. To read the actual answer, skip to after the separator.

  • Using v-show means the carousel is rendered with display: none
  • most carousels are setting the size of their slides when they are init-ed and they update on window.resize

Which means your carousel is init-ing with a height of 0. And it only readjusts on window.resize, regardless of the change to the display property of your v-show element.

A quick and dirty fix is to trigger window.resize when the property controlling v-show changes. I.e:

toggleItem: function () {
  this.show = !this.show;
  window.dispatchEvent(new Event('resize'));
},

But it's dirty and it might also be expensive, depending on what other components/libraries are listening to resize.

A much better solution is to call whatever update method your carousel library exposes when the v-show condition changes.
If you can't figure out how to do that, I suggest adding the carousel library (with version) to the question.

Another potential problem is if the container of the carousel is animated (i.e: you use some type of transition on it). In which case you need to trigger window.resize/carousel.update at the end of the animation, when the container has the correct size. Which is why it would have been ideal to provide a minmal reproducible example.
A(nother) quick and dirty solution for that problem would be to add a transitionend listener on the animated element and dispatch window.resize in that callback, in which case you no longer need anything else. Also, I'd actually place a check on the v-show condition and only dispatch window.resize when it's true. But, again, it's not really clean and it could have side-effects.


Edit: since you use vue-agile, here's your question answered in documentation:

It is also possible to use v-show, but you have to use the reload() method.

Which means this should do it:

<template>
   <agile ref="slider" ... />
</template>

...

methods: {
  toggleItem: function () {
    this.show = !this.show;
    this.$nextTick(() => this.$refs.slider.reload());
  }
  ...
}

A working example here.


Note: Replacing v-show with v-if is an expensive solution, as it means your carousel is added and removed from DOM each time the condition changes. Each time it will rebuild itself and reload the slider images. That's worse than v-show + window.resize solution.

Note: you might want to have a look at the vue wrapper for Swiper, as (arguably) Swiper is better than Slick.

Upvotes: 3

Related Questions