我正在try 在我的Reaction项目中实现流行的Pinterest砖石网格视图.我知道网上有无数的资源可以帮助我做到这一点,但我找不到一个适合我的用例.

我的 case 是:

我有一个Reaction组件,可以有三种预先设定的尺寸:小号、中号和大号.此组件的每个变体在桌面上的宽度均为200px,在移动设备上的宽度为${windowSize.Current[0]*0.45}px.

每个组件的不同之处在于,小卡的预置高度为160px,中卡为250px,大卡为320px.

下面是动态卡片react 的一些片段:

<div className='LiveRoomCard' style={{
                        border: props.liveroom.joined ? '1.5px solid #d500f9' : props.liveroom.pinned ? '1.5px solid black' : '1px solid lightgray',
                        marginLeft: isMobile ? `${windowSize.current[0]*0.008}vw` : '2.5vw',
                        width: isMobile ? `${windowSize.current[0]*0.45}px` : '200px',
                        maxWidth: isMobile ? `${windowSize.current[0]*0.45}px` : '200px',
                        height: props.size === 'small' ? '160px' : props.size === 'medium' ? '250px' : '320px',
                        maxHeight: props.size === 'small' ? '160px' : props.size === 'medium' ? '250px' : '320px'
        }}>
            <div className='LiveRoomCard-image' style={{
                    minHeight: props.size === 'small' ? '70px' : props.size === 'medium' ? '150px' : '200px',
                    maxHeight: props.size === 'small' ? '70px' : props.size === 'medium' ? '150px' : '200px'
                }}>
                <img src={props.liveroom.backgroundImage ?? cardPlaceHolder} alt='post bg image' style={{
                    minHeight: props.size === 'small' ? '70px' : props.size === 'medium' ? '150px' : '200px',
                    maxHeight: props.size === 'small' ? '70px' : props.size === 'medium' ? '150px' : '200px'
                }}/>
            </div>
                  //rest of body that is unimportant for this question
 </div>

这是css的外观:

.LiveRoomCard {
    display: flex;
    flex-direction: column;
    background-color: white;
    height: 320px;
    max-height: 320px;
    border-radius: 15px;
    box-shadow: 2px 2px 2px grey;
    margin-top: 4%;
    margin-left: 2.5vw; /*overriden in react component*/
}
.LiveRoomCard-image {
    flex: 1; /* Allow the content to take remaining top space. The LiveRoomCard-details div then takes the little at the bottom */
}
.LiveRoomCard-image img{
    min-width: 100%;
    max-width: 100%;
    object-fit: cover; /* This maintains aspect ratio and covers the container */
    border-radius: 15px 15px 0 0;
}

现在,这个卡片网格位于父组件中.父组件对每个卡的大小进行随机分配:

const postCardSizes = ["small", "medium", "large"];
    <div className='Liverooms-container' style={{height: isMobile ? '500px' : '700px'}}>
    {rooms.map(liveroom => {               
    
                        const sizeIndex = Math.floor(Math.random() * postCardSizes.length);
                        const randomSize = postCardSizes[sizeIndex];
                        return (
                            <div className='Liverooms-card-container' key={liveroom.roomId}>
                                <LiveRoomCard key={liveroom.roomId} size={randomSize}/>
                            </div>)
                    })}
    </div>

下面是css的外观:

.Liverooms-container {
    display: flex;
    flex-direction: row;
    flex-wrap: wrap;

    overflow-y: scroll;
    overflow-x: scroll;
}
.Liverooms-card-container {
    height: fit-content;
}

I feel like I am almost there as this is what my output looks like on desktop: Desktop Image

And this is what it looks like on mobile:Mobile image

不用说,我正在努力实现的是消除行之间不必要的间距,并实现由三个预先固定高度的组件组成的真正的砖石网格.这是可以实现的吗?还是我必须彻底改变我的方法?

推荐答案

在我们理解/阅读Masonry布局的意义上,从UX POV(或者,不那么专业的术语,作为布局的读者)来说,并不是真正存在的.

Yes, technically, it could be achieved using flex and changing flex-direction to column or with CSS columns property, but we'd need a script to calculate each item's position before-hand and then set the container's height precisely so the items wrap as intended.
Also, with any of these approaches, the order of the items in DOM would be different than what is perceived as "natural flow" (we'd have all items in column A before all items in column B), and so on...

这可能看起来不是什么大问题,但当我们调整容器大小并需要添加或删除列时,它很快就会成为一个问题(当发生这种情况时,基本上不可能跟踪每个项的位置,如果布局也需要滚动,用户将无法跟踪项的移动方式,这使得他们很难继续"reading",因为从底部开始的项将跳到顶部,反之亦然).解决此问题的一种方法是在列数更改时动态重新排序项目.不适当的复杂性.

如果我们想要保持"现有项"不变,并在每一列的底部添加新项,则在布局中添加更多项时会出现一组非常相似的问题,上述"solutions"项中的任何一项.

简而言之,试图用flex或CSScolumns来做砖石工程,所有的前提都会变成编码和用户体验的噩梦.

此外,这并不是最初的Pinterest算法的at all%工作方式.相反,代码小得令人惊讶,优雅,而且,我认为,很容易理解.

以下是逻辑步骤:

  1. 将容器的position: relative和所有砖石项目设置为position: absolutedisplay: none(或opacity: 0).所有这些,以及项的宽度,都可以通过css从一开始就完成,然后被内联计算的样式覆盖,或者通过分配一个显示类(或移除一个隐藏的类)来覆盖.

  2. 基于可用容器width计算可用列的数量.

  3. Typically we'd create an empty array for each column, for storing each item's position as we calculate them, although technically we could also use a 2d matrix. Each column should keep a state of its current height, starting from 0.
    Important note: the "columns" are virtual, they don't have a corresponding DOM element. The masonry items should be immediate children of the masonry container. An item's column determines its translateX transform value (see next step).

  4. loop through the masonry items and place them in the shortest left-most column (or right-most, if you prefer). By placing we mean:
    a) calculate the current item's transform: translate(x, y) based on the column's height and index;
    b) update column's height by adding the current item's height and the optional item gap(s) to it; 1
    c) update the masonry DOM container's height to equal to the tallest column's height (+ appropriate gap(s)).
    Having a deterministic and consistent way of placing the next item (vs placing them randomly) might not seem important, but it is, from a UX perspective. We want our users to be able to follow and be able to continue "reading" the content if they refresh or resize the page (which might change the number of columns). It is important to allow them to determine where they need to continue reading from, without undue cognitive effort. Some implementations also keep the current scroll position in state/cache to allow for more intuitive refresh behavior

  5. 上面的循环比人们想象的要快得多,所以通常我们会计算所有项,然后开始实际显示它们.我见过这个脚本的版本,其中更改display/opacity的砖石项目是在循环中处理的,但根据我的估计,如果我们想要在显示项目时进行任何类型的过渡(或令人震惊的动画),最好在计算完所有transform之后再处理它们.

  6. 外部容器应该有overflow: hidden,所以我们不会在窗口/容器调整大小事件上得到奇怪的行为.说到这一点,在调整窗口大小时,最终通过一些限制,我们必须重新运行上述步骤(而不隐藏项目).注在transform属性上放置一个简短的transition在这里非常有效,因为用户可以很容易地想象当列数改变时项的位置.此外,这些转变是有意义的,并不是巨大的 skip .它们是人们在将n列中的项目重新排列为n - 1n + 1列时所做的事情.

听起来可能有很多事情要做和考虑,但它有一定的优雅和简约.一旦完成编码,它就会变得更加明显.

或者,我们可以在项目离开屏幕时关闭它们的不透明度,这样当用户上下滚动时,它就可以重新显示为动画.

非那样做不行.


100-当您处理动态高度的项目时,这一步骤可能会变得棘手,因为只有在呈现项目之后才能确定动态高度.解决方案是在与DOM分离的、与列相同宽度的隐藏容器中呈现项,并对其进行测量.每一项都必须在被放入网格之前进行测量.为了避免由于挂起的前一个项目仍在等待其图像加载而导致呈现项目的延迟,可以使用占位符.然而,这意味着在挂起的项目确实呈现之后,必须重新定位它之后的所有项目(但这很快;测量是最耗费资源的).

Css相关问答推荐

CSS动画与不正确的时间比率

在NextJS/Reaction应用程序中显示更新问题

为什么CSS flex似乎应用了两次内部元素的填充?

如何引用 SCSS 中的现有类?

如何解决 Spotify 嵌入代码中的白色背景错误?

滚动弹性盒

CSS 不要 Select 包含混合内容的 div 中的第一个元素

为什么我的 CSS 转换在 React 18 中不起作用

.SVG 文件对象在锚标签下可点击

重用 CSS 类更改一些属性

antd 中的子菜单项不可滚动

我在 ins 标签中有一个 img 标签,更改 ins 格式覆盖图像的视图

如何仅包装左侧内容

使用 MUI 动态绘制多个边框

Angular Material 中的样式垫 Select

是否可以删除悬停在链接上时出现的手形光标? (或将其设置为普通指针)

我应该使用 CSS :disabled 伪类还是 [disabled] 属性 Select 器,还是见仁见智?

CSS 否定伪类 :not() 用于父/祖先元素

仅针对使用的类优化 Font Awesome

:before 和 ::before 有什么区别?