返回

Laravel Nova Fancybox 图片画廊集成指南

vue.js

Laravel Nova 中集成 Fancybox 实现图片画廊功能

在使用 Laravel Nova 构建项目时, 我遇到了一个需求: 实现一个图片画廊, 具备灯箱(lightbox)和滑块(slider)功能。有一个字段, 它以 JSON 数组形式存储了图片的路径, 我希望把这些图片路径渲染成可点击的缩略图。点击后, 缩略图应在 Fancybox 模态框中打开, 并支持导航(滑块)功能。

问题原因分析

问题在于, 虽然加载了 Fancybox 的 CSS 和 JS 文件,并且通过自定义 JavaScript 文件初始化了 Fancybox, 也用data-fancybox属性绑定到了元素,但 Nova 中的内容可能是动态加载的,直接的绑定可能无法对后续加入的 DOM 元素生效。另一个可能的原因是Nova自身有某些独特的行为或事件需要注意,比如资源的缓存,可能与Fancybox发生冲突.

解决方案

下面针对问题原因,提供具体的解决方案.

1. 确保 jQuery 正确加载和初始化

因为 Fancybox 依赖 jQuery, 务必保证 jQuery 在 Fancybox 之前加载. 在 NovaServiceProvider 里, 我们已经做了这件事。但我们可以通过在fancybox-init.js添加console log进一步确认:

// public/js/fancybox-init.js
$(document).ready(function() {

    console.log("jQuery version:", $.fn.jquery); // 打印jQuery版本
    console.log("Fancybox is loaded:", typeof $.fn.fancybox === 'function'); // 检查 Fancybox 是否加载

    $('[data-fancybox]').fancybox({
        loop: true,
    });
});

打开浏览器开发者工具 (通常按 F12), 在 Console 标签页查看是否有版本号输出, 以及 Fancybox 是否成功加载。 如果没有,尝试调整NovaServiceProviderNova::script引入js的顺序, 确认jQuery是否可用。

2. 使用事件委托

因为 Nova 可能会动态加载内容,直接绑定事件到元素可能不会对后来添加的元素生效. 我们可以用事件委托, 把事件绑定到一个父元素上,即使子元素是动态添加的也能触发事件。

修改 public/js/fancybox-init.js 文件如下:

// public/js/fancybox-init.js
$(document).ready(function() {
    console.log("jQuery version:", $.fn.jquery);
    console.log("Fancybox is loaded:", typeof $.fn.fancybox === 'function');

    // 使用事件委托
    $(document).on('click', '[data-fancybox]', function(event) {
        event.preventDefault(); // 阻止默认行为 (例如链接跳转)

        $.fancybox.open({
          src  : $(this).attr('href'), //打开fancybox
          type : 'image',  //类型是图片
          opts : {
            loop: true,
              //可以继续在这里加入其它配置选项
          }
      });

    });
});

这里使用 $(document).on('click', '[data-fancybox]', ...) 来实现事件委托. 这样无论 [data-fancybox] 元素是何时加入到 DOM 的, 只要点击, 都能触发 Fancybox。使用event.preventDefault() 来阻止了<a>标签的默认行为(直接打开链接), 而是交由fancybox进行展示.

3. 处理 Nova 的资源字段渲染

在 Nova 资源中,我们用 displayUsing 方法来渲染图片画廊的 HTML. 这部分代码目前没有问题. 但为了确保在不同的Nova使用环境中也能有效, 可以考虑添加一个唯一的class方便识别:

use Laravel\Nova\Fields\Text;

Text::make('Images', 'image')
    ->displayUsing(function ($value) {
        $images = json_decode($value, true);
        if (empty($images)) {
            return 'No images available';
        }

        $html = '<div class="gallery fancybox-gallery" style="display: flex; flex-wrap: wrap;">'; // 添加 fancybox-gallery 类
        foreach ($images as $image) {
            $url = asset($image);
            $html .= '<a data-fancybox="gallery" href="' . $url . '" style="margin: 2px;">';
            $html .= '<img src="' . $url . '" alt="Image" style="width: 50px; height: auto;" />';
            $html .= '</a>';
        }
        $html .= '</div>';
        return $html;
    })
    ->asHtml();

然后,在 fancybox-init.js中,也可以改为通过这个class进行事件绑定,虽然上一步的事件委托已可以解决大部分问题,但这样做理论上能提供些微的性能提升(jQuery查找的元素更精确):

// public/js/fancybox-init.js
$(document).ready(function() {
    console.log("jQuery version:", $.fn.jquery);
    console.log("Fancybox is loaded:", typeof $.fn.fancybox === 'function');

    // 使用事件委托, 针对 .fancybox-gallery 内的元素
    $(document).on('click', '.fancybox-gallery [data-fancybox]', function(event) {
          event.preventDefault(); // 阻止默认行为 (例如链接跳转)

        $.fancybox.open({
          src  : $(this).attr('href'), //打开fancybox
          type : 'image',  //类型是图片
          opts : {
            loop: true,
              //可以继续在这里加入其它配置选项
          }
      });
    });
});

4. 处理重复加载和冲突 (进阶)

如果多次进入/离开包含画廊的页面, 或页面上有其他使用 Fancybox 的地方, 可能会出现重复加载或冲突. 可以用一个标志变量, 只进行一次初始化.

// public/js/fancybox-init.js
var fancyboxInitialized = false; // 标志变量

$(document).ready(function() {
    console.log("jQuery version:", $.fn.jquery);
    console.log("Fancybox is loaded:", typeof $.fn.fancybox === 'function');

     if (!fancyboxInitialized) { // 仅当未初始化时才执行
      $(document).on('click', '.fancybox-gallery [data-fancybox]', function(event) {

              event.preventDefault();

              $.fancybox.open({
                  src  : $(this).attr('href'),
                  type : 'image',
                  opts : {
                      loop: true
                  }
              });
          });

          fancyboxInitialized = true; // 设置为已初始化
    }
});

这个改进保证了事件只绑定一次,避免了重复绑定带来的问题.

5. 利用 Nova 的beforeunload事件(进阶)

当你在 Nova 界面切换时, 可能需要做一些清理工作. 比如防止内存泄漏,或重置一些状态. 可以利用Nova的 beforeunload 事件:

首先需要在NovaServiceProvider.php里, 注册这个事件:

use Laravel\Nova\Nova;
use Laravel\Nova\Events\ServingNova;

public function boot()
{
    parent::boot();

    Nova::serving(function (ServingNova $event) {
        // Load Fancybox CSS
        Nova::style('fancybox', 'https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.css');

        // Load jQuery first (Fancybox depends on it)
        Nova::script('jquery', 'https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.min.js');

        // Load Fancybox JS
        Nova::script('fancybox', 'https://cdnjs.cloudflare.com/ajax/libs/fancybox/3.5.7/jquery.fancybox.min.js');

        // Load custom initialization script
         Nova::script('fancybox-init', asset('js/fancybox-init.js'));

        Nova::beforeServe(function () { //关键
                Nova::script('before-unload', asset('js/before-unload.js'));//关键
        }); //关键

    });

}

然后在/public/js/目录下创建 before-unload.js:

// /public/js/before-unload.js

Nova.booting(function () {

  window.addEventListener('beforeunload', function (event) {
        // 在这里可以做清理. 例如:
        $.fancybox.close(true); // 强制关闭所有 Fancybox 实例
       // 移除事件监听等其它操作...
    });

});

通过上述代码,每当 Nova 页面即将卸载时(比如切换到另一个资源), Fancybox 都会被强制关闭, 防止潜在的冲突。

6. 处理缓存 (额外建议)

为了防止浏览器缓存旧的 JavaScript 文件, 可以在引入自定义脚本时添加一个版本号或时间戳:

Nova::script('fancybox-init', asset('js/fancybox-init.js?v=' . time())); //时间戳
//或
Nova::script('fancybox-init', asset('js/fancybox-init.js?v=1.0.1')); //版本号

这能确保每次修改js后,客户端都会重新请求最新文件.

经过以上几步修改和调整, 现在 Fancybox 应该能在 Laravel Nova 中正常工作了,无论是初次加载还是动态加载的图片, 都能正确显示为画廊并有灯箱和滑动效果。记住测试所有可能的使用情况,以保证项目的健壮。