DerYeger / yeger

Monorepo for @yeger/ NPM packages
MIT License
316 stars 24 forks source link

[Question]: How to add elements without re-rendering the whole layout like in your demo ? #212

Closed juyaki closed 1 year ago

juyaki commented 1 year ago

Description

Hello, and thank you for your library.

I am building a web app with Nuxt 3 and using this library because I want to have something like Pinterest to show photos. When the user scroll to the bottom, I fetch more photos and add it to the item array which I pass to the Masonry layout. I took care of not using push but creating a new array everytime like you mentioned in the documentation.

However, everytime I fetch new photos, the layout is completely re-rendering, which gives me this kind of bad UX: https://github.com/DerYeger/yeger/assets/9263713/6e64c427-3475-4b94-a94e-c3ea6038c385

I looked into the existing issues and found this one which is pretty much very similar: https://github.com/DerYeger/yeger/issues/184

However, on your demo page it seems like you are able to add and remove element without having everything rebuilt. (Or is it?) In such case I'm wondering why on my side I can't get it work the same... https://vue-masonry-wall.yeger.eu

Thanks.

Solution

No response

Additional context

Here is the component (PhotoGrid.vue) code in which I use the masonry wall.

<script setup lang="ts">
import { Photo } from '~/shared/types/photo'
import { useDisplay } from 'vuetify'
import { computed } from '#imports'
import { useUserStore } from '~/store/useUserStore'
import PhotoTile from '~/components/PhotoTile.vue'
import { useAuthStore } from '~/store/useAuthStore'

defineProps<{
  photos: Photo[]
  userBookmarkedPhotoIds: string[]
}>()

const { mobile } = useDisplay()
const userStore = useUserStore()
const authStore = useAuthStore()

const isLoggedIn = computed<boolean>(() => authStore.isLoggedIn)
const masonWallColumnCount = computed<number>(() => (mobile.value ? 2 : 5))
</script>

<template>
  <MasonryWall :items="photos" :gap="16" :min-columns="masonWallColumnCount">
    <template #default="{ item }: { item: Photo }">
      <PhotoTile
        :photo="item"
        :is-bookmarked="userBookmarkedPhotoIds.includes(item.photoId)"
        :is-bookmark-button-visible="isLoggedIn"
        @click:add-bookmark="userStore.addBookmarkPhoto(item)"
        @click:delete-bookmark="userStore.deleteBookmarkPhoto(item)"
      />
    </template>
  </MasonryWall>
</template>

<style scoped lang="scss"></style>

Nuxt version: 3.6.5

Preferences

DerYeger commented 1 year ago

Hello,

my demo does rerender the elements on every change.

To mitigate the issue visible in your video, the items (i.e., the component) have to render without any layout shift. By chance, do you have caching disabled in your dev-tools? This might cause the images to flicker upon rerender.

Further, you have to specify the container of the masonry wall that scrolls with the content (this is only relevant if the scrolling container is not the window itself). It looks like the scroll position is being reset in your video, indicating that a scroll container should be set.

juyaki commented 1 year ago

Thanks for your quick reply.

By chance, do you have caching disabled in your dev-tools? This might cause the images to flicker upon rerender. I am not using the devtool, so I don't think the problems comes from here

I tried to setup the scrollContainer as below but I still have the same issue:

<script setup lang="ts">
import { Photo } from '~/shared/types/photo'
import { useDisplay } from 'vuetify'
import { computed, onMounted, ref } from '#imports'
import { useUserStore } from '~/store/useUserStore'
import PhotoTile from '~/components/PhotoTile.vue'
import { useAuthStore } from '~/store/useAuthStore'

defineProps<{
  photos: Photo[]
  userBookmarkedPhotoIds: string[]
}>()

const { mobile } = useDisplay()
const userStore = useUserStore()
const authStore = useAuthStore()

const isLoggedIn = computed<boolean>(() => authStore.isLoggedIn)
const masonWallColumnCount = computed<number>(() => (mobile.value ? 2 : 5))
const scrollContainer = ref<HTMLElement | null>(null)

onMounted(() => {
  scrollContainer.value = document.getElementById('container')
})
</script>

<template>
  <div id="container">
    <MasonryWall
      :items="photos"
      :gap="16"
      :min-columns="masonWallColumnCount"
      :scroll-container="scrollContainer"
    >
      <template #default="{ item }: { item: Photo }">
        <PhotoTile
          :photo="item"
          :is-bookmarked="userBookmarkedPhotoIds.includes(item.photoId)"
          :is-bookmark-button-visible="isLoggedIn"
          @click:add-bookmark="userStore.addBookmarkPhoto(item)"
          @click:delete-bookmark="userStore.deleteBookmarkPhoto(item)"
        />
      </template>
    </MasonryWall>
  </div>
</template>

Am I using the props correctly? You're right though, there seems to be something wrong with my scrollbar 🤔

DerYeger commented 1 year ago

I can't tell which element is actually the container with the scroll bars based on your example, it might be outside the SFC you posted here. I'd need an executable reproduction to help further.

Consider using ref for the scrollContainer though: https://github.com/DerYeger/yeger/blob/1d01dd82b74fe8de295e620d5dcb018619e2126d/docs/vue-masonry-wall-docs/src/app.vue#L16 https://github.com/DerYeger/yeger/blob/1d01dd82b74fe8de295e620d5dcb018619e2126d/docs/vue-masonry-wall-docs/src/app.vue#L69

juyaki commented 1 year ago

Thank you, it seems like there is an issue which is not related with your library. I managed to get rid of the scroll problem by stopping using v-img from vuetify. Sorry for the useless issue, I'm closing it 🙏