【问题标题】:How to acheive a responsive masonry-like layout which is continuous and results in no gaps?如何实现连续且无间隙的响应式砌体式布局?
【发布时间】:2019-12-06 08:20:46
【问题描述】:

如何实现与 Pintrest 属性相似的布局?

具体来说:

  • 所有图片宽度相同的响应式布局。
  • 所有图片都包含在一个 div 中。没有多个容器试图复制“列”,例如使用 flexbox。
  • 布局的顺序是没有间隙的。例如,如果布局是两列,并且图片的宽度和高度为:[(100,400), (100,100), (100,400), (100,100), (100,400), (100,100)],则列的高度大致相同,而不是左侧的 1200 和右侧的 400。下面的屏幕截图说明了这一点。

这是一个屏幕截图,显示所有照片都在一个 div 容器中。

响应式布局:

第三个要点的示例:深色淋浴图片位于悬挂座椅图片下方,而不是外部房屋图片下方。这是因为挂座图片的高度较小。

同样的,当布局为两列宽时:卧室的图片在提手椅的下方,因为挂座图片的高度较小。

虽然这篇文章看起来与CSS-only masonry layout 相似,但解决方案并没有产生预期的结果:

使用@Oliver Joseph Ash 解决方案会导致:

grid-container {
  display: flex;
  flex-flow: column wrap;
  align-content: space-between;
  /* Your container needs a fixed height, and it 
   * needs to be taller than your tallest column. */
  height: 960px;
  /* Optional */
  background-color: #f7f7f7;
  border-radius: 3px;
  padding: 20px;
  width: 60%;
  margin: 40px auto;
  counter-reset: items;
}

grid-item {
  width: 24%;
  /* Optional */
  position: relative;
  margin-bottom: 2%;
  border-radius: 3px;
  background-color: #a1cbfa;
  border: 1px solid #4290e2;
  box-shadow: 0 2px 2px rgba(0, 90, 250, 0.05), 0 4px 4px rgba(0, 90, 250, 0.05), 0 8px 8px rgba(0, 90, 250, 0.05), 0 16px 16px rgba(0, 90, 250, 0.05);
  color: #fff;
  padding: 15px;
  box-sizing: border-box;
}
<grid-container>
  <grid-item>
    <a href="https://g.foolcdn.com/image/?url=https%3A%2F%2Fg.foolcdn.com%2Feditorial%2Fimages%2F492452%2Fgettyimages-1014028076.jpg&w=700&op=resize">
      <img src="https://g.foolcdn.com/image/?url=https%3A%2F%2Fg.foolcdn.com%2Feditorial%2Fimages%2F492452%2Fgettyimages-1014028076.jpg&w=700&op=resize">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://image.jimcdn.com/app/cms/image/transf/none/path/sa716b1500dd60f05/image/ic839a74ed6a8a054/version/1519833130/image.jpg">
      <img src="https://image.jimcdn.com/app/cms/image/transf/none/path/sa716b1500dd60f05/image/ic839a74ed6a8a054/version/1519833130/image.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://c8.alamy.com/comp/AXBEXR/stock-photograph-of-a-asian-teen-with-a-trumpet-to-her-ear-AXBEXR.jpg">
      <img src="https://c8.alamy.com/comp/AXBEXR/stock-photograph-of-a-asian-teen-with-a-trumpet-to-her-ear-AXBEXR.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://thumbs.dreamstime.com/z/cyber-woman-orange-11363555.jpg">
      <img src="https://thumbs.dreamstime.com/z/cyber-woman-orange-11363555.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcThObqAcFeb4byMcwLVkU1JVMYonpavYmEukk9r3rqF2oBTnd1q">
      <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcThObqAcFeb4byMcwLVkU1JVMYonpavYmEukk9r3rqF2oBTnd1q">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://www.demilked.com/magazine/wp-content/uploads/2018/03/5aaa1cce4180b-funny-weird-wtf-stock-photos-57-5a3bb7ba3c266__700.jpg">
      <img src="https://www.demilked.com/magazine/wp-content/uploads/2018/03/5aaa1cce4180b-funny-weird-wtf-stock-photos-57-5a3bb7ba3c266__700.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-5-5b0801c7504b2__700.jpg">
      <img src="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-5-5b0801c7504b2__700.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-8-5b0801cd0f33d__700.jpg">
      <img src="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-8-5b0801cd0f33d__700.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://www.atomix.com.au/media/2017/07/StockPhotoBanner.jpg">
      <img src="https://www.atomix.com.au/media/2017/07/StockPhotoBanner.jpg">
    </a>
  </grid-item>
</grid-container>

这使得输出看起来像:

而@Michael_B 解决方案:

grid-container {
  display: grid;
  grid-auto-rows: 50px;
  grid-gap: 10px;
  grid-template-columns: repeat(auto-fill, minmax(30%, 1fr));
}

grid-item {
  display: flex;
  align-items: center;
  justify-content: center;
  font-size: 1.3em;
  font-weight: bold;
  color: white;
}

img {
  width: 200px;
}
<grid-container>
  <grid-item>
    <a href="https://g.foolcdn.com/image/?url=https%3A%2F%2Fg.foolcdn.com%2Feditorial%2Fimages%2F492452%2Fgettyimages-1014028076.jpg&w=700&op=resize">
      <img src="https://g.foolcdn.com/image/?url=https%3A%2F%2Fg.foolcdn.com%2Feditorial%2Fimages%2F492452%2Fgettyimages-1014028076.jpg&w=700&op=resize">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://image.jimcdn.com/app/cms/image/transf/none/path/sa716b1500dd60f05/image/ic839a74ed6a8a054/version/1519833130/image.jpg">
      <img src="https://image.jimcdn.com/app/cms/image/transf/none/path/sa716b1500dd60f05/image/ic839a74ed6a8a054/version/1519833130/image.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://c8.alamy.com/comp/AXBEXR/stock-photograph-of-a-asian-teen-with-a-trumpet-to-her-ear-AXBEXR.jpg">
      <img src="https://c8.alamy.com/comp/AXBEXR/stock-photograph-of-a-asian-teen-with-a-trumpet-to-her-ear-AXBEXR.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://thumbs.dreamstime.com/z/cyber-woman-orange-11363555.jpg">
      <img src="https://thumbs.dreamstime.com/z/cyber-woman-orange-11363555.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcThObqAcFeb4byMcwLVkU1JVMYonpavYmEukk9r3rqF2oBTnd1q">
      <img src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcThObqAcFeb4byMcwLVkU1JVMYonpavYmEukk9r3rqF2oBTnd1q">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://www.demilked.com/magazine/wp-content/uploads/2018/03/5aaa1cce4180b-funny-weird-wtf-stock-photos-57-5a3bb7ba3c266__700.jpg">
      <img src="https://www.demilked.com/magazine/wp-content/uploads/2018/03/5aaa1cce4180b-funny-weird-wtf-stock-photos-57-5a3bb7ba3c266__700.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-5-5b0801c7504b2__700.jpg">
      <img src="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-5-5b0801c7504b2__700.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-8-5b0801cd0f33d__700.jpg">
      <img src="https://static.boredpanda.com/blog/wp-content/uploads/2018/05/emilia-clarke-making-stock-photos-8-5b0801cd0f33d__700.jpg">
    </a>
  </grid-item>
  <grid-item>
    <a href="https://www.atomix.com.au/media/2017/07/StockPhotoBanner.jpg">
      <img src="https://www.atomix.com.au/media/2017/07/StockPhotoBanner.jpg">
    </a>
  </grid-item>
</grid-container>

要好很多,但会导致图片高度重叠:

【问题讨论】:

  • 图像的高度是否以某种方式受到限制?例如。它们是一定大小/高度的倍数吗?如果它们是完全“随机”的高度,没有办法只用 CSS 实现砌体布局,你需要一个小的行高和 JavaScript 来动态评估图片的高度,然后设置一个行跨度来匹配每个高度网格项...
  • @exside 目前没有高度限制,但如果需要,我可以将图像大小调整到受限范围。例如强制图片的高度为[400px, 420px, 440px,..., 780px, 800px] 。这样的设置可以实现吗?
  • 技术上是的,这完全取决于您需要/想要在图像高度中具有的粒度,我的意思是您可以制作 100 个 CSS 类来匹配每个行跨度,但我会说这种方法有一个限制,它有点荒谬=),你最好用几行JS来做......所以我可能不会比拥有6-12个“跨度”类更进一步,例如你可以说行高大约是 120px,然后你从那里开始,例如.span-2 将是 240px,.span-3 360px 等等,从 120-1440px 的高度范围应该足够了 =)...
  • 这种方法的“问题”是您需要预先知道图像的高度以设置适当的跨度类,具体取决于您如何获取图像以及如何渲染它们到 HTML 中这很容易或不容易...
  • 您提到可以在几行 JS 中完成,是否有可能了解其中涉及的方式/内容?

标签: css responsive-design flexbox css-grid


【解决方案1】:

我的建议是设置一些“跨度”类并将图像/砌体项目的高度限制为最小单位的倍数。这种方法允许仅使用 CSS 砌体,但我们需要事先知道最小单元项的倍数(例如,在渲染 HTML 时),以便我们可以应用这些看起来像这样的 span 类:

.grid-span-2 {
  grid-row-end: span 2;
}
.grid-span-3 {
  grid-row-end: span 3;
}
.grid-span-4 {
  grid-row-end: span 4;
}
...

如果这不可能,下一个最好的方法是使用一点 JS 来根据项目高度动态计算行跨度。下面的 sn-p 应该可以说明如何处理这个问题。

最重要的部分如下:

    const sizeItems = () => {
        // only trigger DOM repaint when necessary
        if ( $('.grid-container')[0].style.gridAutoRows !== '1px' ) {
            $('.grid-container')[0].style.gridAutoRows = '1px';
        }

        $('.grid-item > *').forEach(el => {
            const height = Math.floor(el.getBoundingClientRect().height);
            // only trigger DOM repaint when necessary
            if ( el.parentElement.style.gridRowEnd !== `span ${height}` ) {
                el.parentElement.style.gridRowEnd = `span ${height}`;
            }
        });
    }

我们需要等待图像加载,然后才能执行此操作,并且在调整视口大小时再次执行此操作,这基本上是 JS 代码的其余部分。

我们在这里所做的是获取网格项中内容的真实/真实高度。我更喜欢在每个网格项中使用包装器元素并使用它来测量.为什么?

a) 网格模块中的某些属性可能会在您看不见的情况下与网格项的实际高度混淆,从而导致不正确的高度计算。

b) 使用这种方法,我们不能使用 grid-gap(因为在这种极端情况下,我们正在制作 1px-rows 以获得最精确的大小),因为它会弄乱单元格高度,所以仍然能够有一个间隙,我们也可以使用这个包装器 div 并对其应用填充以模仿网格间隙。

对于我们设置 grid-auto-rows: 1px; 的 JS 方法,这为我们在项目高度方面提供了最大的灵活性,因为基本上每个网格单元都可以调整大小以完全适合其内容,但请注意,尽管可能存在一些小故障1px,很可能是由于亚像素渲染...

// micro DOM helper, just for convenience
const $ = window.$ = function(a,b){return Array.prototype.slice.call(document.querySelectorAll(a, b));};

const sizeItems = () => {
	// only trigger DOM repaint when necessary
	if ( $('.grid-container')[0].style.gridAutoRows !== '1px' ) {
		$('.grid-container')[0].style.gridAutoRows = '1px';
	}
	
	$('.grid-item > *').forEach(el => {
		const height = Math.floor(el.getBoundingClientRect().height);
		// only trigger DOM repaint when necessary
		if ( el.parentElement.style.gridRowEnd !== `span ${height}` ) {
			el.parentElement.style.gridRowEnd = `span ${height}`;
		}
	});
}

Promise.all($('img').map(img => { 
	return new Promise(resolve => {
		img.onload = () => {
			resolve();
		}
		img.onerror = () => {
			resolve();
		}
	});
})).then(() => {
	const emit = new Event('images');
	window.dispatchEvent(emit);
});

window.addEventListener('images', function(e) {
	console.log('onImagesLoaded');
	sizeItems();
});

window.addEventListener('load', function(e) {
	console.log('onLoad');
	sizeItems();
});

window.addEventListener('resize', function(e) {
	console.log('onResize');
	sizeItems();
});
* {
  box-sizing: border-box;
  margin: 0;
}
body {
  font-family: sans-serif;
}

.grid-container {
  background: gold;
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(calc(100%/3), 1fr));
  grid-gap: 0; /* can't use grid gap! */
  /* grid-auto-rows: auto; */
  grid-auto-flow: dense;
  /* if you don't want a gap on the outside of the grid when working with padding */
  /* margin: -4px; */
}
@media screen and (max-width: 600px) {
  .grid-container {
    grid-template-columns: repeat(auto-fill, minmax(50%, 1fr));
  }
}
@media screen and (max-width: 400px) {
  .grid-container {
    display: block;
  }
}

.grid-item {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  font-size: 1.3em;
  font-weight: bold;
  color: hsl(0,0%,14%);
}

.grid-gap {
  /* if you need a grid-gap, apply padding to a wrapper element INSIDE the grid-item */
  /* padding: 4px; */
  overflow: hidden;
  width: 100%;
}

.grid-item a,
.grid-item img {
  display: block;
  width: 100%; height: 100%;
}

.grid-item figcaption {
  background: lightcoral;
  color: white;
  font-size: 14px;
  font-style: italic;
  padding: 4px 0;
  text-align: center;
}
<div class="grid-container">
	<div class="grid-item">
		<div class="grid-gap">
			<figure>
				<a href="https://via.placeholder.com/397x625">
					<img src="https://via.placeholder.com/397x625/255a60/FFFFFF" />
				</a>
				<figcaption>This is a caption!</figcaption>
			</figure>
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/678x765">
				<img src="https://via.placeholder.com/678x765/4496a1/FFFFFF" />
			</a>
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/567x987">
				<img src="https://via.placeholder.com/567x987/eaf8ab/FFFFFF" />
			</a>
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/968x956">
				<img src="https://via.placeholder.com/968x956/ccd47e/FFFFFF" />
			</a>	
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/987x687">
				<img src="https://via.placeholder.com/987x687/255a60/FFFFFF" />
			</a>
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/620x345">
				<img src="https://via.placeholder.com/620x345/255a60/FFFFFF" />
			</a>
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/543x789">
				<img src="https://via.placeholder.com/543x789/4496a1/FFFFFF" />
			</a>
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/376x274">
				<img src="https://via.placeholder.com/376x274/eaf8ab/FFFFFF" />
			</a>
		</div>
	</div>
	<div class="grid-item">
		<div class="grid-gap">
			<a href="https://via.placeholder.com/468x623">
				<img src="https://via.placeholder.com/468x623/9fb828/FFFFFF" />
			</a>	
		</div>
	</div>
</div>

【讨论】:

  • 由于某些原因,当使用图像时,仍然会出现带标题的空白:i.imgur.com/6GVDgNk.png,没有标题:i.imgur.com/VHTOXkI.png。如果照片&lt;img...&gt; 被替换为&lt;div class="photo" style="height: Xpx"&gt;&lt;/div&gt; 元素,其中X 是一个表示不同高度的int,它会按预期工作。不知道为什么会这样?
  • 您是否使用了与我的 sn-p 中相同/相似的标记,如果您更改了它,您是否调整了 JS 代码?只要您使用包装器来测量高度,标题就不应该有所作为。我注意到有时在 sn-ps 中需要多次尝试才能正常工作,但我认为这可能是 SO sn-p 嵌入的问题...
  • 使用提供的代码,唯一的区别是我链接到磁盘上的图像,在 jsfiddle 上尝试完全相同的代码(带占位符)会产生相同的结果:jsfiddle.net/6953fude
  • @Greg 我更新了上面 sn-p 中的 JS 代码,它现在在加载图像时会发出一个自定义事件,因此我们可以为此添加一个事件监听器。但我认为如果图像已经缓存,图像的onload 事件可能不会触发,所以我们还需要添加一些其他侦听器,基本上在任何事件之前执行sizeItems(),只是为了确保......你能检查再次使用更新的代码?
  • 很遗憾还有一些差距:i.imgur.com/pnesget.png
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2015-10-21
  • 1970-01-01
  • 1970-01-01
  • 2015-01-08
  • 1970-01-01
  • 1970-01-01
  • 2016-09-26
相关资源
最近更新 更多