使用 Webpack 3 实现 Slick 懒加载图片网格
如何以类似Facebook在帖子中显示图片的方式,在灵活的网格中布局和延迟加载图片。选定的图片将打开一个灯箱,可在轮播中预览。图片的alt文本将转换为图片下方的说明文字。

概述
如何以类似Facebook在帖子中显示图片的方式,在灵活的网格中布局和延迟加载图片。选定的图片将打开一个灯箱,可在轮播中预览。图片的alt文本将转换为图片下方的说明文字。
特点
- 延迟图片加载
- ES6 转译器
- JavaScript 源映射
- Sass CSS 预处理器
- PostCSS Autoprefixer
- CSSnano
- Webpack 3
源代码: https://github.com/jimfrenette/uiCookbook/tree/master/photogrid
入门
如果您尚未安装Node.js,那么这是首要任务。请前往Node.js并进行安装。
在您的CLI中导航到项目根目录,例如终端、Cygwin或PowerShell。
输入npm init
以交互方式创建package.json文件。暂时接受默认选项即可。此文件中的元数据可以根据需要稍后更新。
npm init
现在项目根目录下应该存在一个package.json
文件。这将在稍后由npm用于安装模块和运行脚本。
在项目根目录中创建一个index.html文件。在body中添加一个带有.photogrid
样式类的无序图片列表。对于延迟图片加载,不要使用src
属性,而是将图片路径放在名为data-src
的数据属性中。同时在文档head中包含dist/style.css
链接,并在body结束标签之前包含dist/app.js
链接。
index.html
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <link rel="stylesheet" media="all" href="dist/style.css"> </head> <body> <ul class="photogrid"> <li> <img data-src="https://images.unsplash.com/reserve/unsplash_528b27288f41f_1.JPG?auto=format&fit=crop&w=2700&q=80&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D" alt="Sea breeze and splashing waves. A photo by @dankapeter on Unsplash"/> </li> <li> <img data-src="https://images.unsplash.com/photo-1494633114655-819eb91fde40?auto=format&fit=crop&w=2550&q=80&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D" alt="Above it All. A photo by @anthonyintraversato on Unsplash" /> </li> <li> <img data-src="https://images.unsplash.com/photo-1511125357779-27038c647d9d?auto=format&fit=crop&w=2551&q=80&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D" alt="Found this beauty while being lost in the streets of Cancún, Mexico. A photo by @odiin on Unsplash" /> </li> <li> <img data-src="https://images.unsplash.com/photo-1483919283443-8db97e2bcd81?auto=format&fit=crop&w=2550&q=80&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D" alt="Touring NYC. A photo by @freddymarschall on Unsplash" /> </li> <li> <img data-src="https://images.unsplash.com/photo-1487357298028-b07e960d15a9?auto=format&fit=crop&w=2550&q=80&ixid=dW5zcGxhc2guY29tOzs7Ozs%3D" alt="Wind turbines, Greece. A photo by @jeisblack on Unsplash" /> </li> </ul> <script async src="dist/app.js"></script> </body> </html>
使用Emmet(内置于VS Code中),您可以通过在第一行输入感叹号然后选择Tab键来创建index.html内容。
图片延迟加载
这是通过David Walsh的简单图片延迟加载和渐入方法实现的。
创建一个名为src
的文件夹。在该文件夹中,创建一个js
文件夹和一个sass
文件夹。
在js文件夹中,创建一个index.js
应用程序入口文件和一个lazyimage.js
模块文件。
在 sass 文件夹中,创建一个 style.scss
文件,它将用作 Sass 处理的入口点。添加三个 Sass 分片,_base.scss
、_photogrid.scss
和 _lazyimage.scss
。文件名中的下划线表示该文件是 Sass 分片,因此只有在导入时才会被处理。
在 lazyimage.js 模块中,导出此 Lazyimage ES6 类。
lazyimage.js
export default class Lazyimage { constructor(options) { this.init(); } init() { [].forEach.call(document.querySelectorAll('img[data-src]'), function(img) { img.setAttribute('src', img.getAttribute('data-src')); img.onload = function() { img.removeAttribute('data-src'); }; }); } }
在应用程序入口点导入 lazyimage 模块。
index.js
import Lazyimage from './lazyimage' new Lazyimage();
将此Sass添加到基本分片中,用于无序列表和图片元素的默认样式。
_base.scss
ul { padding: 0; } img { border-style: none; height: auto; max-width: 100%; }
将此Sass添加到photogrid分片中,使用flexbox将photogrid样式应用于图片列表。
_photogrid.scss
ul.photogrid { margin: 0.5vw 0.5vw 0.5vw -0.5vw; font-size: 0; flex-flow: row wrap; display: flex; li { flex: auto; width: 200px; margin: 0.5vw; } }
将此 Sass 添加到 lazyimage 部分,以便在图片加载完毕且 data-src
属性已移除后,图片淡入。
_lazyimage.scss
img { opacity: 1; transition: opacity 0.3s; } img[data-src] { opacity: 0; }
将三个分片导入到style Sass文件中。
style.scss
@import "base"; @import "photogrid"; @import "lazyimage";
Webpack
安装Webpack。截至本文撰写之时,版本3.10是最新版本。
npm install --save-dev webpack
在项目根目录下创建 webpack.config.js 文件。此应用程序的 CSS 是使用我上个月的帖子中记录的过程构建的,Webpack 3 Sass cssnano Autoprefixer 工作流。建议您阅读该文章以获取更多信息。
webpack.config.js
const path = require('path') const webpack = require('webpack') const ExtractTextPlugin = require('extract-text-webpack-plugin') module.exports = { context: path.resolve(__dirname, './src'), entry: { app: './js/index.js', css: './sass/style.scss', }, output: { path: path.resolve(__dirname, './dist'), publicPath: '/dist/', filename: '[name].js' }, module: { rules: [ { test: /\.(css|scss)$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: 'css-loader', options: { minimize: true || {/* CSSNano Options */} } }, { loader: 'postcss-loader' }, { loader: 'sass-loader' } ] }) }, { test: /\.js$/, use: 'babel-loader' } ] }, plugins: [ new ExtractTextPlugin('style.css'), ], devtool: '#eval-source-map' }
提取文本插件
安装 extract-text-webpack-plugin。此插件用于将 CSS 从包中提取到 style.css 文件中。
npm install --save-dev extract-text-webpack-plugin
自动前缀
Autoprefixer 使用 caniuse.com 数据评估 CSS 并添加或删除供应商前缀,例如 -webkit 和 -moz。
安装 autoprefixer PostCSS 插件。
npm install --save-dev autoprefixer
- 由于
devDependencies
是使用--save-dev
选项安装的,所以 package.json 文件将更新,以便可以使用npm install
重新安装 node 模块。
在 package.json 文件中,添加一个 browserlist 配置,列出 autoprefixer 的最低浏览器支持。
package.json
{ "name": "photogrid", "version": "1.0.0", "description": "", "main": "index.js", "browserslist": [ "> 2%", "last 2 versions", "ie > 9" ], ... }
在项目根目录中创建一个 PostCSS 配置模块,用于引入 autoprefixer 插件。
postcss.config.js
module.exports = { plugins: [ require('autoprefixer') ] }
Babel
Babel是一个JavaScript转译器,它将项目的ES6代码转换为ES5 JavaScript。安装Babel、加载器和预设。
npm install --save-dev babel-core babel-loader babel-preset-es2015
在项目根目录中创建一个.babelrc配置文件。
.babelrc
{ "presets": [ [ "es2015", { "modules": false } ] ] }
SASS 和 CSS 加载器
安装这些加载器以处理 extract-text-webpack-plugin 的 Sass 和 CSS。
SASS Loader 将 Sass 编译为 CSS,也需要 node-sass。
npm install --save-dev sass-loader node-sass
PostCSS Loader 使用 PostCSS 处理 CSS。
npm install --save-dev postcss-loader
CSS Loader 解析 CSS 中的 import at-rules 和 url 函数。
npm install --save-dev css-loader
Style Loader 在 DOM 中内联 <style></style>
。
npm install --save-dev style-loader
首次构建
安装 cross-dev,以便在 Windows 和 OS X 等各种平台上运行 NPM 脚本时,更容易设置和使用开发和生产环境的 NODE_ENV 变量。
npm install --save-dev cross-env
在 package.json
文件中,为 npm-run-script 定义 dev
和 build
命令。这些命令用于执行开发或生产 webpack 打包过程。
package.json
... ], "scripts": { "dev": "cross-env NODE_ENV=development webpack --watch --progress --colors", "build": "cross-env NODE_ENV=production webpack --progress --hide-modules", "test": "echo \"Error: no test specified\" && exit 1" }, ... }
在 webpack 配置 JavaScript 的 module.export 之后,添加此代码,用于各自的 dev 和 build npm 脚本,并使用 NODE_ENV 设置。
webpack.config.js
... if (process.env.NODE_ENV === 'production') { module.exports.devtool = '#source-map' module.exports.plugins = (module.exports.plugins || []).concat([ new webpack.DefinePlugin({ 'process.env': { NODE_ENV: '"production"' } }), new webpack.optimize.UglifyJsPlugin({ sourceMap: true, compress: { warnings: false } }), new webpack.LoaderOptionsPlugin({ minimize: true }) ]) }
- 请注意,上面代码片段中的省略号 ... 并非实际代码的一部分,它仅表示被跳过且与示例不相关的代码。在遇到其余代码片段中的省略号时,请记住这一点。要查看完整文件,请查看源代码。
使用npm-run-scripts
别名npm run
,执行构建。
npm run build
您现在应该在项目根目录中有一个新的 dist
文件夹,其中包含 css 和 js 输出。在浏览器中加载网页,查看照片网格是否按预期呈现。
灯箱
在 src/js
文件夹中创建一个名为 lightbox.js
的新文件,并在文件顶部添加 jQuery 和 slick-carousel 导入语句。
lightbox.js
import $ from 'jquery' import 'slick-carousel'
使用 npm 并带上 --save
选项安装 jQuery 和 Slick。这会将它们列为 package.json
文件中的 dependencies
。
npm install --save jquery slick-carousel
- 请注意,多个包可以通过单个
npm install
命令安装。为了减少输入,可以使用 npm install 别名npm i
。更多信息请参阅 npm-install 文档。
将 Lightbox 类添加到 lightbox.js 模块中。
lightbox.js
... export default class Lightbox { constructor(options) { this.settings = $.extend({/* defaults */}, options); this.init(); } init() { let source = $(this.settings.source); if (source.length) { source.each((index, el) => { this.create(index, el); }); } } create(index, el) { let lightbox = this.settings.name + '__' + index, opener = $(el).find(this.settings.opener); $('body').append('<div data-lightbox="' + lightbox + '" class="lightbox"><div></div></div>'); if (this.settings.type === 'slider') { $('div[data-lightbox="' + lightbox + '"] > div') .append('<div class="lightbox-slider"></div>'); var slider = $('div[data-lightbox="' + lightbox + '"] .lightbox-slider'); slider.slick({ dots: true }); opener.each((index, el) => { this.popSlider(lightbox, slider, el); }); } // close button $('div[data-lightbox="' + lightbox + '"] > div') .prepend('<a class="lightbox-close" href="javascript:void(0)">+</a>'); $('.lightbox-close').on( 'click', function() { $('[data-lightbox="' + lightbox + '"]').removeClass('is-open'); }); //close on outside click window.onclick = function(evt) { if (evt.target.dataset.lightbox == lightbox) { $('[data-lightbox="' + lightbox + '"]').removeClass('is-open'); } } // close on escape $(document).keyup(function(evt) { if (evt.which === 27) { $('[data-lightbox="' + lightbox + '"]').removeClass('is-open'); } }); } popSlider(lightbox, slider, el) { let img = $(el).find('img'), src = img.prop('src'), slide = document.createElement('div'), slideImg = document.createElement('img'); slideImg.src = src; slide.appendChild(slideImg); if (img.attr('alt')) { let caption = document.createElement('p'), captionText = document.createTextNode(img.attr('alt')); caption.appendChild(captionText); slide.appendChild(caption); } slider.slick('slickAdd', slide); img.wrap('<a href="' + src + '"></a>').on( 'click', function(evt) { evt.preventDefault(); $('[data-lightbox="' + lightbox + '"]').addClass('is-open'); let index = $(this).closest(el).index(); slider.slick('slickGoTo', index); }); } }
在应用程序入口点,导入 lightbox 模块并设置目标为 photogrid 的选项。
index.js
... import Lightbox from './lightbox' new Lightbox({ name: 'lightbox', source: '.photogrid', opener: 'li', type: 'slider' });
在 src/sass
文件夹中,创建一个名为 _lightbox.scss
的新 Sass 分部文件。
_lightbox.scss
/* overlay (background) */ .lightbox { max-height: 0; /* instead of `display: none` so slider can init properly. */ position: fixed; /* stay in place */ z-index: 1; left: 0; top: 0; width: 100%; height: 100%; overflow: auto; /* enable scroll if needed */ background-color: rgb(0,0,0); /* fallback color */ background-color: rgba(0,0,0,0.75); /* black w/ opacity */ > div { position: relative; background-color: rgb(0,0,0); margin: 15% auto; /* 15% from the top and centered */ padding: 20px; color: #fff; //width: 100%; /* could be more or less, depending on screen size */ .slick-prev, .slick-next { z-index: 10; } .slick-prev { left: -20px; } .slick-next { right: -20px; } .slick-dots { li button:before { color: #fff; } } } &.is-open { max-height: 100%; /* unhide */ } } /* close button */ .lightbox-close { position: absolute; top: 2px; right: 2px; text-align: center; line-height: 20px; font-size: 20px; font-weight: bold; color: rgba(255,255,255,0.75); width: 20px; height: 20px; transform: rotate(45deg); text-decoration: none; z-index: 10; &:hover { color: rgb(255,255,255); } }
将 lightbox 分片、slick 和 slick-theme 导入到 style Sass 文件中。
style.scss
... @import "lightbox"; /* node_modules */ @import "~slick-carousel/slick/slick.scss"; @import "~slick-carousel/slick/slick-theme.scss";
构建
在再次构建应用程序和 CSS 之前,需要更新 webpack 配置以用于 slick-theme 中的字体 URL 加载。以下是更新后的模块配置。
webpack.config.js
... module: { rules: [ { test: /\.(css|scss)$/, use: ExtractTextPlugin.extract({ fallback: 'style-loader', use: [ { loader: 'css-loader', options: { minimize: true || {/* CSSNano Options */} } }, { loader: 'postcss-loader' }, { /* for ~slick-carousel/slick/slick-theme.scss */ loader: 'resolve-url-loader' }, { /* for resolve-url-loader: source maps must be enabled on any preceding loader */ loader: 'sass-loader?sourceMap' } ] }) }, { test: /\.js$/, use: 'babel-loader' }, { /* for ~slick-carousel/slick/slick-theme.scss */ test: /\.(eot|woff|woff2|ttf|svg|png|jpg|gif)$/, loader: 'url-loader?limit=30000&name=[name]-[hash].[ext]' } ] } ...
安装 url-loader 和 resolve-url-loader,以便 webpack 处理 slick-theme 中的相对路径。
npm install --save-dev url-loader resolve-url-loader
运行构建。
npm run build
- 对于开发,使用
npm run dev
监视更改并在保存更改时增量构建。
就是这样,刷新浏览器并选择一张照片以打开照片预览灯箱和滑块。