实现瀑布流布局,就这几行代码?

瀑布流布局是实现一种比较流行的页面布局方式,表现为参差不齐的瀑布多栏卡片。跟网格布局相比,流布显得更灵动,局行更具艺术气息。代码

瀑布流布局

实现瀑布流布局的实现方式有多种,比如multi-column布局,瀑布grid布局,流布flex 布局等。局行但是代码这些实现方式都有各自的局限性,代码也略复杂。实现

其实,瀑布有个最原始、流布最简单,局行也是代码兼容性最好的实现方式,那就是使用绝对定位。瀑布流布局的元素是一些等宽不等高的高防服务器卡片,只要根据元素的实际宽高计算出自己的坐标位置就行了。

要计算坐标自然要用到 JavaScript,这就不是纯 CSS 方案,对某些前端极客来讲显得不那么纯粹。不过只要理清思路了,也用不了几行代码。本文就给出最近实现的一个版本。

// 计算每个卡片的坐标 export function calcPositions({  columns = 2, gap = 7, elements }) {    if (!elements || !elements.length) {      return [];   }   const y = []; //上一行卡片的底部纵坐标数组,用于找到新卡片填充位置   const positions = []; // 每个卡片的坐标数组   elements.forEach((item, index) => {      if (y.length < columns) {  // 还未填满一行       y.push(item.offsetHeight);       positions.push({          left: (index % columns) * (item.offsetWidth + gap),         top: 0       });     } else {        const min = Math.min(...y); // 最小纵坐标       const idx = y.indexOf(min); // 纵坐标最小的卡片索引       y.splice(idx, 1, min + gap + item.offsetHeight); // 替换成新卡片的纵坐标       positions.push({          left: idx * (item.offsetWidth + gap),         top: min + gap       });     }   }); // 由于采用绝对定位,容器是无法自动撑开的。因此需要计算实际高度,即最后一个卡片的top加上自身高度   return {  positions, containerHeight: positions[positions.length - 1].top + elements[elements.length - 1].offsetHeight }; } 

上面这段代码的作用就是计算每个卡片的服务器租用left、top,以及容器的总高度。关键位置都有注释,应该不难理解。

有了这几行核心代码,要想封装成瀑布流组件就很容易了。以 Vue 为例,可以这样封装:

MasonryLite.vue

<template>   <div class="masonry-lite">     <slot></slot>   </div> </template> <script> import {  calcPositions } from ./index.js; export default {    name: MasonryLite,   props: {      gap: {        type: Number,       default: 12,     },     columns: {        type: Number,       default: 2,     },   },   data() {      return { };   },   mounted() {      this.doLayout();   },   methods: {      doLayout() {        const children = [...this.$el.querySelectorAll(.masonry-item)];       if (children.length === 0) {          return;       }       const {  positions, containerHeight } = calcPositions({          elements: children,         columns: this.columns,         gap: this.gap,       });       children.forEach((item, index) => {          item.style.cssText = `left:${ positions[index].left}px;top:${ positions[index].top}px;`;       });       this.$el.style.height = `${ containerHeight}px`;     },   }, }; </script> <style lang="scss" scoped> .masonry-lite{    position: relative; } .masonry-item {    position: absolute; } </style> 

使用组件:

<MasonryLite>   <div class="product-card masonry-item" v-v-for="(item, index) in items" :key="index">     <img :src="item.imageUrl" />     <header>{ {  item.title }}</header>   </div> </MasonryLite> 

不过这样其实还会有点问题,就是doLayout的执行时机。因为该方案基于绝对定位,需要元素在渲染完成后才能获取到实际宽高。如果卡片内有延迟加载的图片或者其他动态内容,高度会发生变化。这种情况下就需要在DOM更新后主动调用一次doLayout重新计算布局。

如果大家有更好的实现方案,欢迎交流!

代码仓库:https://github.com/kaysonli/masonry-lite

npm 包:masonry-lite

如果觉得对你有帮助,帮忙点个不要钱的star。

本文转载自微信公众号「1024译站」,可以通过以下二维码关注。转载本文请联系1024译站公众号。香港云服务器

系统运维
上一篇:质行万里,同舟奋楫 | 新华三 “服务合作伙伴运营能力跃升计划”启动
下一篇:数据中心布线指南