rachelandrew / cssgrid-ama

Ask me anything about CSS Grid Layout
328 stars 12 forks source link

CSS Grid auto height of elements? (Pinterest Layout) #19

Closed wesbos closed 7 years ago

wesbos commented 7 years ago

first- thanks so much for your work on everything grid!

I'm looking to make a layout like this.

Is this possible with grid? Rather than have the row's items stretch to the height of the largest one, I'd like the one underneath it to come up. I've looked at all your examples and I've modified this one to have one high item:

http://codepen.io/wesbos/pen/LxRaye

rachelandrew commented 7 years ago

That's not something grid is designed for. Grid is two dimensional so you are always working in both rows and columns at the same time. You can't use grid to do a "masonry" style layout like that. You could place items in that way if you had a lot of rows and managed how many each spanned, but you can't use auto-placement to get that kind of layout.

keithjgrant commented 7 years ago

Yeah, that layout looks very flexboxy to me, with flex-direction: column and flex-wrap: wrap. The tricky part would be determining the appropriate height for the flex container, since it needs to be constrained in order to produce wrapping.

edit of course, that changes the order of the items

rachelandrew commented 7 years ago

Neither flexbox or Grid can really do this masonry thing, it's a bit of a different type of layout.

I'd love to come up with a solution for it, I think it fits better into flexbox type layout than grid type layout as it is a content out type of construction. However we don't have the tools currently to do it. That doesn't mean that a future level couldn't include such a feature, and that's part of why I'm collecting these use cases and seeing which things we can't do. As that's how we figure out what we need next.

rachelandrew commented 7 years ago

And with grid you can fake that sort of layout by spanning rows, so you can get the look of that - just not with auto-placement. I've got a bit of that going on here: http://codepen.io/rachelandrew/pen/QKwvxJ

So if you know how many columns you have you can do quite a lot by spanning different numbers of rows.

wesbos commented 7 years ago

thanks a lot for the response - sad to hear we still can't do this layout with just CSS.

I think CSS columns come closest to it, but the content goes top to bottom rather than left to right. I wonder if maybe a direction on CSS columns would be something worth thinking about.

Thanks again!

chriscoyier commented 7 years ago

It does seem like CSS columns is the closest thing. In the current design of CSS-Tricks, I went for columns because I wanted to give the cards/masonry look a go, but keeping it as simple as possible. By far the biggest complaint is the up-down chronological-ness rather than left-right. Maybe someday column-direction can be a thing, or something.

rachelandrew commented 7 years ago

I'm not sure I like column-direction as a column kinda needs to be ... a column :D especially once you add to the mix the fact that not all languages run left to right and top to bottom.

However I do think this kind of layout needs to be a thing in CSS so these ideas and use cases are excellent and very useful.

inorganik commented 7 years ago

Here's a pen doing it with flexbox and only 12 lines of javascript. I know the idea was not using javascript, but I think 12 lines is a small price to pay.

abusedmedia commented 7 years ago

@inorganik, even tough I do like jQuery, you cannot say it's 12 line of js

inorganik commented 7 years ago

@abusedmedia I removed the jQuery, now it's 13 lines 😃

rachelandrew commented 7 years ago

@programmer5000-com I think you misunderstand what this is. This is just a Q&A about grid, the Issue is closed because the original Q was answered. Linked in the thread is an issue on the CSS WG drafts if you wanted to follow that.

luck2011 commented 7 years ago

yes, column-direction please.

carolineportugal commented 7 years ago

yes, different item-height allowed in same row please.

champramentio commented 6 years ago

You can check this : http://w3bits.com/css-masonry/. Works like a charm for me

luck2011 commented 6 years ago

@champramentio CSS3 grid was created for static lists, like the link you gave. When new items are added into the list(like paginations), it will fail.

janosh commented 6 years ago

Here's @inorganik's solution rewritten in modern JS (shaves off 4 lines, down to 9):

const numCols = 3
const colHeights = Array(numCols).fill(0)
const container = document.getElementById('container')
Array.from(container.children).forEach((child, i) => {
  const order = i % numCols
  child.style.order = order
  colHeights[order] += parseFloat(child.clientHeight)
})
container.style.height = Math.max(...colHeights) + 'px'

Using this approach I had a lot of trouble with the flex items not respecting the parent container width, however, so I really wanted to make it happen with grid instead. If you're using react and styled-components, the suggested solution by @rachelandrew, i.e. having lots of small rows and managing how many each grid item spans, is quite easy to implement:

// masonry/index.js
import React, { Component, createRef } from 'react'

import { Parent, Child } from './styles'

export default class Masonry extends Component {
  static defaultProps = {
    rowHeight: 40, // in pixels
    colWidth: `17em`,
  }

  state = { spans: [] }
  ref: createRef()
  // sums up the heights of all child nodes for each grid item
  sumUp = (acc, node) => acc + node.scrollHeight

  computeSpans = () => {
    const { rowHeight } = this.props
    const spans = []
    Array.from(this.ref.current.children).forEach(child => {
      const childHeight = Array.from(child.children).reduce(this.sumUp, 0)
      const span = Math.ceil(childHeight / rowHeight)
      spans.push(span + 1)
      child.style.height = span * rowHeight + `px`
    })
    this.setState({ spans })
  }

  componentDidMount() {
    this.computeSpans()
    window.addEventListener('resize', this.computeSpans)
  }

  componentWillUnmount() {
    window.removeEventListener('resize', this.computeSpans)
  }

  render() {
    return (
      <Parent ref={this.ref} {...this.props}>
        {this.props.children.map((child, i) => (
          <Child key={i} span={this.state.spans[i]}>
            {child}
          </Child>
        ))}
      </Parent>
    )
  }
}

and the styled components:

// masonry/styles.js
import styled from 'styled-components'

export const Parent = styled.div`
  display: grid;
  grid-template-columns: repeat(
    auto-fit,
    minmax(${props => props.colWidth}, 1fr)
  );
  grid-auto-rows: calc(${props => props.rowHeight}px - 2em);
  grid-gap: 2em;
`

export const Child = styled.div`
  grid-row: span ${props => props.span};
  height: max-content;
`

Then simply use it like so:

// blog.js
import React from 'react'

import Masonry from './masonry'
import Post from './post'

const Blog = ({ posts }) => (
  <Masonry>
    {posts.map(post => (
      <Post key={post.slug} {...post} />
    ))}
  </Masonry>
)

The above was copied here from this blog post.

klimashkin commented 5 years ago

Just for reference, it's useful not only for building masonry tiles https://github.com/w3c/csswg-drafts/issues/1183

xlcrr commented 5 years ago

For anyone using Vue, this plugin takes about a minute

https://github.com/paulcollett/vue-masonry-css

Slooo commented 4 years ago

column-count, but you need to sort the data.

https://codepen.io/slooo/pen/yLYZNKw

kidwm commented 4 years ago

FYI, https://github.com/w3c/csswg-drafts/issues/4650

tymmesyde commented 4 years ago

W3C draft: https://drafts.csswg.org/css-grid-3/

tonypee commented 9 months ago

here's an example of this using React to manage the column rendering.

https://stackblitz.com/edit/vitejs-vite-r9gmdc?file=src%2Fmain.tsx,src%2FApp.tsx,src%2FApp.css&terminal=dev

aayushsingh7 commented 3 months ago

Thank me later😋 https://www.smashingmagazine.com/native-css-masonry-layout-css-grid/