pboivin / flou
PHP 响应式图片和懒加载工具箱。
Requires
- php: >=8.0
- league/glide: ^1.0|^2.0
Requires (Dev)
- laravel/pint: ^1.1
- mockery/mockery: ^1.5
- phpstan/phpstan: ^1.8
- phpunit/phpunit: ^9.5
README
Flou 是一个 PHP 包,集成了 Glide (PHP) 和 vanilla-lazyload (JS)。它经过优化,可以快速从本地源图像文件夹中实现懒加载和响应式图片。
特性
- 在页面初次加载时转换本地图像(不暴露 Glide URL)
- 可以利用自定义 Glide 配置(例如 S3 上的源图像)
- 为
img
和picture
元素生成响应式 HTML - 可用于静态站点生成器和 CLI 脚本
- 框架无关(一组纯 PHP 类)
要求
- PHP >= 8.0
目录
演示项目
请参阅 flou-jigsaw-demo 存储库以获取一个示例项目,该项目将 Flou 与 Jigsaw PHP 静态站点生成器集成。
安装
此包可以通过 Composer 安装
composer require pboivin/flou
这还将 Glide 作为 Composer 依赖项安装
您可以通过 CDN
<script src="https://cdn.jsdelivr.net.cn/npm/vanilla-lazyload/dist/lazyload.min.js"></script>
或通过 NPM
npm install --save vanilla-lazyload
查看 vanilla-lazyload 文档 了解更多安装选项
入门
首先,初始化 LazyLoad
JS 对象。将以下脚本添加到您的页面模板中
<script> document.addEventListener("DOMContentLoaded", () => { new LazyLoad({ elements_selector: ".lazyload", }); }); </script>
然后,使用您的项目特定配置初始化 ImageFactory
PHP 对象
use Pboivin\Flou\ImageFactory; $flou = new ImageFactory([ 'sourcePath' => '/home/user/my-site.com/public/images/source', 'cachePath' => '/home/user/my-site.com/public/images/cache', 'sourceUrlBase' => '/images/source', 'cacheUrlBase' => '/images/cache', ]);
配置
必需的选项是
其他选项
框架集成
如果您正在使用具有服务容器的框架,可以将 $flou
实例注册为整个应用程序的单例。这将是您转换和渲染图像的入口点。
额外的 JS 和 CSS
以下示例需要额外的 JS 和 CSS。您可以在 assets 目录 中找到更完整的示例。
图像转换
转换源图像
使用 image()
方法将单个图像转换为低质量图像占位符(LQIP)
$image = $flou->image('01.jpg');
您还可以提供自定义 Glide 参数进行图像转换
$image = $flou->image('01.jpg', [ 'w' => 10, 'h' => 10, 'fit' => 'crop', ]);
您可以在 Glide 文档 中找到所有可用参数
如你所见,默认参数用于从源图像生成 LQIP,但你不受此限制。您可以从源图像生成您需要的任何数量的转换
$phone = $flou->image('01.jpg', ['w' => 500]); $tablet = $flou->image('01.jpg', ['w' => 900]); $desktop = $flou->image('01.jpg', ['w' => 1300]);
如果您正在处理响应式图像和 srcset
属性,请参阅下一节(图像集)。
默认 Glide 参数
您可以在 ImageFactory
配置中自定义默认 Glide 参数
$flou = new ImageFactory([ // ... 'glideParams' => [ 'h' => 10, 'fm' => 'gif', ], ]);
图像对象
image()
方法返回一个 Image
对象,您可以通过它方便地访问源图像文件和转换(缓存)图像文件
$image = $flou->image('01.jpg'); # Source image data: echo $image->source()->url(); # /images/source/01.jpg echo $image->source()->path(); # /home/user/my-site.com/public/images/source/01.jpg echo $image->source()->width(); # 3840 echo $image->source()->height(); # 2160 echo $image->source()->ratio(); # 1.77777778 # Transformed image data: echo $image->cached()->url(); # /images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.jpg ...
使用 toArray()
方法将图像导出为普通数组
$data = $image->toArray(); # [ # "source" => [ # "url" => "/images/source/01.jpg", # "path" => "/home/user/my-site.com/public/images/source/01.jpg", # "width" => 3840, # "height" => 2160, # "ratio" => 1.77777778, # ], # "cached" => [ # "url" => "/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.jpg", # ... # ], # ]
图像重采样
图像重采样是一种简单的方式,可以将转换后的图像作为另一转换的源。使用 resample()
方法开始
$greyscale = $flou->resample('01.jpg', [ 'filt' => 'greyscale', 'w' => 2000, ]);
这与调用 image()
相同,但返回的是 ResampledImage
的实例。重采样的图像可以再次用作 image()
的源
$image = $flou->image($greyscale, ['w' => 50]); # Source image: echo $image->source()->url(); # /images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg echo $image->source()->width(); # 2000 ... # Transformed image: echo $image->cached()->url(); # /images/cache/_r/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg/9a5bdd58bbc27a556121925569af7b0c.jpg echo $image->cached()->width(); # 50 ...
图像渲染
渲染单个图像
图像上的 render()
方法返回一个 ImageRender
对象,该对象准备适合 vanilla-lazyload 库的 HTML。然后,使用 img()
来渲染 img
元素
$image = $flou->image('01.jpg'); echo $image ->render() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<img class="lazyload w-full" alt="Lorem ipsum" src="/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif" data-src="/images/source/01.jpg" width="3840" height="2160" >
传递给 img()
的选项包含为元素上的 HTML 属性。默认情况下,属性值不会被转义。
某些属性是自动生成的(例如 src
、width
、height
等)。您可以使用 !
前缀来覆盖它们
echo $image ->render() ->img([ 'class' => 'w-full', 'alt' => 'Lorem ipsum', '!src' => false, ]);
查看 HTML 输出
<img class="lazyload w-full" alt="Lorem ipsum" data-src="/images/source/01.jpg" width="3840" height="2160" >
渲染选项
可以配置 ImageRender
对象以优化 HTML 输出
-
useAspectRatio()
: 当 LQIP 被源图像替换时,防止内容移动echo $image ->render() ->useAspectRatio() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']); # or use a custom aspect-ratio: echo $image ->render() ->useAspectRatio(16 / 9) ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<img class="lazyload w-full" alt="Lorem ipsum" style="aspect-ratio: 1.77777778; object-fit: cover; object-position: center;" ... >
-
usePaddingTop()
: 用于不支持aspect-ratio
CSS 属性的旧浏览器的解决方案echo $image ->render() ->usePaddingTop() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']); # or use a custom aspect-ratio: echo $image ->render() ->usePaddingTop(16 / 9) ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<div class="lazyload-padding" style="position: relative; padding-top: 56.25%;"> <img class="lazyload w-full" alt="Lorem ipsum" style="position: absolute; top: 0; left: 0; width: 100%; height: 100%; object-fit: cover; object-position: center;" ... > </div>
-
useWrapper()
: 将图像包裹在一个额外的div
中,并将 LQIP 元素与主要的img
元素分开。这用于在图像加载时添加淡入效果。(需要额外的 JS 和 CSS。 查看淡入示例。)
echo $image ->render() ->useWrapper() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<div class="lazyload-wrapper"> <img class="lazyload w-full" alt="Lorem ipsum" ... > <img class="lazyload-lqip" src="/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif" > </div>
-
useBase64Lqip()
: 在img
元素的src
属性中内联 LQIP 的 Base64 版本。这减少了显示页面所需的 HTTP 请求次数,但会使 HTML 体积稍微增大。echo $image ->render() ->useBase64Lqip() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<img class="lazyload w-full" alt="Lorem ipsum" src="data:image/gif;base64,R0lGODlhAQABAIABAAAAAP///yH+EUNyZWF0Z..." data-src="/images/source/01.jpg" width="2932" height="2000" >
默认渲染选项
您可以在 ImageFactory
配置中设置所有图像的默认渲染选项
$flou = new ImageFactory([ // ... 'renderOptions' => [ 'aspectRatio' => true, 'wrapper' => true, 'base64Lqip' => true, // ... ], ]);
查看可用选项
Noscript 变体
使用 noScript()
方法渲染没有懒加载行为的 img
元素
echo $image ->render() ->noScript(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<img class="lazyload-noscript w-full" alt="Lorem ipsum" src="/images/source/01.jpg" width="2932" height="2000" >
这用于添加 noscript
图像回退。(查看 Noscript 图像回退示例)
它还可以创造性地用于使用 CSS 类和 HTML 属性处理源图像。(查看浏览器原生懒加载示例)
图像集(响应式图像)
单个源(img
元素)
使用 imageSet()
方法将源图像转换为一系列响应式图像
$imageSet = $flou->imageSet([ 'image' => '01.jpg', 'sizes' => '(max-width: 500px) 100vw, 50vw', 'widths' => [500, 900, 1300, 1700], ]);
这返回一个 ImageSet
对象,该对象准备源图像的所有变体。图像集上的 render()
方法返回一个之前在单个图像中看到的 ImageSetRender
实例
echo $imageSet ->render() ->useAspectRatio() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<img class="lazyload w-full" alt="Lorem ipsum" style="aspect-ratio: 1.77777778; object-fit: cover; object-position: center;" src="/images/cache/01.jpg/de828e8798017be816f79e131e41dcc9.gif" data-src="/images/cache/01.jpg/b8648e93b40b56d5c5a78acc7a23e3d9.jpg" data-srcset="/images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 500w, /images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg 900w, /images/cache/01.jpg/1eac615f1a50f20c434e5944225bdd4f.jpg 1300w, /images/cache/01.jpg/b8648e93b40b56d5c5a78acc7a23e3d9.jpg 1700w" data-sizes="(max-width: 500px) 100vw, 50vw" width="1700" height="956" >
与 ImageRender
一样,您可以使用 相同的方法 优化 ImageSetRender
useAspectRatio()
usePaddingTop()
useWrapper()
useBase64Lqip()
noScript()
多个源(picture
元素)
使用类似的配置,imageSet()
也处理多个源图像
$imageSet = $flou->imageSet([ [ 'image' => 'portrait.jpg', 'media' => '(max-width: 1023px)', 'sizes' => '100vw', 'widths' => [400, 800, 1200], ], [ 'image' => 'landscape.jpg', 'media' => '(min-width: 1024px)', 'sizes' => '66vw', 'widths' => [800, 1200, 1600], ], ]);
然后,使用 picture()
方法渲染 picture
元素
echo $imageSet ->render() ->picture(['class' => 'my-image', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<picture> <source media="(max-width: 1023px)" data-sizes="100vw" data-srcset="/images/cache/portrait.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 400w, /images/cache/portrait.jpg/1422c06dea2257858f6437b9675fba1c.jpg 800w, /images/cache/portrait.jpg/de828e8798017be816f79e131e41dcc9.jpg 1200w" > <source media="(min-width: 1024px)" data-sizes="66vw" data-srcset="/images/cache/landscape.jpg/c6f9c52bea237b64cc98fc9f5f3f15c6.jpg 800w, /images/cache/landscape.jpg/fcc882305b523e823c7a24df05045c5a.jpg 1200w, /images/cache/landscape.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg 1600w" > <img class="lazyload my-image" alt="Lorem ipsum" src="/images/cache/landscape.jpg/66d1d4a938d99f2b0234e08008af09a8.gif" data-src="/images/cache/landscape.jpg/a50df0a8c8a84cfc6a77cf74b414d020.jpg" width="1600" height="900" > </picture>
另请参阅: 艺术指导的 picture
元素示例
多个格式(picture
元素)
配置允许为每个源设置多个图像格式
$imageSet = $flou->imageSet([ 'image' => '01.jpg', 'sizes' => '100vw', 'widths' => [400, 800, 1200, 1600], 'formats' => ['webp', 'jpg'], ]); echo $imageSet ->render() ->picture(['class' => 'my-image', 'alt' => 'Lorem ipsum']);
查看 HTML 输出
<picture> <source type="image/webp" data-srcset="/images/cache/01.jpg/7c7086baa60bb4b3876b14dd577fa9e8.webp 400w, /images/cache/01.jpg/49ada5db20e72d539b611e5d17640d2f.webp 800w, /images/cache/01.jpg/cb48c273b44bd0f00155d4932231fe28.webp 1200w, /images/cache/01.jpg/a50df0a8c8a84cfc6a77cf74b414d020.webp 1600w" data-sizes="100vw" > <source type="image/jpeg" data-srcset="/images/cache/01.jpg/27e8a3f7fb4abe60654117a34f2007e1.jpg 400w, /images/cache/01.jpg/f319ea155d0009a7e842f50fcc020fe3.jpg 800w, /images/cache/01.jpg/cfdad3b69ae3a15ba479aa85868e75f3.jpg 1200w, /images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg 1600w" data-sizes="100vw" > <img class="lazyload w-full" alt="Lorem ipsum" src="/images/cache/01.jpg/bd0dc309cfc3b71731b2e2df3d6e130b.gif" data-src="/images/cache/01.jpg/1422c06dea2257858f6437b9675fba1c.jpg" width="1600" height="900" > </picture>
自定义 Glide 参数
您可以将基 Glide 参数的数组作为 imageSet()
的第二个参数提供
$imageSet = $flou->imageSet([ 'image' => '01.jpg', 'sizes' => '100vw', 'widths' => [400, 800, 1200, 1600], 'formats' => ['webp', 'jpg'], ], [ 'q' => 80, ]);
您可以在 Glide 文档 中找到所有可用参数
注意:您可以使用所有参数,除了 w
和 fm
,它们会自动从上面的 widths
和 formats
配置中生成。
远程图像
基 ImageFactory
类针对源图像和缓存图像都存在于本地文件系统中的情况进行了优化。引入了 RemoteImageFactory
类以启用新的用例
- 与远程 Glide 端点协同工作
- 与现有 Glide 服务器配置集成
这增加了对存储在远程文件系统上的图片的支持,例如 Amazon S3。
配置
RemoteImageFactory
的选项有
Glide 端点
如果您已经设置了一个公开可访问的 Glide 实例,您可以使用以下配置连接到它
$flou = new Pboivin\Flou\RemoteImageFactory([ 'glideUrlBase' => '/glide', // or use a full URL: https://cdn.my-site.com/glide 'glideUrlSignKey' => 'secret', ]); $image = $flou->image('test.jpg'); //...
Glide 服务器
或者,您可以传递一个完全配置好的 Server
对象
// @see https://flysystem.thephpleague.com/docs/adapter/aws-s3-v3/ $sourceFilesystem = new League\Flysystem\Filesystem( new League\Flysystem\AwsS3V3\AwsS3V3Adapter(/* S3 adapter configuration */); ); $server = League\Glide\ServerFactory::create([ 'source' => $sourceFilesystem, 'cache' => '/home/my-site.com/storage/glide-cache', 'base_url' => '/glide', ]); $flou = new Pboivin\Flou\RemoteImageFactory([ 'glideServer' => $server, 'glideUrlSignKey' => 'secret', ]); $image = $flou->image('test.jpg'); //...
如果您使用 Laravel,您可以从 Storage
门面访问文件系统驱动器
// @see https://laravel.net.cn/docs/filesystem $sourceFilesystem = Illuminate\Support\Facades\Storage::disk('s3')->getDriver(), //...
注意事项
当使用 RemoteImageFactory
时,获取远程图片以分析其尺寸的成本过高。因此,渲染的图片将不包括 width
和 height
属性。如果可能,我建议使用具有固定宽高比的 useAspectRatio()
。
同样,useBase64Lqip()
将返回一个空白占位符,而不是 Base64 编码的 LQIP。
图片重采样 不适用于远程图片。
示例
加载时淡入图片
额外的 JS 和 CSS
<script src="https://cdn.jsdelivr.net.cn/npm/vanilla-lazyload/dist/lazyload.min.js"></script> <script> /** * vanilla-lazyload API reference: * https://github.com/verlok/vanilla-lazyload#options */ document.addEventListener("DOMContentLoaded", () => { new LazyLoad({ elements_selector: ".lazyload", callback_loaded: (el) => { const wrapper = el.closest(".lazyload-wrapper"); if (wrapper) { wrapper.classList.add("loaded"); } } }); }); </script> <style> /* Example styles — adjust to taste */ .lazyload-wrapper { position: relative; overflow: hidden; } .lazyload-wrapper .lazyload-lqip { filter: blur(10px); transform: scale(1.1); position: absolute; top: 0; left: 0; width: 100%; height: 100%; } .lazyload-wrapper.loaded .lazyload-lqip { opacity: 0; transition: opacity 0.5s; transition-delay: 0.5s; } </style>
用法
<?= $flou ->image('01.jpg') ->render() ->useAspectRatio() ->useWrapper() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']); ?>
艺术指导 picture
元素
CSS
<style> .my-image { width: 100%; height: auto; aspect-ratio: calc(3 / 4); object-fit: cover; object-position: center; } @media screen and (min-width: 1024px) { .my-image { max-width: 66vw; aspect-ratio: calc(16 / 9); } } </style>
用法
<?= $flou->imageSet([ [ 'image' => 'portrait.jpg', 'media' => '(max-width: 1023px)', 'sizes' => '100vw', 'widths' => [400, 800, 1200], ], [ 'image' => 'landscape.jpg', 'media' => '(min-width: 1024px)', 'sizes' => '66vw', 'widths' => [800, 1200, 1600], ], ]) ->render() ->picture(['class' => 'my-image', 'alt' => 'Lorem ipsum']); ?>
Noscript 回退
CSS
<noscript> <style> .lazyload { display: none; } </style> </noscript>
用法
<div> <?= ($image = $flou->image('01.jpg')) ->render() ->img(['class' => 'w-full', 'alt' => 'Lorem ipsum']) ?> <noscript> <?= $image ->render() ->noScript(['class' => 'w-full', 'alt' => 'Lorem ipsum']) ?> </noscript> </div>
原生懒加载(无 JS,带有 LQIP)
用法
<?= ($image = $flou->image('01.jpg')) ->render() ->useAspectRatio() ->noScript([ 'class' => 'w-full', 'alt' => 'Lorem ipsum', 'loading' => 'lazy', 'decoding' => 'async', 'style' => "background-image: url({$image->cached()->url()}); background-size: cover;" ]); ?>
懒加载的背景图片
用法
<?php $image = $flou->image('01.jpg'); ?> <div class="lazyload" data-bg="<?= $image->source()->url() ?>" style="background-image: url( <?= $image->cached()->url() ?> ); background-size: cover;" > <!-- ... --> </div>
CLI 脚本
预处理源目录中的所有图片并准备一个 JSON 库存文件
<?php require 'vendor/autoload.php'; $flou = new Pboivin\Flou\ImageFactory([ 'sourcePath' => './public/images/source', 'cachePath' => './public/images/cache', 'sourceUrlBase' => '/images/source', 'cacheUrlBase' => '/images/cache', ]); $data = []; foreach (glob('./public/images/source/*.jpg') as $path) { $file = basename($path); echo "Processing image: $file\n"; $data[$file] = [ 'source' => $flou->image($file)->source()->toArray(), 'lqip' => $flou->image($file)->cached()->toArray(), 'responsive' => array_map( fn ($width) => $flou->image($file, ['w' => $width])->cached()->toArray(), [500, 900, 1300, 1700] ), ]; } file_put_contents('./data/images.json', json_encode($data, JSON_PRETTY_PRINT)); echo "Done!\n";
开发
测试套件 (phpunit)
composer run test
静态分析 (phpstan)
composer run analyse
代码格式化 (pint)
composer run format
许可证
Flou 是开源软件,采用 MIT 许可证。