在这之前,有写过一篇关于实现瀑布流的文章,后期有人留言提出需要添加灯箱效果的功能,所以这次则讲述下如何实现此功能。由于该篇接上篇写的: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-阿里巴巴矢量图标库
二、JS功能开发
2.1 闭包
如上篇瀑布流功能函数定义,也在闭包中进行功能开发,代码如下:
/** * 闭包 - 灯箱效果 */ (function($, win, doc){ /** * 图片灯箱效果 * @param {*} options */ $.fn.imageBox = function(options){ // 须链式调用,返回this return this; } })(jQuery, window, document);
复制
2.2 定义传参
如上图,页面结构包含”顶部一段话“、”图片“、”底部描述内容“三块,这里我们如何获取对应内容,并将渲染到灯箱指定位置呢?所以这块需要做对外接口,使其调整灵活点,读取指定位置内容进行渲染即可。如下代码:
<img src="${item.img}" alt="" data-img="${item.img}" data-title="顶部一段话" data-desc="${item.description}"
复制
如上,我们在渲染时,将三块内容分别存放在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);
复制
2.3 页面调用
这里将上篇瀑布流中页面代码稍作调整,在img上添加2.2中所示data数据,以及在执行瀑布流功能函数前,执行灯箱功能函数调用,代码如下:
<body> <!-- 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" }, // 略... ]; // 将获取的数据填充到dom中,返回拼接后的html function generateHtml(item, count){ return `<a href="javascript:;" class="item"> <img src="${item.img}" alt="" data-img="${item.img}" data-title="顶部一段话" data-desc="${item.description}" /> <div class="txt-box"> <h3>${item.title}-${count}</h3> <p>${item.description}</p> </div> </div>`; } var loadIndex = 0; // ajax 加载数据 function ajaxLoadData(that){ // 略 } $(function(){ // 添加灯箱效果 $('#waterfall').imageBox({ imgName: "img[data-img]", imgColumnName: "img" }); // 实例瀑布流 var waterfall = $('#waterfall').waterfall({ eleItemClassName: "item", loadingText: "正在加载数据中...", loadOverText: "没有更多数据了", loadText: "~ 下拉加载更多数据 ~", // 滑到底部执行函数 slideToDownCallback: function(){ ajaxLoadData(this); } }); // 请求数据 ajaxLoadData(waterfall); }); </script>
复制
2.4 图解并定义容器
上图中说明了各区域的类名,以及各容器间关系(包含与被包含),现在我们将根据此图,在灯箱功能函数执行时,初始化灯箱打开后的页面结构。代码如下:
/** * 闭包 - 灯箱效果 */ (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 = $('<div />').addClass("image-dialog-wrap").hide(), flexBox = $('<div />').addClass('dialog-flex-box'), titleBox = $('<div />').addClass("flex-item dialog-desc-box"), // 底部标题信息 imgBox = $('<div />').addClass('flex-item dialog-image-box'), // 图片信息 topBox = $('<div />').addClass('flex-item dialog-top-box'), // 头部标题信息 btnLeft = $('<button type="button" />').addClass('image-btn btn-left'), btnRight = $('<button type="button" />').addClass('image-btn btn-right'), btnScale = $('<button type="button" />').addClass('scale-btn'), closeBox = $('<div />').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.5 点击事件
以上准备工作做完后,现在可以实现具体功能了。首先点击图片时,需要获取瀑布流容器中需要添加到灯箱中的所有图片信息,这里是每次点击都重新获取一次,由于瀑布流是上拉加载的,所以页面中图片数量是处于变化中的。
如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... });
复制
2.6 获取全部图片
在闭包中定义函数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; }
复制
2.7 重构当前显示图片信息
将函数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'); } }
复制
2.8 默认显示模式
灯箱功能函数初始时,在灯箱结构部分初始完成后,默认执行一次setImageMode()函数,设置灯箱图片显示模式。
2.9 显示灯箱
在2.5中,将选择图片事件中,添加如下代码,此时点击图片后则可以正常显示灯箱效果了。
getAllImage()函数执行后,会返回对应的list和index结构,是和imageData结构一致,所以获取后直接赋值给它即可,后期图片左右切换会使用到。
// 选择图片 $(this).on('click', options.imgName, function(){ // 获取所有图片信息 imageData = getAllImage(that, this, options); // 重置当前灯箱中显示图片信息 resetCurrentImage(); // 显示灯箱容器 imageBoxContainer.fadeIn(); });
复制
效果如下:
2.10 左右切换
在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(); });
复制
2.11 全屏显示
全屏显示效果是结合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); });
复制
2.12 瀑布流和灯箱的JS代码
这里将瀑布流和灯箱的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 = $('<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; } /** * 获取每列中高度最小值元素 * @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 = $('<div />').addClass(options.containerListClassName), //容器DOM对象 domObj = $(this), elementItems = domObj.find('.' + options.eleItemClassName), 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).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 = $('<div />').addClass("image-dialog-wrap").hide(), flexBox = $('<div />').addClass('dialog-flex-box'), titleBox = $('<div />').addClass("flex-item dialog-desc-box"), // 底部标题信息 imgBox = $('<div />').addClass('flex-item dialog-image-box'), // 图片信息 topBox = $('<div />').addClass('flex-item dialog-top-box'), // 头部标题信息 btnLeft = $('<button type="button" />').addClass('image-btn btn-left'), btnRight = $('<button type="button" />').addClass('image-btn btn-right'), btnScale = $('<button type="button" />').addClass('scale-btn'), closeBox = $('<div />').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);
复制
以上仅供参考,如有疑问欢迎交流!