Просмотр исходного кода

Closes #20211: Use thumbnails for ImageAttachment hover previews to improve page load performance (#21386)

Martin Hauser 3 дней назад
Родитель
Сommit
23f1c86e9c

+ 13 - 2
netbox/extras/tables/tables.py

@@ -39,9 +39,20 @@ __all__ = (
 )
 
 IMAGEATTACHMENT_IMAGE = """
+{% load thumbnail %}
 {% if record.image %}
-  <a href="{{ record.image.url }}" target="_blank" class="image-preview" data-bs-placement="top">
-    <i class="mdi mdi-image"></i></a>
+  {% thumbnail record.image "400x400" as tn %}
+    <a href="{{ record.get_absolute_url }}"
+       class="image-preview"
+       data-preview-url="{{ tn.url }}"
+       data-bs-placement="left"
+       title="{{ record.filename }}"
+       rel="noopener noreferrer"
+       target="_blank"
+       aria-label="{{ record.filename }}">
+      <i class="mdi mdi-image"></i>
+    </a>
+  {% endthumbnail %}
 {% endif %}
 <a href="{{ record.get_absolute_url }}">{{ record.filename|truncate_middle:16 }}</a>
 """

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.css


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js


Разница между файлами не показана из-за своего большого размера
+ 0 - 0
netbox/project-static/dist/netbox.js.map


+ 9 - 7
netbox/project-static/src/bs.ts

@@ -150,20 +150,22 @@ function initSidebarAccordions(): void {
  */
 function initImagePreview(): void {
   for (const element of getElements<HTMLAnchorElement>('a.image-preview')) {
-    // Generate a max-width that's a quarter of the screen's width (note - the actual element
-    // width will be slightly larger due to the popover body's padding).
-    const maxWidth = `${Math.round(window.innerWidth / 4)}px`;
+    // Prefer a thumbnail URL for the popover (so we don't preload full-size images),
+    // but fall back to the link target if no thumbnail was provided.
+    const previewUrl = element.dataset.previewUrl ?? element.href;
+    const image = createElement('img', { src: previewUrl });
 
-    // Create an image element that uses the linked image as its `src`.
-    const image = createElement('img', { src: element.href });
-    image.style.maxWidth = maxWidth;
+    // Ensure lazy loading and async decoding
+    image.loading = 'lazy';
+    image.decoding = 'async';
 
     // Create a container for the image.
     const content = createElement('div', null, null, [image]);
 
     // Initialize the Bootstrap Popper instance.
     new Popover(element, {
-      // Attach this custom class to the popover so that it styling can be controlled via CSS.
+      // Attach this custom class to the popover so that its styling
+      // can be controlled via CSS.
       customClass: 'image-preview-popover',
       trigger: 'hover',
       html: true,

+ 23 - 0
netbox/project-static/styles/custom/_misc.scss

@@ -89,6 +89,29 @@ img.plugin-icon {
   }
 }
 
+// Image preview popover (rendered for <a.image-preview> by initImagePreview())
+.image-preview-popover {
+  --bs-popover-max-width: clamp(240px, 25vw, 640px);
+
+  .popover-header {
+    overflow: hidden;
+    text-overflow: ellipsis;
+    white-space: nowrap;
+  }
+  .popover-body {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+  img {
+    display: block;
+    max-width: 100%;
+    max-height: clamp(160px, 33vh, 640px);
+    height: auto;
+  }
+}
+
+
 body[data-bs-theme=dark] {
   // Assuming icon is black/white line art, invert it and tone down brightness
   img.plugin-icon {

Некоторые файлы не были показаны из-за большого количества измененных файлов