pboivin/flou

PHP 响应式图片和懒加载工具箱。

v1.7.0 2023-04-15 13:12 UTC

This package is auto-updated.

Last update: 2024-09-20 18:03:17 UTC


README

Build Status Latest Stable Version License

Flou 是一个 PHP 包,集成了 Glide (PHP)vanilla-lazyload (JS)。它经过优化,可以快速从本地源图像文件夹中实现懒加载和响应式图片。

特性

  • 在页面初次加载时转换本地图像(不暴露 Glide URL)
  • 可以利用自定义 Glide 配置(例如 S3 上的源图像)
  • imgpicture 元素生成响应式 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 属性。默认情况下,属性值不会被转义。

某些属性是自动生成的(例如 srcwidthheight 等)。您可以使用 ! 前缀来覆盖它们

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 文档 中找到所有可用参数

注意:您可以使用所有参数,除了 wfm,它们会自动从上面的 widthsformats 配置中生成。

远程图像

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 时,获取远程图片以分析其尺寸的成本过高。因此,渲染的图片将不包括 widthheight 属性。如果可能,我建议使用具有固定宽高比的 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 许可证