效果

2025年11月17日 11点50分

实现步骤

实现内容

  1. 等待DOM完全加载
  2. 获取所有的IMG标签
  3. 展示所有的IMG描述内容

我美化了什么

  1. 鼠标移上显示,避免遮挡图片内容,同样避免破坏版面
  2. 给文字背景毛玻璃效果
  3. 给毛玻璃动态背景颜色,颜色提取图片主色作为背景颜色
  4. 给字体一点描边,增加极端背景色下的可读性

实现代码

document.addEventListener('DOMContentLoaded', function () {
    const mainContent = document.querySelector('.post-content, .article-content');
    if (!mainContent) {
        return;
    }

    mainContent.querySelectorAll('img').forEach(img => {
        const title = img.getAttribute('title');
        const alt = img.getAttribute('alt');

        if (!title && !alt) {
            return; 
        }

        img.crossOrigin = 'anonymous';

        const customElement = document.createElement('div');
        customElement.setAttribute('class', 'article-image-wrapper');
        customElement.style.display = 'none'; 

        const figcaption = document.createElement('figcaption');
        figcaption.setAttribute('class', 'image-info-overlay');

        let infoHTML = '';
        if (alt) {
            infoHTML += `<p class="image-alt-text">${alt}</p>`;
        }
        if (title) {
            infoHTML += `<p class="image-title-text">${title}</p>`;
        }
        figcaption.innerHTML = infoHTML;

        img.parentNode.insertBefore(customElement, img);
        customElement.appendChild(img);
        customElement.appendChild(figcaption);
        
        customElement.addEventListener('mouseover', () => {
            customElement.classList.add('is-hovering');
        });
        customElement.addEventListener('mouseout', () => {
            customElement.classList.remove('is-hovering');
        });

        img.addEventListener('load', function () {
            customElement.style.display = 'inline-block'; 
            
            if (isSameOrigin(img.src)) {
                const canvas = document.createElement('canvas');
                const rgbColor = getImageAverageColor(canvas, img);
                figcaption.style.backgroundColor = rgbColor;
            } else {
                figcaption.style.backgroundColor = 'rgba(0, 0, 0, 0.7)';
            }
        });

        img.addEventListener('error', function () {
            customElement.remove(); 
        });
    });

    function isSameOrigin(url) {
        try {
            const imgUrl = new URL(url);
            return imgUrl.origin === window.location.origin;
        } catch (e) {
            return true;
        }
    }
    
    function getImageAverageColor(canvas, img) {
        try {
            canvas.width = img.naturalWidth;
            canvas.height = img.naturalHeight;

            const context = canvas.getContext("2d");
            context.drawImage(img, 0, 0, canvas.width, canvas.height);

            const data = context.getImageData(0, 0, canvas.width, canvas.height).data;
            let r = 0, g = 0, b = 0;

            const pixelCount = data.length / 4;
            for (let i = 0; i < data.length; i += 4) {
                r += data[i];
                g += data[i + 1]; 
                b += data[i + 2]; 
            }
            
            r = Math.round(r / pixelCount);
            g = Math.round(g / pixelCount);
            b = Math.round(b / pixelCount);

            return `rgba(${r}, ${g}, ${b}, 0.3)`;
        } catch (e) {
            return 'rgba(0, 0, 0, 0.7)';
        }
    }
});

# css部分



.article-image-wrapper {
	position: relative;
	display: inline-block;
	overflow: hidden
}

.article-image-wrapper img {
	display: block;
	max-width: 100%;
	height: auto
}

.image-info-overlay {
	position: absolute;
	bottom: 0;
	left: 0;
    right: 0;
	padding-left: 10px;
	padding-bottom: 2px;	
	padding-top: 2px;
	border-radius: 0px 0px 4px 4px;
	color: white;
	backdrop-filter: blur(5px);
	box-sizing: border-box;
	transform: translateY(100%);
	transition: opacity .3s ease-out, transform .3s ease-out
}

.article-image-wrapper.is-hovering .image-info-overlay {
	opacity: 1;
	transform: translateY(0)
}

.image-info-overlay p {
	margin: 5px 0;
	line-height: 1;
	text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
	color: #ffffff;
}
.image-alt-text{
	font-size: 0.8rem;
}
.image-title-text{
	font-size: 1.1rem;
}

下面是比较详细的解释

代码在 DOM 内容完全加载后执行,它遍历文章中的所有 <img> 标签,并对那些设置了 titlealt 属性的图片执行以下操作:

  • 图片包装与结构化:
    • 将图片用一个新的 <div> 元素(类名为 article-image-wrapper)包裹起来,并将其初始设置为隐藏 (display: 'none')。
    • 在包裹元素内,为图片添加一个 figcaption 元素(类名为 image-info-overlay),用于显示图片的 alttitle 信息。
  • 信息显示(Alt/Title):
    • <img> 标签中获取 alttitle 属性值,并将它们格式化为 <p> 标签,放入 figcaption 元素中。
  • 交互效果:
    • 为图片包裹元素添加鼠标悬停监听器,在悬停时添加/移除 is-hovering 类,以便通过 CSS 实现悬停时的视觉效果。
  • 图片加载成功后的处理:
    • 当图片加载成功后,将包裹元素显示出来 (display: 'inline-block')。
    • 尝试获取图片的平均颜色:
      • 如果是同源图片,则使用 <canvas> 元素计算图片的平均 RGB 颜色,并将此颜色设置为 figcaption 的背景色(带有 $0.3$ 的透明度)。
      • 如果是跨域或计算失败(包括同源计算失败),则设置默认的深色背景(rgba(0, 0, 0, 0.7))。
  • 跨域设置与错误处理:
    • 将图片元素的 crossOrigin 属性设置为 'anonymous',以支持跨域图片在 <canvas> 上进行颜色计算。
    • 如果图片加载失败,则移除整个图片包裹元素。

fancybox

另外点击图片是有灯箱效果的,我使用的是fancybox3,首先你需要引入fancybox的css和js文件,然后在你的JS中添加如下代码:

'use strict';

const cheerio = require('cheerio');

hexo.extend.filter.register('after_post_render', function(data) {
    if (!data.content) return data;

    const $ = cheerio.load(data.content, {
        decodeEntities: false 
    });

    $('img:not(a img)').each(function() {
        const $img = $(this);
        const src = $img.attr('src');
        const alt = $img.attr('alt');
        
        if (!src) return;

        const $a = $('<a>')
            .attr('href', src) 
            .attr('data-fancybox', 'gallery') 
            .attr('data-caption', alt || ''); 

        $img.wrap($a);
    });

    data.content = $.html();
    return data;
});

因为博客的图片我基本都从原图(jpg)格式转换为了avif格式。所以如果你希望点击图片展示的灯箱为原图(大尺寸原图),你可以使用以下的代码,把原图路径替换以下

$('img:not(a img)').each(function() {
    const $img = $(this);
    const originalSrc = $img.attr('src'); 
    const newSrc = originalSrc.replace(/\.avif$/, '.jpg'); 
    console.log(newSrc);
    const alt = $img.attr('alt');
    
    if (!newSrc) return;

    const $a = $('<a>')
        .attr('href', newSrc) 
        .attr('data-fancybox', 'gallery') 
        .attr('data-caption', alt || ''); 

    $img.wrap($a);
});