naver / egjs-infinitegrid

A module used to arrange card elements including content infinitely on a grid layout.
https://naver.github.io/egjs-infinitegrid/
MIT License
2.23k stars 95 forks source link

MasonryInfiniteGrid: responsive columns with percent width? #464

Closed nimmolo closed 2 years ago

nimmolo commented 2 years ago

I know it is possible to define column size in px, and then there will be as many columns as possible will fit in container.

But is it possible to define column size in % and set a different number of columns for different screen width?

I'm thinking of the way bootstrap flex columns (the layout effect, not the CSS). MasonryIG uses absolute position so implementation is different, but i'd like to get responsive percentage based column effect. (something like col-12 col-md-6 col-lg-4 col-xxl-3)

Currently i'm using something like percentage={true} and column={3}. Maybe param would be column={{0: 1, 460: 2, 998: 3, 1200: 4}}

nimmolo commented 2 years ago

I'm reading trough MasonryInfiniteGrid options again. I'm noticing it seems like maybe i can set column={0} so it will calculate columns from width of first element.

@daybrush - Can MasonryInfiniteGrid use a percentage value for width of first element? If so i could just use Bootstrap responsive column classes on the first element.

Using those Bootstrap column classes, my item elements have

flex: 0 0 auto;
width: 100%;
@(min-width: 768px) width: 50%;
@(min-width: 992px) width: 33.3333333%;
@(min-width: 1400px) width: 25%;

But this is still giving "incorrect" column with calculations, even if I make the gap={0} - MIG is calculating 3 columns instead of 4, 2 instead of 3, etc., with large gaps. The elements do have the correct widths, but the inline positions for the three columns are left: 0%; left: 37.4844%; and left: 74.9689%; which leaves big gaps, where it seems it should be left: 0; left: 25% left:50%; left:75%

Screen Shot 2022-01-07 at 5 15 30 PM

nimmolo commented 2 years ago

Hmm - what is the difference between isEqualSize and isConstantSize? In the documentation it sounds the same. But when I use in a layout,isEqualSize seems to mean width and height of all items is the same. Maybe isConstantSize means only the width is the same?

If i use isConstantSize={true}, the first batch of items still 3-column position as above, but the second batch of added elements comes in with the desired 4-column position. Very unexpected!

Like this: Screen Shot 2022-01-07 at 8 31 05 PM Screen Shot 2022-01-07 at 8 31 15 PM

daybrush commented 2 years ago

@nimmolo

Since the column can be changed dynamically, it must be changed programmatically according to its size.

isConstantSize does not change the size of the item anymore even if it is resized.

isEqualSize means that all items have the same size.

The above problem will be fixed in the next version.

nimmolo commented 2 years ago

@daybrush - I see, Ok, no problem. Maybe should I wait for next version, and work on other things.

But IG does calculate column size (or container size?) correctly for the second load πŸ€”, in last image above. Why do you think IG can correctly calculate column size on the reload load of data, but incorrect on first load? Is there a way I can help pass that calculation of column size (or container size) programatically to IG so it can calculate first column correctly?

daybrush commented 2 years ago

@nimmolo

The reason is not well understood, but I think it is a decimal point issue. Here is the code to fix this problem in the next version

daybrush commented 2 years ago

@nimmolo

Test @egjs/svelte-infinitegrid@4.1.2-beta.4 (beta) version. Thank you :)

nimmolo commented 2 years ago

Thanks @daybrush - I updated to @egjs/svelte-infinitegrid@4.1.2-beta.4.

Issue is still present, at first. But πŸ˜„ if I resize the window, the columns are now correctly calculated. So something is getting better. Responsive changing number of columns at different container size is also correctly calculated, when window is resized.

If window is not resized: It seems that the first calculation of column width is maybe a little narrower, but... still wrong number of columns on load. On reload, the number of columns is again suddenly correct, but position is unpredictable. First load does not re-layout, so there are two areas with different columns.

To help with troubleshooting i can include my most recent code. Let me know if there is anything else I can do to help.

Here is the Svelte template currently:

<MasonryInfiniteGrid
  class="row ig"
  useLoading={true}
  useFirstRender={true}
  gap={0}
  percentage={true}
  column={0}
  items={newItems}
  itemBy={(item) => item.cursor}
  bind:this={ig}
  on:requestAppend={({ detail: e }) => {
    e.wait();
    handleNext();
    e.ready();
    if (newItems.length && pageInfo.hasPreviousPage) {
      items = [...items, ...newItems];
    }
  }}
  let:visibleItems
>
{#each visibleItems as item (item.key)}
  <Item edge={item.data} />
{/each}
</MasonryInfiniteGrid>
{/if}

If i useLoading, there's a separate issue with both beta.3 and beta.4 -- I did not mention before, but maybe relevant. When I scroll to the loading div, the screen and scroll bar "flickers" very fast, and nothing loads. I must scroll backwards and forwards to loading several times to load the new batch.

If I don't useLoading, it doesn't flicker, and mostly does load new batch. However, not always. (For UX i would like to useLoading, but it's not necessary to troubleshoot now.)

CSS

Here is the CSS, standard Bootstrap 5.1 row and column layout, nothing special.

.row {
  --bs-gutter-x: 1.25rem;
  --bs-gutter-y: 0;
  display: flex;
  flex-wrap: wrap;
  margin-top: calc(-1 * var(--bs-gutter-y));
  margin-right: calc(-0.5 * var(--bs-gutter-x));
  margin-left: calc(-0.5 * var(--bs-gutter-x));
}
.row > * {
  flex-shrink: 0;
  width: 100%;
  max-width: 100%;
  padding-right: calc(var(--bs-gutter-x) * 0.5);
  padding-left: calc(var(--bs-gutter-x) * 0.5);
  margin-top: var(--bs-gutter-y);
}
.col-12 {
  flex: 0 0 auto;
  width: 100%;
}
@(min-width: 768px) {
  .col-md-6 {
    flex: 0 0 auto;
    width: 50%;
  }
}
@(min-width: 992px) {
  .col-lg-4 {
    flex: 0 0 auto;
    width: 33.33333333%;
  }
}
@(min-width: 1400px) {
  .col-xxl-3 {
    flex: 0 0 auto;
    width: 25%;
  }
}
.ig {
  height: 600px; // initial height, gets overwritten
}
daybrush commented 2 years ago

@nimmolo

useLoading is a property to use the loading bar. You don't need to use it here.

How to add :global(...) to use InfiniteGrid class in Svelte?

:global(:ig)
.global(.row)
.global(.row) > *
nimmolo commented 2 years ago

@daybrush β€” Thanks for the info on useLoading - I do want to use it eventually.

The columns are still incorrectly calculated on load β€” only when the browser window is resized, they are recalculated correctly.

My CSS is loaded OK and applied to the container and items, there's no problem there. I included the CSS above so you could reproduce the problem for testing, maybe it can help with troubleshooting. Maybe easier to just include the Bootstrap stylesheet from the repository. Let me know if there's anything i can do to help, or if you need me to make a demo or REPL.

Also, please let me know if you want me to play around with the beta4 code locally, i haven't been able to figure out where it calculates the column size yet. Do you believe the bug is in the core IG code, or only the Svelte part? If you can maybe point me to the place in the code where this calculation done, i'm happy to try to help find a fix. I appreciate all the work you guys are doing.

(I have all my global styles in a global.scss file, and they are imported by the __layout.svelte at the top level of the app. I don't have any of them in the component.)

daybrush commented 2 years ago

@nimmolo

What I can think of is that the InfiniteGrid makes position: absolute, so the width may be different when it is not absolute. Why don't you check this part out?

nimmolo commented 2 years ago

@daybrush Thanks - i'm checking it out in FF, Chrome, Safari:

Each item width is defined in item CSS class as width: 25%, but on first render, IG is positioning every item in the second column at left: 37.3894%, not left: 25%. Note that gap={0}.

When window is resized, layout is "corrected", but second item is positioned left: 25.0053%, third item at left: 50.0106%, fourth item at left: 75.0159%. I think this is the problem, the fractional difference is small, but maybe it's too much on first render. What do you think?

In any case, position: absolute means IG is calculating one of the widths incorrectly, either for the grid container or the first item, because every item is positioned according to the ratio between these two calculations. is that right?

I want to help find where it's getting that calculation "wrong". Is there a place in IG code i can console.log IG's caclucated width for the grid container and the first item? this should tell me what it's calculating. I've noticed cssRect and rect properties, but i don't see the central place where I should log these to examine.

update: I tried transform instead of position: absolute - same. Columns are incorrect, then when window is resized, they are "correct". However, transform uses px instead of %

daybrush commented 2 years ago

@nimmolo

center = item.cssRect.left + item.cssRect.width / 2

It seems that the width has changed strangely as it is changed to position:absolute.

How about removing display:flex?

Do you have an address where I can test?

nimmolo commented 2 years ago

@daybrush thanks - I removed display:flex and negative margin on grid container, no change. I also tried disabling lazy-load image, but still no change.

I don't have a staging site set up yet. πŸ˜” Only local dev site. Maybe i can make a simpler REPL for you to check out? Using bootstrap style...

It still seems that item.cssRect.width is being incorrectly calculated. How is this being calculated? element.outerWidth?

daybrush commented 2 years ago

@nimmolo

oh sorry It is item.rect.width. However, this may also be calculated incorrectly. rect is the position and size obtained from the element. cssRect is the position and size registered in style.

It is calculated as item.offsetWidth. It would be nice if you could make a repl.

nimmolo commented 2 years ago

@daybrush Ok thank you. I will make a repl, it may take a couple days. I have never made before.

I can upload the beta4 version to the repl?

daybrush commented 2 years ago

@nimmolo

yes. beta.4 Thank you :)

nimmolo commented 2 years ago

@daybrush ~~I just realized I don't know. How do i include the files for infinitegrid to the repl? Or do i just put import { MasonryInfiniteGrid } from "@egjs/svelte-infinitegrid"; and it will work~~

Yes. I got it. Nevermind!

nimmolo commented 2 years ago

@daybrush Repl was easy! :) Here it is

Be sure to make the window wider before the repl loads, to see incorrect columns.

Screen Shot 2022-01-13 at 9 29 39 PM

If you resize after load, columns will be always correct, also for me locally. It's first load that is the problem.

Screen Shot 2022-01-13 at 9 30 54 PM

Thank you for checking it out, i appreciate it. πŸ™ Edit: Just realized maybe you can't easily examine the CSS this way. I made a comment referencing only the rules that are used, so you can override more easily.

daybrush commented 2 years ago

@nimmolo

oh thank you The reason was confirmed.

The container size was obtained incorrectly. The reason is that the container size was obtained before the bootstrap.css load was completed.

    margin-right: calc(-.5 * var(--bs-gutter-x));
    margin-left: calc(-.5 * var(--bs-gutter-x));

I'll try to solve this way.

I am trying to use ResizeObserver.

https://developer.mozilla.org/en-US/docs/Web/API/ResizeObserver

Will you have any problems?

nimmolo commented 2 years ago

@daybrush Sounds good. Thank you for figuring that out, it makes sense.

ResizeObserver seems great, and I don't need to support IE...i'm also using IntersectionObserver for lazy loading, similar api.

nimmolo commented 2 years ago

@daybrush did you mean i should try ResizeObserver in my code? (or maybe you're going to try to implement in Infinite Grid...)

Let me know if i can help experiment, i'm happy to try a PR, or test anything.

daybrush commented 2 years ago

@nimmolo

Today or tomorrow we will add the option.

nimmolo commented 2 years ago

πŸ‘ Awesome

daybrush commented 2 years ago

@nimmolo

Try @egjs/svelte-infinitegrid@4.2.0-beta.2 version

And use prop useResizeObserver={true}

nimmolo commented 2 years ago

@daybrush - thank you πŸ™

EDIT - columns are working on my local app, now - they are calculated correctly on first load. (There seems to be a difference in infinite loading - infinite works but threshold is acting differently. I had to increase the threshold to 200, but this may be related to something in my CSS, I will test some more.)

However, I am not sure if useResizeObserver quite fixed it πŸ’―. See repl here - if you open the right side of the REPL window wide before everything loads, columns are still computed incorrectly. I updated the repl with the new version and the param useResizeObserver. If you resize the window after load, columns are recomputed ok, but this was also true before β€” with version 4.1.2.beta4, columns were correct after resize.

It's during the first render on the repl, container width is miscalculated, maybe again before bootstrap CSS loads. I can't tell why it's different on the REPL.

daybrush commented 2 years ago

Try @egjs/svelte-infinitegrid@4.2.0-beta.5 version

In repl, only latest is applied, so it seems that the beta version cannot be used. It seems to work fine when I test it locally.

nimmolo commented 2 years ago

Thank you @daybrush - Layout seems to work correctly on my local install, columns are correct in the first render. I understood what you said about Repl using only latest released version.

nimmolo commented 2 years ago

I believe the @egjs/svelte-infinitegrid@4.2.0-beta.5 version solves the issue. Thanks!

stale[bot] commented 2 years ago

This issue/PR has been automatically marked as stale because it has not had any update (including commits, comments, labels, milestones, etc) for 30 days. It will be closed automatically if no further update occurs in 7 day. Thank you for your contributions!

ν•œκΈ€ 이 이슈/PR은 commits, comments, labels, milestones λ“± 30일간 ν™œλ™μ΄ μ—†μ–΄ μžλ™μœΌλ‘œ stale μƒνƒœλ‘œ μ „ν™˜λ˜μ—ˆμŠ΅λ‹ˆλ‹€. 이후 7일간 ν™œλ™μ΄ μ—†λ‹€λ©΄ μžλ™μœΌλ‘œ λ‹«νž μ˜ˆμ •μž…λ‹ˆλ‹€. ν”„λ‘œμ νŠΈ κ°œμ„ μ— κΈ°μ—¬ν•΄μ£Όμ…”μ„œ κ°μ‚¬ν•©λ‹ˆλ‹€.