手摸手,带你实现移动端H5瀑布流布局

移动端瀑布流布局是一种比较流行的网页布局方式,视觉上来看就是一种像瀑布一样垂直落下的排版。每张图片并不是显示的正正方方的,而是有的长有的短,呈现出一种不规则的形状。但是它们的宽度通常都是相同的

因为移动端瀑布流布局主要为竖向瀑布流,因此本文所探讨的是竖向瀑布流

特点

竖向瀑布流布局主要有下面几种特点:

  • 一般出现在移动端 H5 页面底部
  • 主要以图片或视频为主
  • 降低页面复杂度,节省空间,可以容纳更多内容
  • 不规则展示,不会那么枯燥,用户体验好
  • 难以或者说不能滚动到页面最底部

不同于传统的分页,瀑布流因为以上这些特点一般被用在这些场景下:

  • 推荐机制下的信息 即根据用户画像推荐或者运营人员推荐的信息
  • 大分类下的信息流 展示的信息有很多,它们的大分类都是相同的,适合用户不明确详细需要获得什么信息或商品的情况下
  • 各个信息或商品之间没有比较强的相关性 和上面一条类似,展示的不是千遍一律的东西,相对独立的信息或商品也许能让用户意外发掘到想要的东西

实现

一般来说主要分为 CSS 实现和 JS 实现

CSS 实现主要是用到一些专门的样式属性,实现起来简单,但是往往会有兼容性问题

JS 实现的方法则不存在这些问题,并且能实现比较个性化的需求,但是实现起来比较麻烦

column 多列布局方法

column 实现瀑布流主要依赖两个属性

column-count 属性是设置共有几列

column-gap 属性是设置每列之间的间隔

column 兼容性

column caniuse.png
代码

<style>
  .pic {
    column-count: 3;
    column-gap: 5px;
  }

  .pic .item {
    border: 1px solid #ccc;
    margin-bottom: 5px;
  }

  .item img {
    width: 100%;
  }
</style>

<body>
  <div class="pic">
    <div class="item">
      <!-- 获取 api 取到的图片地址 -->
      <img src="" />
      <div>001</div>
    </div>
    ······
    <div class="item">
      <img src="" />
      <div>008</div>
    </div>
  </div>
</body>

flex 弹性布局方法

flex 实现瀑布流需要给父元素设置为横向排列。然后通过设置 flex-flow: column wrap 使其换⾏

代码

<style>
  .pic {
    display: flex;
    flex-flow: column wrap;
    height: 100vh;
  }

  .item {
    /* 每行展示 3 个 */
    width: calc(100%/3 - 5px);
    border: 1px solid #ccc;
    margin-bottom: 5px;
  }

  .item img {
    width: 100%;
  }
</style>
<body>
  <div class="pic">
    <div class="item">
      <!-- 获取 api 取到的图片地址 -->
      <img src="" />
      <div>001</div>
    </div>
    ······
    <div class="item">
      <img src="" />
      <div>008</div>
    </div>
  </div>
</body>

效果

在这里插入图片描述

可以发现图片排序顺序是先垂直方向,然后才是水平方向的。column 多列布局和 flex 弹性布局方法实现的效果图最终相似

JS + 懒加载方法

在不考虑兼容性或者没有特殊图片展示顺序需求下,只是实现瀑布流的话上面两种方案是够用的。如果要实现一些个性化的需求的话,还是得用 JS

主要思路就是:

  1. 先将第一行排满
  2. 计算第一行的所有图片高度,将第二行第一张图放在第一行最矮的图片后面
  3. 进行玩步骤 2,重新计算当前所有列高度,避免步骤 2 添加完成后,该列高度还是最矮

完整代码

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>JS实现瀑布流</title>
    <style>
        .item {
            box-sizing: border-box;
            border: 1px solid #ccc;
            position: absolute;
            /* 展示为三列,减去间隔宽度 */
            width: calc(100% / 3 - 5px);
        }

        .item img {
            width: 100%;
        }
    </style>
</head>

<body>
    <div id="pic">
        <div class="item">
            <img src="..." />
            <div>001</div>
        </div>
        <!-- 剩余图片,实际场景中应该使用for循环 -->
        <div class="item">
            <img src="..." />
            <div>015</div>
        </div>
    </div>
</body>
<script>
    var pic = document.getElementById('pic');
    var items = pic.children;
    // 相邻间距为5像素
    var space = 5;
    var picAmount = 15;

    window.onload = function () {
        function getClient() {
            return {
                width: window.innerWidth ||
                    document.documentElement.clientWidth ||
                    document.body.clientWidth,
                height: window.innerHeight ||
                    document.documentElement.clientHeight ||
                    document.body.clientHeight,
            };
        }

        function getScrollTop() {
            return document.documentElement.scrollTop ||
                window.pageYOffset ||
                document.body.scrollTop;
        }

        waterFall();

        function waterFall() {
            var pageWidth = getClient().width;
            var itemWidth = items[0].offsetWidth;
            var columns = parseInt(pageWidth / (itemWidth + space));
            var picList = [];
            for (var i = 0; i < items.length; i++) {
                if (i < columns) {
                    items[i].style.top = 0;
                    items[i].style.left = (itemWidth + space) * i + 'px';
                    picList.push(items[i].offsetHeight);
                } else {
                    // 找到数组中最小高度的那一列,并拿到其下标
                    var minHeight = picList[0];
                    var index = 0;
                    for (var j = 0; j < picList.length; j++) {
                        if (minHeight > picList[j]) {
                            minHeight = picList[j];
                            index = j;
                        }
                    }
                    items[i].style.top = picList[index] + space + 'px';
                    items[i].style.left = items[index].offsetLeft + 'px';
                    // 操作列的高度 = 当前列原本高度 + 图片的高度 + 相邻的间距
                    picList[index] = picList[index] + items[i].offsetHeight + space;
                }
            }
        }
        window.onresize = function () {
            waterFall();
        };

        // 监听滚动事件,模拟懒加载
        window.onscroll = function () {
            // 如果滚动到的位置比当前显示的最后一个图片高,则请求新的图片
            if (
                getClient().height + getScrollTop() >=
                items[items.length - 1].offsetTop
            ) {
                // 后续的新图片
                var datas = [...];
                for (var i = 0; i < datas.length; i++) {
                    picAmount += 1;
                    var div = document.createElement('div');
                    div.className = 'item';
                    div.innerHTML = `<img src="${datas[i]}" alt=""><div>0${picAmount}</div>`;
                    pic.appendChild(div);
                }
                waterFall();
            }
        };
    };
</script>

</html>

效果

在这里插入图片描述

不同于上面两个 css 实现的瀑布流,JS 实现的图片排序顺序是先水平方向,然后才是垂直方向

在这里插入图片描述

可以看到当滚动页面的时候,新的图片会不断添加进来,这样就实现懒加载了

总结

如果实现效果简单不考虑兼容的的话可以选择使用 CSS 实现;若要兼容老版本浏览器或者实现一些个性化的需求还是得用 JS 实现

当然除了上文说的这些方法以外,也可以使用第三方库 Masonry 实现

  • 3
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
<template> <div class="chat-container"> <!-- 聊天消息列表 --> <div class="message-list"> <div class="message-item" v-for="message in messages" :key="message.id"> <div class="avatar"><img :src="message.avatar" alt=""></div> <div class="message-content"> <div class="name">{{ message.name }}</div> <div class="text">{{ message.text }}</div> <div class="time">{{ message.time }}</div> </div> </div> </div> <!-- 发送消息表单 --> <div class="send-form"> <input type="text" class="input-message" v-model="newMessage" placeholder="请输入消息内容"> <button class="send-button" @click="sendMessage">发送</button> </div> </div> </template> <script> export default { data() { return { messages: [ { id: 1, name: '小明', avatar: 'https://avatars.githubusercontent.com/u/6224295?v=4', time: '2021-10-01 10:00:00', text: '你好啊,我想问一下这个聊天布局怎么实现的?' }, { id: 2, name: '小红', avatar: 'https://avatars.githubusercontent.com/u/6224295?v=4', time: '2021-10-01 10:05:00', text: '这个是使用 Vue 实现的,主要是利用了 Flex 布局和 Vue 组件的特性。' } ], newMessage: '' } }, methods: { // 发送消息 sendMessage() { if (this.newMessage.trim() !== '') { const message = { id: this.messages.length + 1, name: '小明', avatar: 'https://avatars.githubusercontent.com/u/6224295?v=4', time: new Date().toLocaleString(), text: this.newMessage } this.messages.push(message) this.newMessage = '' } } } } </script> <style scoped> .chat-container { display: flex; flex-direction: column; height: 100%; } .message-list { flex: 1; overflow-y: auto; padding: 10px; } .message-item { display: flex; align-items: center; margin-bottom: 10px; } .avatar { width: 40px; height: 40px; margin-right: 10px; border-radius: 50%; overflow: hidden; } .avatar img { width: 100%; height: 100%; object-fit: cover; } .message-content { flex: 1; } .name { font-size: 14px; font-weight: bold; margin-bottom: 5px; } .text { font-size: 16px; } .time { font-size: 12px; color: #666; margin-top: 5px; } .send-form { display: flex; align-items: center; padding: 10px; background-color: #fff; } .input-message { flex: 1; height: 30px; padding: 0 10px; border: 1px solid #ccc; border-radius: 15px; outline: none; } .send-button { margin-left: 10px; width: 60px; height: 30px; border: none; border-radius: 15px; background-color: #42b983; color: #fff; cursor: pointer; outline: none; } </style>

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值