在这之前,有写过一篇关于实现瀑布流的文章,后期有人留言提出需要添加灯箱效果的功能,所以这次则讲述下如何实现此功能。由于该篇接上篇写的:jQuery实现响应式瀑布流效果(jQuery+flex)_jquery瀑布流插件-CSDN博客
,所以建议先学习上篇,再来练习此篇内容。
弹框灯箱部分的页面样式这里就不细讲了,在之前瀑布流页面样式后追加即可,代码如下:
/* 弹框样式 */
.image-dialog-wrap {
width: 100%;
height: 100%;
box-sizing: border-box;
background-color: rgba(0, 0, 0, .5);
position: fixed;
left: 0;
top: 0;
z-index: 999;
}
.image-dialog-wrap .dialog-close {
width: 36px;
height: 36px;
background: url("../images/dialog-close.png") no-repeat center;
background-size: 100% 100%;
position: absolute;
top: 15px;
right: 15px;
z-index: 10;
cursor: pointer;
}
.image-dialog-wrap .dialog-flex-box {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
overflow: hidden;
}
.image-dialog-wrap .dialog-flex-box .flex-item {
display: block;
width: 100%;
box-sizing: border-box;
}
.image-dialog-wrap .dialog-top-box {
flex: 3;
position: relative;
padding: 15px 30px;
text-align: center;
}
.image-dialog-wrap .dialog-top-box::before {
display: inline-block;
content: attr(data-title);
font-size: 16px;
color: #fff;
}
.image-dialog-wrap .dialog-image-box {
flex: 16;
background-size: contain;
background-repeat: no-repeat;
background-position: center;
position: relative;
}
.image-dialog-wrap .dialog-image-box.cover-box{
position: absolute;
top: 0;
height: 100% !important;
display: block !important;
}
.image-dialog-wrap .dialog-image-box .image-btn {
width: 50px;
height: 100px;
margin-top: -50px;
border: 0;
outline: none;
background-color: rgba(0, 0, 0, .5);
background-position: center;
background-repeat: no-repeat;
background-size: contain;
position: absolute;
top: 50%;
cursor: pointer;
display: none;
}
.image-dialog-wrap .dialog-image-box .image-btn.btn-left {
background-image: url("../images/angle-left.png");
left: 0;
}
.image-dialog-wrap .dialog-image-box .image-btn.btn-right {
background-image: url("../images/angle-right.png");
right: 0;
}
.image-dialog-wrap .dialog-image-box:hover .image-btn {
display: block;
}
.image-dialog-wrap .dialog-image-box:hover .image-btn[disabled] {
opacity: .2;
cursor: default;
}
.image-dialog-wrap .scale-btn {
width: 45px;
height: 45px;
background: url(../images/scale-up.png) no-repeat center;
background-size: 80%;
position: absolute;
top: 10px;
right: 70px;
border: 0;
outline: none;
border-radius: 5px;
cursor: pointer;
}
.image-dialog-wrap .scale-btn[rel=cover] {
background-image: url(../images/scale-down.png);
}
.image-dialog-wrap .dialog-desc-box {
flex: 3;
font-size: 16px;
color: #fff;
padding: 15px 20px;
}
@media screen and (min-width: 1200px) {
.image-dialog-wrap .dialog-flex-box {
margin: 0 auto;
}
}
@media screen and (max-width: 1199px) {
.image-dialog-wrap {
padding: 50px 15px 0;
}
.image-dialog-wrap .dialog-image-box {
background-color: transparent;
}
.image-dialog-wrap .dialog-image-box .image-btn {
width: 30px;
height: 80px;
margin-top: -40px;
}
}
移动端的界面效果图:
样式内部的图标,都是从iconfront中下载的,需要可以从该网站下载,地址:
iconfont-阿里巴巴矢量图标库
如上篇瀑布流功能函数定义,也在闭包中进行功能开发,代码如下:
/**
* 闭包 - 灯箱效果
*/
(function($, win, doc){
/**
* 图片灯箱效果
* @param {*} options
*/
$.fn.imageBox = function(options){
// 须链式调用,返回this
return this;
}
})(jQuery, window, document);
如上图,页面结构包含”顶部一段话“、”图片“、”底部描述内容“三块,这里我们如何获取对应内容,并将渲染到灯箱指定位置呢?所以这块需要做对外接口,使其调整灵活点,读取指定位置内容进行渲染即可。如下代码:
![](${item.img})
如上,我们在渲染时,将三块内容分别存放在img标签的data中,然后在显示灯箱图片是,取对应图片的data数据即可。所以功能函数中对外定义参数如下:
/**
* 闭包 - 灯箱效果
*/
(function($, win, doc){
/**
* 图片灯箱效果
* @param {*} options
*/
$.fn.imageBox = function(options){
options = $.extend({
imgName: "img", // 需要添加到灯箱中图片类名或标签名
imgColumnName: "img", // 填充灯箱中图片链接地址
topColumnName: "title", // 填充顶部标题容器的data后缀-title
descriptionColumnName: "desc" // 填充底部描述容器的data后缀-desc
}, options);
return this;
}
})(jQuery, window, document);
这里将上篇瀑布流中页面代码稍作调整,在img上添加2.2中所示data数据,以及在执行瀑布流功能函数前,执行灯箱功能函数调用,代码如下:
上图中说明了各区域的类名,以及各容器间关系(包含与被包含),现在我们将根据此图,在灯箱功能函数执行时,初始化灯箱打开后的页面结构。代码如下:
/**
* 闭包 - 灯箱效果
*/
(function($, win, doc){
/**
* 图片灯箱效果
* @param {*} options
*/
$.fn.imageBox = function(options){
options = $.extend({
imgName: "img", // 需要添加到灯箱中图片类名或标签名
imgColumnName: "img", // 填充灯箱中图片链接地址
topColumnName: "title", // 填充顶部标题容器的data后缀-title
descriptionColumnName: "desc" // 填充底部描述容器的data后缀-desc
}, options);
var that = this,
// 存储图片信息
imageData = {
list: [], //所有图片
index: -1 //索引
},
// 常量(用来切换是否全屏显示)
CONSTMODE = {
CONTAIN: 'contain',
COVER: 'cover'
},
imageBoxContainer = $('').addClass("image-dialog-wrap").hide(),
flexBox = $('').addClass('dialog-flex-box'),
titleBox = $('').addClass("flex-item dialog-desc-box"), // 底部标题信息
imgBox = $('').addClass('flex-item dialog-image-box'), // 图片信息
topBox = $('').addClass('flex-item dialog-top-box'), // 头部标题信息
btnLeft = $('').addClass('image-btn btn-left'),
btnRight = $('').addClass('image-btn btn-right'),
btnScale = $('').addClass('scale-btn'),
closeBox = $('').addClass('dialog-close'); // 关闭按钮
// 添加到页面中
$('body').append(imageBoxContainer);
// 添加左右切换按钮容器到图片容器中
imgBox.append(btnLeft);
imgBox.append(btnRight);
// 将顶部标题、图片、底部描述三块区域添加到dialog-flex-box容器中
flexBox.append(topBox);
flexBox.append(imgBox);
flexBox.append(titleBox);
// 将dialog-flex-box、关闭按钮、全屏显示按钮添回到的灯箱总容器中
imageBoxContainer.append(flexBox);
imageBoxContainer.append(closeBox);
imageBoxContainer.append(btnScale);
return this;
}
})(jQuery, window, document);
以上准备工作做完后,现在可以实现具体功能了。首先点击图片时,需要获取瀑布流容器中需要添加到灯箱中的所有图片信息,这里是每次点击都重新获取一次,由于瀑布流是上拉加载的,所以页面中图片数量是处于变化中的。
如2.3中可知,灯箱功能函数执行是,传入的是瀑布流大窗口的ID(#waterfall),所以这里我们直接通过$(this).on() 进行监听即可。先实现灯箱的相关事件定义好,由于关闭功能较简单先实现其功能,代码如下:
// 向左切换
btnLeft.on('click', function(){
// do something...
});
// 向右切换
btnRight.on('click', function(){
// do something...
});
// 图片显示模式切换
btnScale.on('click', function(){
// do something...
});
// 关闭弹框
closeBox.on('click', function(){
imageBoxContainer.fadeOut();
});
// 选择图片
$(this).on('click', options.imgName, function(){
// do something...
});
在闭包中定义函数getAllImage(),获取全部图片信息和当前显示图片索引。代码如下:
/**
* 获取数据
* @param {*} eleWrap
* @param {*} imgSrc
* @param {*} options
* @returns
*/
function getAllImage(eleWrap, imgSrc, options){
var data = {
list: [], //存储所有图片
index: 0 //当前显示图片索引
},
// 如果传入为图片地址,直接使用,否则重新读取
current = 'string' === typeof imgSrc ? imgSrc : $(imgSrc).data(options.imgColumnName);
// 循环获取所有图片
$(eleWrap).find(options.imgName).each(function(index){
var url = $(this).data(options.imgColumnName);
if(url){
// 添加图片
data.list.push($(this).data());
// 判断索引,是否为默认显示项
if(url==current){
data.index = index;
}
}
});
return data;
}
将函数resetCurrentImage()和setImageMode()定义在$.fn.imageBox内部,这样方便与直接获取容器对象,并修改样式和内容。代码如下:
// 重置当前图片
function resetCurrentImage(){
var data = imageData.list[imageData.index],
img = new Image(),
imgMaxHeight = $(win).height() - 200;
// 指定显示图片地址
img.src = data[options.imgColumnName];
// 顶部标题
topBox.attr('data-title', data[options.topColumnName]);
// 底部描述
titleBox.text(data[options.descriptionColumnName]);
// 设置图片样式
imgBox.css({
"background-image": "url('"+data[options.imgColumnName]+"')",
"height": img.height > imgMaxHeight ? imgMaxHeight : img.height
});
}
// 设置模式
function setImageMode(mode){
mode = mode || CONSTMODE.CONTAIN;
// 在全屏按钮上记录显示模式
btnScale.data('mode', mode);
btnScale.attr('rel', mode);
imgBox.css('background-size', mode);
// 判断是否全屏显示
if(mode==CONSTMODE.COVER){
imgBox.addClass('cover-box');
}else {
imgBox.removeClass('cover-box');
}
}
灯箱功能函数初始时,在灯箱结构部分初始完成后,默认执行一次setImageMode()函数,设置灯箱图片显示模式。
在2.5中,将选择图片事件中,添加如下代码,此时点击图片后则可以正常显示灯箱效果了。
getAllImage()函数执行后,会返回对应的list和index结构,是和imageData结构一致,所以获取后直接赋值给它即可,后期图片左右切换会使用到。
// 选择图片
$(this).on('click', options.imgName, function(){
// 获取所有图片信息
imageData = getAllImage(that, this, options);
// 重置当前灯箱中显示图片信息
resetCurrentImage();
// 显示灯箱容器
imageBoxContainer.fadeIn();
});
效果如下:
在2.0中,灯箱显示时获取全部图片信息时,已经将所有图片信息存储在list中,当前显示图片索引值为index,所以这里切换时,只要控制index即可。代码如下:
// 向左切换
btnLeft.on('click', function(){
// 如果索引值小于0,则停止执行
imageData.index = imageData.index - 1 <= 0 ? 0 : imageData.index - 1;
// 如果切换到第一个图片信息,则禁用向左切换按钮,否则解禁按钮
if(imageData.index == 0) btnLeft.prop('disabled', true);
else if(btnLeft.prop('disabled')) btnLeft.prop('disabled', false);
// 解禁向右切换按钮
if(btnRight.prop('disabled')) btnRight.prop('disabled', false);
// 重构当前图片信息
resetCurrentImage();
});
// 向右切换
btnRight.on('click', function(){
// 如果索引值大于图片信息总长度,则停止执行
imageData.index = imageData.index + 1 >= imageData.list.length - 1 ? imageData.list.length - 1 : imageData.index + 1;
// 如果切换到最后一张图片信息,则禁用向右切换按钮,否则解禁
if(imageData.index == imageData.list.length - 1) btnRight.prop('disabled', true);
else if(btnRight.prop('disabled')) btnRight.prop('disabled', false);
// 解禁向左切换按钮
if(btnLeft.prop('disabled')) btnLeft.prop('disabled', false);
// 重构当前图片信息
resetCurrentImage();
});
全屏显示效果是结合CSS样式和JS交互共同实现的,这块比较好理解,setImageMode()重置显示模式在2.7中已定义,直接在点击事件中调用即可,代码如下:
// 图片显示模式切换
btnScale.on('click', function(){
var mode = $(this).data('mode');
// 如果是COVER则传入CONTAIN,若是CONTAIN则传入COVER
setImageMode(mode==CONSTMODE.CONTAIN?CONSTMODE.COVER:CONSTMODE.CONTAIN);
});
这里将瀑布流和灯箱的JS代码放在一起,如果有小朋友参照上述代码执行,出错或显示不了,可以马对照以下代码。
/**
* 闭包 - 定义瀑布流封装函数
*/
(function($, win, doc){
/**
* 获取列数
* @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;
}
/**
* 生成对应的列
* @param {*} that
* @param {*} options
* @returns
*/
function generateGrid(that, options){
var itemList = [];
for(var i = 0, tmpSubElement; i < that.columns; i++){
tmpSubElement = $('').addClass(options.gridClassName + "-" + (i+1)).addClass(options.gridInnerItemClassName);
itemList[i] = $('').addClass(options.gridClassName);
itemList[i].append(tmpSubElement);
that.container.append(itemList[i]);
}
return itemList;
}
/**
* 获取每列中高度最小值元素
* @param {*} that
* @param {*} options
* @returns
*/
function getMinItem(that, options){
return that.grids.sort((first, second) => {
return first.find('.' + options.gridInnerItemClassName).height() - second.find('.' + options.gridInnerItemClassName).height();
})[0];
}
// 拼图,将元素追加到对应容器中
function jigsawPhoto(that, items, options){
var minHeightElement;
// 循环处理
items.each((i, item) => {
minHeightElement = getMinItem(that, options);
minHeightElement.find('.' + options.gridInnerItemClassName).append(item);
});
}
/**
* 定义瀑布流功能
* @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);
// 定义容器和DOM
var containerListObj = $('').addClass(options.containerListClassName), //容器DOM对象
domObj = $(this),
elementItems = domObj.find('.' + options.eleItemClassName),
loadObj = $('').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).append(containerListObj); // 添加响应式容器
$(this).append(loadObj); // 添加加载提示内容
//将创建DOM对象赋值到this当前对象上
this.container = containerListObj; //放置列容器DOM对象
this.items = elementItems; //瀑布流所有元素DOM对象集
this.loading = loadObj; //上拉加载盒子DOM对象
// 生成结果
this.grids = generateGrid(this, options);
// 拼图堆加
jigsawPhoto(this, elementItems, options);
// 监听屏幕大小变化
$(win).resize(function(){
that.container.empty();
// 获取当前显示列数
that.columns = getGridColumn(that.grid);
// 生成结果
that.grids = generateGrid(that, options);
// 拼图堆加
jigsawPhoto(that, that.items, options);
});
// 定义变量判断是否正在获取追加内容
var isLoading = false;
// 执行回调函数位置滚动条高度
this.scrollTop = 0;
// 滑到底部执行函数
this.slideToDownCallback = options.slideToDownCallback;
// 重新执行获取数据,刷新数据
this.refresh = function(){
// 追加获取元素
var newItems = domObj.find('.' + options.eleItemClassName ).each((i, item) => {
that.items.push(item);
});
// 拼图堆加
jigsawPhoto(that, newItems, options);
//
$(doc).scrollTop(that.scrollTop);
isLoading = false;
loadObj.text(options.loadText);
}
// 开始加载 时执行
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.scrollTop = $(doc).scrollTop();
that.slideToDownCallback();
}
});
return this;
}
})(jQuery, window, document);
/**
* 闭包 - 灯箱效果
*/
(function($, win, doc){
/**
* 获取数据
* @param {*} eleWrap
* @param {*} imgSrc
* @param {*} options
* @returns
*/
function getAllImage(eleWrap, imgSrc, options){
var data = {
list: [], //存储所有图片
index: 0 //当前显示图片索引
},
// 如果传入为图片地址,直接使用,否则重新读取
current = 'string' === typeof imgSrc ? imgSrc : $(imgSrc).data(options.imgColumnName);
// 循环获取所有图片
$(eleWrap).find(options.imgName).each(function(index){
var url = $(this).data(options.imgColumnName);
if(url){
// 添加图片
data.list.push($(this).data());
// 判断索引,是否为默认显示项
if(url==current){
data.index = index;
}
}
});
return data;
}
/**
* 图片灯箱效果
* @param {*} options
*/
$.fn.imageBox = function(options){
options = $.extend({
imgName: "img", // 需要添加到灯箱中图片类名或标签名
imgColumnName: "img", // 填充灯箱中图片链接地址
topColumnName: "title", // 填充顶部标题容器的data后缀-title
descriptionColumnName: "desc" // 填充底部描述容器的data后缀-desc
}, options);
var that = this,
// 存储图片信息
imageData = {
list: [], //所有图片
index: -1 //索引
},
// 常量(用来切换是否全屏显示)
CONSTMODE = {
CONTAIN: 'contain',
COVER: 'cover'
},
imageBoxContainer = $('').addClass("image-dialog-wrap").hide(),
flexBox = $('').addClass('dialog-flex-box'),
titleBox = $('').addClass("flex-item dialog-desc-box"), // 底部标题信息
imgBox = $('').addClass('flex-item dialog-image-box'), // 图片信息
topBox = $('').addClass('flex-item dialog-top-box'), // 头部标题信息
btnLeft = $('').addClass('image-btn btn-left'),
btnRight = $('').addClass('image-btn btn-right'),
btnScale = $('').addClass('scale-btn'),
closeBox = $('').addClass('dialog-close'); // 关闭按钮
// 添加到页面中
$('body').append(imageBoxContainer);
// 添加左右切换按钮容器到图片容器中
imgBox.append(btnLeft);
imgBox.append(btnRight);
// 将顶部标题、图片、底部描述三块区域添加到dialog-flex-box容器中
flexBox.append(topBox);
flexBox.append(imgBox);
flexBox.append(titleBox);
// 将dialog-flex-box、关闭按钮、全屏显示按钮添回到的灯箱总容器中
imageBoxContainer.append(flexBox);
imageBoxContainer.append(closeBox);
imageBoxContainer.append(btnScale);
// 重置当前图片
function resetCurrentImage(){
var data = imageData.list[imageData.index],
img = new Image(),
imgMaxHeight = $(win).height() - 200;
// 指定显示图片地址
img.src = data[options.imgColumnName];
// 顶部标题
topBox.attr('data-title', data[options.topColumnName]);
// 底部描述
titleBox.text(data[options.descriptionColumnName]);
// 设置图片样式
imgBox.css({
"background-image": "url('"+data[options.imgColumnName]+"')",
"height": img.height > imgMaxHeight ? imgMaxHeight : img.height
});
}
// 设置模式
function setImageMode(mode){
mode = mode || CONSTMODE.CONTAIN;
// 设置模式
btnScale.data('mode', mode);
btnScale.attr('rel', mode);
imgBox.css('background-size', mode);
// 判断是否全屏显示
if(mode==CONSTMODE.COVER){
imgBox.addClass('cover-box');
}else {
imgBox.removeClass('cover-box');
}
}
setImageMode();
// - - - - - - - - - - - - - - - - - - - - - - 点击事件 Start - - - - - - - - - - - - - - - - - - - - - -
// 向左切换
btnLeft.on('click', function(){
imageData.index = imageData.index - 1 <= 0 ? 0 : imageData.index - 1;
if(imageData.index == 0) btnLeft.prop('disabled', true);
else if(btnLeft.prop('disabled')) btnLeft.prop('disabled', false);
if(btnRight.prop('disabled')) btnRight.prop('disabled', false);
resetCurrentImage();
});
// 向右切换
btnRight.on('click', function(){
imageData.index = imageData.index + 1 >= imageData.list.length - 1 ? imageData.list.length - 1 : imageData.index + 1;
if(imageData.index == imageData.list.length - 1) btnRight.prop('disabled', true);
else if(btnRight.prop('disabled')) btnRight.prop('disabled', false);
if(btnLeft.prop('disabled')) btnLeft.prop('disabled', false);
resetCurrentImage();
});
// 图片显示模式切换
btnScale.on('click', function(){
var mode = $(this).data('mode');
// 如果是COVER则传入CONTAIN,若是CONTAIN则传入COVER
setImageMode(mode==CONSTMODE.CONTAIN?CONSTMODE.COVER:CONSTMODE.CONTAIN);
});
// 关闭弹框
closeBox.on('click', function(){
imageBoxContainer.fadeOut();
});
// 选择图片
$(this).on('click', options.imgName, function(){
// 获取所有图片信息
imageData = getAllImage(that, this, options);
// 重置当前灯箱中显示图片信息
resetCurrentImage();
// 显示灯箱容器
imageBoxContainer.fadeIn();
});
return this;
}
})(jQuery, window, document);
以上仅供参考,如有疑问欢迎交流!