浅谈瀑布流原理及Vue实现

瀑布流,又称瀑布流式布局。是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部。最早采用此布局的网站是Pinterest,逐渐在国内流行开来。国内大多数清新站基本为这类风格。

瀑布流布局效果

在这里插入图片描述

既多行等宽元素排列,等宽不等高,后面的元素依次排列在上一个元素后面

那么瀑布流的规则是什么哪?下面将用图解的方式分析一下瀑布流的算法

图解瀑布流

第一排图片的顶部会处于同一个高度,依次排列在顶端,哪第一排排满之后,后面的图片,也就是第6张图片应该怎样排列?

在这里插入图片描述

是这样吗?
在这里插入图片描述

可能有人认为会是这种方式进行排列,然而这样排列的方式是错误的,这样排列容易导致某一列可能会过长,而其他的列会过短,导致列之间高度相差过大的情况出现。那么怎样取解决这个问题那?答案就是利用定位在最短的一列下面进行排列,就像下图
在这里插入图片描述

看到这里,基本上已经理解了瀑布流的基本原理,接下来就是怎样去实现了!

瀑布流的实现

下面以Vue为例来展示怎样实现瀑布流效果

1.基本的布局

布局就比较简单的写一下哈~ 反正主要讲原理~

.v-waterfall-content{
    width: 100%;
    height: 100%;
    position: relative;
}
.v-waterfall-item{
    position: absolute;
}
.v-waterfall-item img{
    width: 100%;
    height: 100%;
}

2.瀑布流核心实现

首先在data中定义一些基本的数据

  waterfallList:[],//存放计算好的数据
  waterfallImgWidth:200,//每一列的宽度
  waterfallImgCol:5,//多少列
  waterfallImgRight:10,//右边距
  waterfallImgBottom:10,//下边距
  waterfallDeviationHeight:[],//存放瀑布流各个列的高度
  imgList:[]

然后给图片数组填充图片数据:

    let imgArr = [
      require('../assets/100x70.png'),
      require('../assets/100x80.png'),
      require('../assets/100x90.png'),
      require('../assets/100x100.png'),
      require('../assets/100x120.png'),
      require('../assets/100x150.png'),
      require('../assets/100x210.png'),
      require('../assets/100x230.png'),
      require('../assets/100x250.png')
    ];
    for (let i = 0;i < 100;i++){
      this.imgList.push(this.imgArr[Math.round(Math.random() * 8)]);
    }

这里要注意,在js中存放静态文件链接的时候要用require,不然不会显示:

    <div class="v-waterfall-content">
        <div v-for="img in imgList"
             class="v-waterfall-item">
            <img :src="img" alt="">
        </div>
    </div>
在这里插入图片描述

可以看到,所有图片都挤到一起,根本没有流,下面谈一谈怎样让图片流起来

根据上面解释的瀑布流的基本原理,就是需要找到图片列里面高度最低的那一个,要找到最低的就需要记录没一列的高度,下面是记录列高度的实现:

//图片预加载,获取图片宽和高
imgPreloading(){
    for (let i = 0;i < this.imgList.length;i++){
        let aImg = new Image();
        aImg.src = this.imgList[i];
        aImg.onload = aImg.onerror = (e)=>{
            let imgData = {};
            //根据设定的列宽度求出图片的高度
            imgData.height = this.waterfallImgWidth/aImg.width*aImg.height;
            imgData.src = this.imgList[i];
            this.waterfallList.push(imgData);
            //调用图片位置计算方法
            this.rankImg(imgData);
        }
    }
}
//计算图片偏移量
rankImg(imgData){
    let {waterfallImgWidth,waterfallImgRight,waterfallImgBottom,waterfallDeviationHeight,waterfallImgCol} = this;
    //找出当前最短列的索引
    let minIndex = this.waterfallDeviationHeight.indexOf(Math.min.apply(null, this.waterfallDeviationHeight))
    //获取最短列底部高度,既下一张图片的顶部高度
    imgData.top = waterfallDeviationHeight[minIndex];
    //计算左侧偏移,最短列索引*(右边距+列宽度)
    imgData.left = minIndex*(waterfallImgRight+waterfallImgWidth);
    //改变当前列高度
    waterfallDeviationHeight[minIndex] += imgData.height + waterfallImgBottom;
}

这两个方法就是瀑布流的核心代码了,修改一下html代码,在刚才写的给图片数组填充完数据之后调用一下imgPreloading方法,刷新浏览器就可以看到效果啦!

<div class="v-waterfall-content" id="v-waterfall">
    <div v-for="img in waterfallList"
         class="v-waterfall-item"
        :style="{top:img.top+'px',left:img.left+'px',width:waterfallImgWidth+'px',height:img.height}">
        <img :src="img.src" alt="">
    </div>
</div>
在这里插入图片描述

到这里基本上瀑布流的原理和核心思路大致明了了。瀑布流最重要的就是记录流的位置和计算图片的高度,
第一次写技术型博客,有写的不好的地方,还请多多指正,放上完整代码

<template>
    <div class="v-waterfall-content" id="v-waterfall">
        <div v-for="img in waterfallList"
             class="v-waterfall-item"
            :style="{top:img.top+'px',left:img.left+'px',width:waterfallImgWidth+'px',height:img.height}">
            <img :src="img.src" alt="">
        </div>
    </div>
</template>

<script>
    export default {
        name: "v-waterfall",
        data(){
            return {
                waterfallList:[],
                imgArr:[
                    require('../assets/100x70.png'),
                    require('../assets/100x80.png'),
                    require('../assets/100x90.png'),
                    require('../assets/100x100.png'),
                    require('../assets/100x120.png'),
                    require('../assets/100x150.png'),
                    require('../assets/100x210.png'),
                    require('../assets/100x230.png'),
                    require('../assets/100x250.png')
                ],
                waterfallImgWidth:100,
                waterfallImgCol:5,
                waterfallImgRight:10,
                waterfallImgBottom:10,
                waterfallDeviationHeight:[],
                imgList:[]
            }
        },
        created() {
            for (let i = 0;i < 100;i++){
                this.imgList.push(this.imgArr[Math.round(Math.random() * 8)]);
            }
        },
        mounted(){
            this.calculationWidth();
        },
        methods:{
            //计算每个图片的宽度或者是列数
            calculationWidth(){
                let domWidth = document.getElementById("v-waterfall").offsetWidth;
                if (!this.waterfallImgWidth && this.waterfallImgCol){
                    this.waterfallImgWidth = (domWidth-this.waterfallImgRight*this.waterfallImgCol)/this.waterfallImgCol;
                }else if(this.waterfallImgWidth && !this.waterfallImgCol){
                    this.waterfallImgCol = Math.floor(domWidth/(this.waterfallImgWidth+this.waterfallImgRight))
                }
                //初始化偏移高度数组
                this.waterfallDeviationHeight = new Array(this.waterfallImgCol);
                for (let i = 0;i < this.waterfallDeviationHeight.length;i++){
                    this.waterfallDeviationHeight[i] = 0;
                }
                this.imgPreloading()
            },
            //图片预加载
            imgPreloading(){
                for (let i = 0;i < this.imgList.length;i++){
                    let aImg = new Image();
                    aImg.src = this.imgList[i];
                    aImg.onload = aImg.onerror = (e)=>{
                        let imgData = {};
                        imgData.height = this.waterfallImgWidth/aImg.width*aImg.height;
                        imgData.src = this.imgList[i];
                        this.waterfallList.push(imgData);
                        this.rankImg(imgData);
                    }
                }
            },
            //瀑布流布局
            rankImg(imgData){
                let {waterfallImgWidth,waterfallImgRight,waterfallImgBottom,waterfallDeviationHeight,waterfallImgCol} = this;
                //for (let i = 0;i < this.waterfallList.length;i++){
                let minIndex = this.filterMin();
                imgData.top = waterfallDeviationHeight[minIndex];
                imgData.left = minIndex*(waterfallImgRight+waterfallImgWidth);
                waterfallDeviationHeight[minIndex] += imgData.height + waterfallImgBottom;
                //}
                console.log(imgData);
            },
            /**
             * 找到最短的列并返回下标
             * @returns {number} 下标
             */
            filterMin(){
                const min = Math.min.apply(null, this.waterfallDeviationHeight);
                return this.waterfallDeviationHeight.indexOf(min);
            }
        }
    }
</script>

<style scoped>
.v-waterfall-content{
    width: 100%;
    height: 100%;
    position: relative;
}
.v-waterfall-item{
    float: left;
    position: absolute;
}
.v-waterfall-item img{
    width: auto;
    height: auto;
}
</style>

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,064评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,606评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,011评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,550评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,465评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,919评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,428评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,075评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,208评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,185评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,191评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,914评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,482评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,585评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,825评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,194评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,703评论 2 339

推荐阅读更多精彩内容

  • 问答题47 /72 常见浏览器兼容性问题与解决方案? 参考答案 (1)浏览器兼容问题一:不同浏览器的标签默认的外补...
    _Yfling阅读 13,720评论 1 92
  • 翻译自“Collection View Programming Guide for iOS” 0 关于iOS集合视...
    lakerszhy阅读 3,790评论 1 22
  • 隔壁团乐队是索尼音乐旗下摇滚乐团,英文名为NextDoorBand,乐队由主唱张波,吉他手种旭,贝斯手好力友,鼓手...
    左小刀电影工作室阅读 1,984评论 0 1
  • xcode8正式版本可以下载了,里面搭载了swift3。swift3和swift2语法是不兼容的,所以旧代码需要转...
    亲密数阅读 391评论 0 0
  • 老师在课上纪录片时随意之作,敬佩。 看了周梦蝶的纪录片《再见化城人》
    芫荽菜阅读 236评论 0 0