瀑布流原理:是一种常见的网页布局方式,它的特点是将内容以多列的形式呈现,每一列的内容高度不固定,根据内容的大小自适应调整,从而形成了像瀑布一样的流动效果。

瀑布流的实现原理涉及到数据加载、布局设计、图片加载和响应式设计等方面,需要综合考虑各个方面的因素,以实现高效、流畅、美观的瀑布流效果。

目前网上很多技术博文是使用定位完成瀑布流效果的,这里将使用jquery和flex布局来实现响应式瀑布流效果。

效果如下图:

以上在不同屏幕尺寸下,自动调整页面布局列的效果,以满足页面内容正常展示;通过计算范围不同屏幕下需要显示几列,当浏览器大小改变时,重新调整页面数据。

题外话,最近发现很多网站采集CSDN网站内容,未经作者同意照搬照抄,虽然让人心里有点不舒服,但考虑再三,还是继续自己技术分享,希望大家喜欢。

一、页面搭建

1.1 css样式

这里就直接贴页面样式代码,如果有不清楚的,可以自建demo运行看看即可,CSS仅实现了页面布局和内容样式修饰作用。

在css目录创建demo_waterfall.css文件,代码如下:

html, body{ margin: 0; padding: 0; font-size: 12px; font-family: Verdana, Geneva, Tahoma, sans-serif; }/* 瀑布流列flex布局样式 */
.waterfall-flex-container{ position: relative; display: flex; }
.waterfall-flex-container .column-item{ flex: 1; padding: 0 10px; }
.waterfall-flex-container .column-item::after{ display: block; content: ''; clear: both; }.waterfall-load-more{ text-align: center; font-size: 14px; color: #666; padding: 30px 0; }/* 追加容器样式 */
.waterfall-container{ padding: 20px; }
.waterfall-container .wf-content{ position: relative; }.waterfall-container .item{ width: 300px; display: block;  text-decoration: none; color: #333; background-color: #fff; box-shadow: 0 0 6px rgba(0, 0, 0, .1); border-radius: 10px; margin: 10px 0; padding: 12px; box-sizing: border-box; overflow: hidden; float: left; }
.waterfall-container .item img{ max-width: 100%; }.waterfall-flex-container .column-item .item{ width: 100%; box-sizing: border-box; margin: 20px 0; transition:All 0.4s ease-in-out; border: 1px solid #eee; float: none; }.waterfall-flex-container .column-item .item:hover{ transform: scale(1.03); }@media screen and (max-width: 520px) {.waterfall-flex-container .column-item{ padding: 0; }
}

1.2 js闭包

在js目录创建demo_waterfall.js文件,后续插件函数定义和功能均在此完成,代码如下:

/*** 闭包 - 定义瀑布流封装函数*/
(function($, win, doc){})(jQuery, window, document);

1.3 html

创建html页面,并引入样式文件、jquery和自定义插件js(demo_waterfall.js)文件,代码如下:

<!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>瀑布流</title><link rel="stylesheet" type="text/css" href="css/demo_waterfall.css" /><script type="text/javascript" src="js/jquery-3.6.4.min.js"></script><script type="text/javascript" src="js/demo_waterfall.js"></script>
</head>
<body></body>
</html>

这里是基于jquery基础上进行开发的,所以jquery必须要引入。

1.4 页面定义瀑布流div

代码如下:

<!-- waterfall -->
<div class="waterfall-container"><div class="wf-content" id="waterfall"></div>
</div>
<!-- /waterfall -->

1.5 渲染内容

因为只是静态Demo,所以这边需要使用setTimeout来模拟ajax延迟加载效果,然后将渲染元素追加到#waterfall容器中,代码如下:

<!-- waterfall --><div class="waterfall-container"><div class="wf-content" id="waterfall"></div></div><!-- /waterfall -->
<script type="text/javascript">
var imagesList = [{title: "图片1", description: "这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (1).png"},{title: "图片2", description: "这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (2).png"},{title: "图片3", description: "这是一张优美的图片!", img: "images/waterfall/photo (3).png"},{title: "图片4", description: "这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (4).png"},{title: "图片5", description: "这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (5).png"},{title: "图片6", description: "这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (6).png"},{title: "图片7", description: "这是一张优美的图片!", img: "images/waterfall/photo (7).png"},{title: "图片8", description: "这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (8).png"},{title: "图片9", description: "这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (9).png"},{title: "图片10", description: "这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (10).png"},{title: "图片11", description: "这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (11).png"},{title: "图片12", description: "这是一张优美的图片!", img: "images/waterfall/photo (12).png"},{title: "图片13", description: "这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (13).png"},{title: "图片14", description: "这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (14).png"},{title: "图片15", description: "这是一张优美的图片!", img: "images/waterfall/photo (15).png"},{title: "图片16", description: "这是一张优美的图片!这是一张优美的图片!这是一张优美的图片!", img: "images/waterfall/photo (16).png"},
];// 将获取的数据填充到dom中,返回拼接后的html
function generateHtml(item){return `<a href="javascript:;" class="item"><img src="${item.img}" alt="" /><div class="txt-box"><h3>${item.title}</h3><p>${item.description}</p></div></div>`;
}var loadIndex = 0;// ajax 加载数据
function ajaxLoadData(){// 模拟ajax请求延迟效果setTimeout(function(){loadIndex++;for(var i in imagesList){$('#waterfall').append(generateHtml(imagesList[i]));}}, 2000);   // 延迟2秒
}$(function(){// 请求数据ajaxLoadData();});  </script>

页面效果如下:

如上,页面现在还是有点乱,这是因为没添加响应式布局;如果希望在加载瀑布流前页面也能正常展示,大家可以自己修饰下,这较为简单就不讲解了,后面会直奔主题讲瀑布流实现。

二、功能开发

2.1 定义瀑布流插件

在闭包中定义瀑布流功能函数,代码如下:

/*** 闭包 - 定义瀑布流封装函数*/
(function($, win, doc){/*** 定义瀑布流功能* @param {*} options */$.fn.waterfall = function(options){// 定义 和 合并外部参数options = $.extend({eleItemClassName: "",  //放置每列中的元素选择器}, options);}})(jQuery, window, document);

2.2 定义参数

options = $.extend(true, {eleItemClassName: "",                    //放置每列中的元素选择器// 瀑布流最包层容器选择器名称containerListClassName: 'waterfall-flex-container',gridClassName: "column-item",   //自定义列选择器类名称gridInnerItemClassName: "grid-inner-item",    //自定义列内部选择器类名称loadClassName: "waterfall-load-more",    //加载更多容器选择器类名称loadingText: "正在加载中...",loadOverText: "已经没有数据了",loadText: "~ 下拉加载更多 ~",diff: 50,           //底部差值,提前执行// 布局,不同尺寸下显示不行列元素,尺寸从大到小排序grid: {2300: 6,1920: 5,1360: 4,980: 3,768: 2,520: 1},// 错误回调函数errorCallback: function(){},// 滑到底部执行函数slideToDownCallback: function(){}
}, options);

如上参数,在此次功能实现中拥有不同作用,如果这里参数还不能满足你们需求,可以在基础上再完善。

1)需要追加元素的类选择器

options.eleItemClassName:用于获取#waterfall容器中所有需要追加的元素

2)这里类定义,是为了方便后期争对不同场景需要,可以定制瀑布流样式,参数说明:

options.containerListClassName:自定义瀑布流外层容器类选择,执行时可不传入该参数。
options.gridClassName:flex容器列的类选择,执行时可不传入
options.gridInnerItemClassName:列内部盛放追加元素的容器,执行时可不传入

3)上拉加载

options.loadClassName:上拉加载容器类选择器,执行时可不传入
options.loadingText:正在加载是显示的内容
options.loadOverText:数据加载完显示的内容
options.loadText:未加载时显示内容

4)不同屏幕显示列数

options.grid::定义在不屏幕尺寸下显示的列数

5)上拉加载

options.diff:拉到底部前,提前50像素执行回调函数
options.slideToDownCallback:拉到底部时,执行的回调函数

2.3 实现响应式列

首先在$.fn.waterfall函数外定义getGridColumn()函数,获取当前屏幕尺寸下显示的列数,根据options.grid定义来判断可显示几列数据,代码如下:

/*** 获取列数* @param {*} gridList * @returns */
function getGridColumn(gridList){var winWidth = win.innerWidth;// 循环判断当前宽度范围for(var i = 0; i < gridList.length; i++){if(winWidth>gridList[i].min&&winWidth<=gridList[i].max){return gridList[i].value;}}// 否则返回最大值列数return gridList[0].value;
}

$.fn.waterfall功能代码如下:

/*** 定义瀑布流功能* @param {*} options */
$.fn.waterfall = function(options){options = $.extend(true, {// 瀑布流最包层容器选择器名称containerListClassName: 'waterfall-flex-container',eleItemClassName: "",                    //放置每列中的元素选择器gridClassName: "column-item",gridInnerItemClassName: "grid-inner-item",loadClassName: "waterfall-load-more",loadingText: "正在加载中...",loadOverText: "已经没有数据了",loadText: "~ 下拉加载更多 ~",diff: 50,           //底部差值,提前执行// 布局,不同尺寸下显示不行列元素,尺寸从大到小排序grid: {2300: 6,1920: 5,1360: 4,980: 3,768: 2,520: 1},// 错误回调函数errorCallback: function(){},// 滑到底部执行函数slideToDownCallback: function(){}}, options);// 定义容器和DOMvar containerListObj = $('<div />').addClass(options.containerListClassName),           //容器DOM对象domObj = $(this),// 获取所有需要追加的元素DOM对象elementItems = domObj.find('.' + options.eleItemClassName),// 定义上拉加载DOM对象,后面会使用到loadObj = $('<div />').addClass(options.loadClassName).text(options.loadText);// 定义变量var that = this;// 获取容器显示列范围this.grid = (function(grid){var list = [],keys = Object.keys(grid).map(item => parseInt(item)).sort((a, b) => b - a);keys.forEach((key, i) => {if(i == keys.length - 1){list.push({ min: 0, max: key, value: grid[key] })}else{list.push({ min: keys[i+1], max: key, value: grid[key] })}});return list;})(options.grid);// 获取当前显示列数this.columns = getGridColumn(this.grid);$(this).before(containerListObj);   // 添加响应式容器$(this).before(loadObj);$(this).hide();                     // 影响原容器}

2.4 调用waterfall

代码在html代码中调用waterfall功能函数,传入需要读取元素的类选择器,代码如下:

$(function(){var waterfall = $('#waterfall').waterfall({eleItemClassName: "item"});// 请求数据ajaxLoadData();
});  

此时页面结构则发生变化,后续显示的列将追加到.waterfall-flex-container类选择器中,代码效果如下:

options.grid通过匿名函数修改数据结构,以便后期判断当前屏幕尺寸时,自动调用显示列数。

通过getGridColumn()函数,获取当前屏幕下可显示几列数据。

以上代码执行后,重构后的this.grid和this.columns列数的值变化,我这边演示屏幕可显示5列数,数据如下:

2.5 自动调用列数

在$.fn.waterfall函数外定义generateGrid()函数,用于生成当前屏幕尺寸的列数元素,并追加到列容器中。代码如下:

/*** 生成对应的列* @param {*} that * @param {*} options * @returns */
function generateGrid(that, options){var itemList = [];for(var i = 0, tmpSubElement; i < that.columns; i++){tmpSubElement = $('<div />').addClass(options.gridClassName + "-" + (i+1)).addClass(options.gridInnerItemClassName);itemList[i] = $('<div />').addClass(options.gridClassName);itemList[i].append(tmpSubElement);that.container.append(itemList[i]);}return itemList;
}

在$.fn.waterfall功能函数中,通过已知的this.grid和this.columns列数,重新渲染页面显示的列数,代码如下:

// 略...$(this).before(containerListObj);   // 添加响应式容器
$(this).before(loadObj);
$(this).hide();                     // 影响原容器//将创建DOM对象赋值到this当前对象上
this.container = containerListObj;    //放置列容器DOM对象
this.items = elementItems;            //瀑布流所有元素DOM对象集
this.loading = loadObj;               //上拉加载盒子DOM对象// 生成结果
this.grids = generateGrid(this, options);

以上代码执行后,容器.waterfall-flex-container中生成对应列元素,效果如下:

要在浏览器大小改变后,列数也跟着调整,则需要使用jquery添加resize监听事件;当窗体大小改变后,清空列容器下所有节点,通过getGridColumn()函数重新生成列数。代码如下:

 // 监听屏幕大小变化
$(win).resize(function(){that.container.empty();// 获取当前显示列数that.columns = getGridColumn(that.grid);// 生成列数据that.grids = generateGrid(that, options);
});

此时拖放浏览器修改浏览器窗口大小时,会发现DOM中列数在不同尺寸下,列元素会增加或减少。

2.6 追加数据

在$.fn.waterfall功能函数外,再定义getMinItem()和jigsawPhoto()函数,分另用来获取当前哪列高度最小和将元素追加到对应列中(一直追加列高度最小的容器中),代码如下:

/*** 获取每列中高度最小值元素* @param {*} that * @param {*} options * @returns */
function getMinItem(that, options, cha){return that.grids.sort((first, second) => {return first.find('.' + options.gridInnerItemClassName).height() - second.find('.' + options.gridInnerItemClassName).height();})[0];
}// 拼图,将元素追加到对应容器中
function jigsawPhoto(that, options){var minHeightElement;// 循环处理that.items.each((i, item) => {minHeightElement = getMinItem(that, options);minHeightElement.find('.' + options.gridInnerItemClassName).append(item);});
}

这里我们需要在2.6中代码位置,添加jigsawPhoto()函数,代码如下:

//将创建DOM对象赋值到this当前对象上
this.container = containerListObj;    //放置列容器DOM对象
this.items = elementItems;            //瀑布流所有元素DOM对象集
this.loading = loadObj;               //上拉加载盒子DOM对象// 生成结果
this.grids = generateGrid(this, options);
// 拼图堆加
jigsawPhoto(this, options);// 监听屏幕大小变化
$(win).resize(function(){that.container.empty();// 获取当前显示列数that.columns = getGridColumn(that.grid);// 生成结果that.grids = generateGrid(that, options);// 拼图堆加jigsawPhoto(that, options);
});

但此时继续执行刷新页面,发现每个列容器中,还是空空如也,什么DOM节点也追加进去;这里输出 下that.items则会发现,该属性下元素为0,未读取到任何元素节点;这里因为我们在执行$.fn.waterfall时,ajaxLoadData()函数还未执行,并且内部数据追加也是2秒之后 ,所以当$.fn.waterfall执行完,未读到任何元素是正常的。

这个问题需要带下2.7中再解决。

2.7 上拉加载更多

在2.2中定义了很多接口参数,这里我们需要将接口参数options.slideToDownCallback赋值到this对象的slideToDownCallback上,以方便后期使用。

另外定义this.refresh用来刷新页面数据,这里直接追加#waterfall中容器中的数据,通过jquery获取到再追加到this.items中,有些人会担心是否会将之前数据重叠,这大可不必,在jigsawPhoto()函数执行后,#waterfall容器中元素就全部追加到瀑布流列中,#waterfall等同被清空了,后期追加进来数据都为新数据。

还有this.startLoading()函数和this.loadEnd函数分别是在“正在加载数据时”和“数据加载完时”执行。

代码如下:

// 定义变量判断是否正在获取追加内容
var isLoading = false;
// 执行回调函数位置滚动条高度
this.scrollTop = 0;
// 滑到底部执行函数
this.slideToDownCallback = options.slideToDownCallback;// 重新执行获取数据,刷新数据
this.refresh = function(){// 追加获取元素domObj.find('.' + options.eleItemClassName ).each((i, item) => {that.items.push(item);});// 拼图堆加jigsawPhoto(that, options);isLoading = false;loadObj.text(options.loadText);
}
// 开始加载 时执行
this.startLoading = function(){isLoading = true;loadObj.text(options.loadingText);
}
// 加载完毕后 执行
this.loadEnd = function(){isLoading = true;loadObj.text(options.loadOverText);
}

在上拉加载的功能完成后,别忘了修改html页面定义位置还需调整,这里loadIndex只是记录当前上拉加载几次,用于判断何时结果上拉加载事件,代码如下:

// ajax 加载数据
function ajaxLoadData(that){that.startLoading();// 模拟ajax请求延迟效果setTimeout(function(){loadIndex++;for(var i in imagesList){$('#waterfall').append(generateHtml(imagesList[i]));}that.refresh();if(loadIndex>3){that.loadEnd();     //加载没有数据后执行,后续拉到底部则不会触发slideToDownCallback回调函数} }, 2000);   // 延迟2秒
}$(function(){var waterfall = $('#waterfall').waterfall({eleItemClassName: "item",loadingText: "正在加载数据中...",loadOverText: "没有更多数据了",loadText: "~ 下拉加载更多数据 ~",// 滑到底部执行函数slideToDownCallback: function(){ajaxLoadData(this);}});// 请求数据ajaxLoadData(waterfall);});  </script>

这时再刷新页面,会发现数据可以正常加载出来了。但会发现只会执行一次,并且是ajaxLoadData(waterfall)执行时渲染的数据,optoins.slideToDownCallback定义的回调函数还未执行,这是因为此函数只有拉到底部时才会触发;那怎样判断滚动条拉到底部了,这里我们需要使用jquery监听scroll()滚动条事件,代码如下:

// 略...// 开始加载 时执行
this.startLoading = function(){isLoading = true;loadObj.text(options.loadingText);
}
// 加载完毕后 执行
this.loadEnd = function(){isLoading = true;loadObj.text(options.loadOverText);
}// 监听滑动事件
$(win).scroll(function(e){// 判断是否已滑到底部if($(doc).height() - $(win).height() - options.diff <= $(doc).scrollTop() && !isLoading){that.slideToDownCallback();}
});

到这里,瀑布流上拉加载功能则已完成了,上拉加载3次后,则停止加载数据;并且不断修改浏览器大小时,页面列数会不断修改。

三、问题

3.1 最小高度计算

在完成该demo后,大家会发现底部图片还是存在此小瑕疵的,如下图某些图片位置稍作调整,可以会更美观些。该瀑布流实现方式是通过getMinItem()不断获取高度最小的列进行追加,大家可以在这里作些调用,返回更为合适列进行追加数数。

3.2 图片位置问题

另外,当上拉加载执行完成后,细心朋友会发现之前加载图片都变换了位置,这里因为jigsawPhoto()函数都是从that.items开始位置重新渲染,导致追加过程中某些元素错误后,后续追加都不将在原位置上。

解决方法:jigsawPhoto(that, items, options)执行时,将that.items通过形参items传入,增加形参items进行items.each()循环。

3.3 图片加载

当在真实环境中通过ajax读取网络图片,会存在图片过大或网速等因素,导致图片加载过慢,影响真实的元素高度,从而导致计算存在较大误差。这里可以将读取到的img元素,通过实例Image(),监听每个图片的load()事件,当同批图片加载完成后再执行追加。(个人思路,有更好方法欢迎留言分享)。

以上问题就留给大家自己去实现解决了,有问题欢迎在CSDN上留言。

jQuery实现响应式瀑布流效果(jQuery+flex)相关推荐

  1. js实现响应式瀑布流

    导读:瀑布流,又称瀑布流式布局.是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部.最早采用此布局的网站是Pinterest ...

  2. 响应式瀑布流加载更多

    演示https://infinite-scroll.com/demo/masonry/ <!doctype html> <html lang="en" class ...

  3. 30个创意出色的非常实用的jquery框架插件-(视觉大背景,瀑布流效果)

    jQuery是在网页设计师和开发者最近的热门话题之一.人们使用它的广泛范围从个人博客到企业网站,目前流行的视觉大背景滚动效果,瀑布流图片阴影效果,图片缩放,动态标签复制,html5音频播放,视频播放等 ...

  4. html5圆形头像滚动效果,基于jQuery实现响应式圆形图片轮播特效

    本文实例讲述了基于jQuery实现响应式圆形图片轮播特效代码.分享给大家供大家参考.具体如下: 运行效果截图如下: mislider是一款效果非常酷的jQuery响应式圆形图片轮播图特效插件,misl ...

  5. 瀑布流效果Demo总结(5)之基于jquery+blocksit.min.js的实现

    1.综述 最近研究了时下流行的瀑布流展示效果. 当前共计尝试的方法及其优缺点如下: (1)基于JQuery框架及blocksit.min.js实现的瀑布流不连续,每列中多多少少都会有一些位置出现空白. ...

  6. 在 JQuery Mobile 中实现瀑布流图库布局

    先来看在Windows系统的1080P显示器中显示的效果: 这个整合方式几乎没有现存的实例,是自己总结出来的方法,在此记录下来. 首先访问Masonry官网下载masonry.pkgd.min.js: ...

  7. JS结合PHP瀑布流,JavaScript_原生JS实现响应式瀑布流布局,原生JS实现的瀑布流布局,代 - phpStudy...

    原生JS实现响应式瀑布流布局 原生JS实现的瀑布流布局,代码及demo代码地址:https://github.com/leozdgao/responsive_waterfall Demo:http:/ ...

  8. HTML聊天框特效,利用jQuery实现响应式聊天窗口界面特效

    特效描述:利用jQuery实现 响应式 聊天窗口 界面特效.利用jQuery实现响应式聊天窗口界面特效 代码结构 1. 引入CSS 2. 引入JS 3. HTML代码 10 Conversations ...

  9. 微信小程序 - - - - - 瀑布流效果实现

    瀑布流效果 1. 瀑布流 1.1 什么叫瀑布流? 1.2 瀑布流有什么优点? 1.3 如何实现瀑布流的关键是什么? 2. 实现思路 2.1 思路梳理 2.2 实现瀑布流 对于一些小程序,关于瀑布流的需 ...

最新文章

  1. win7启动后报丢失nscmk.dll解决解决方式
  2. mongodb查询值不为空_MongoDB使用规范(上)
  3. 安装mysql 图_如何在Windows下安装MYSQL,并截图说明
  4. python to datetime_Python中缺少datetime.timedelta.to_seconds()-float?
  5. 利用Hownet进行语义相似度计算的类(
  6. 对Excel选择性粘贴中的跳过空单元选项容易造成的两种误解
  7. yaml参数文件的使用
  8. 2022.9.13 手机验证码登录功能
  9. kali虚拟机配置成桥接模式
  10. win10下自带输入法变为繁体字的原因及解决方法
  11. ubuntu20.04虚拟机使用水星mw150us无线usb接口网卡
  12. 嵌入式计算机的分类与应用
  13. 新浪微博基于Docker的混合云架构与应用实践
  14. 酷狗软件测试自学,酷狗音乐检测网络的简单教程
  15. python进行文本分析
  16. R语言 无敌小抄 cheatsheet
  17. 网页及移动平台2D游戏开发探索
  18. kissy ajax,KISSY - A Powerful JavaScript Framework
  19. 饥荒服务器怎么修改器,饥荒通用修改器TestTools
  20. 天意U盘维护系统1.8无法用Ultroiso制作

热门文章

  1. 音乐格式怎么转换,音频格式转换的方法 1
  2. EndNote使用笔记
  3. 分享一个产品体验报告
  4. 神经网络学习笔记2.2 ——用Matlab写一个简单的卷积神将网络图像分类器
  5. matlab如何修改三维箭头类型,matlab画三维箭头
  6. ios 隐藏UIScrollView的滚动条
  7. 毕业设计-基于机器视觉的答题卡生成及批阅分析系统-opencv
  8. 浅谈:定制网站如何防止被套路
  9. C语言程序训练-1586-计算组合数
  10. Drawer 篇 - Drawer 常识