怎样实现图片自适应优化脚本

wen 实用脚本 30

这是为您生成的图片自适应优化脚本,它利用现代浏览器的能力,根据设备屏幕尺寸和分辨率智能加载最合适的图片版本,从而提升页面加载速度和显示效果。

怎样实现图片自适应优化脚本

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">🖼️ 智能图片自适应优化</title>
    <style>
        * {
            box-sizing: border-box;
            margin: 0;
            padding: 0;
        }
        body {
            font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
            background: #f4f6f9;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            min-height: 100vh;
            padding: 2rem 1rem;
        }
        .container {
            max-width: 1100px;
            width: 100%;
            background: white;
            border-radius: 2rem;
            box-shadow: 0 20px 40px -12px rgba(0, 0, 0, 0.1);
            padding: 2rem 2rem 2.5rem;
            transition: all 0.2s;
        }
        h1 {
            font-weight: 600;
            font-size: 1.9rem;
            letter-spacing: -0.02em;
            color: #0b1a2e;
            display: flex;
            align-items: center;
            gap: 12px;
            margin-bottom: 0.25rem;
            flex-wrap: wrap;
        }
        h1 small {
            font-weight: 400;
            font-size: 1rem;
            color: #4a5b6e;
            background: #eef2f6;
            padding: 0.2rem 0.9rem;
            border-radius: 40px;
            letter-spacing: 0.3px;
        }
        .sub {
            color: #3f4e62;
            margin-bottom: 2rem;
            border-left: 4px solid #3b82f6;
            padding-left: 1rem;
            font-weight: 400;
            background: #f9fafc;
            border-radius: 0 12px 12px 0;
            line-height: 1.6;
        }
        /* 演示网格 */
        .demo-grid {
            display: grid;
            grid-template-columns: repeat(auto-fit, minmax(260px, 1fr));
            gap: 2rem 1.5rem;
            margin: 2rem 0 1rem;
        }
        .card {
            background: #ffffff;
            border-radius: 20px;
            overflow: hidden;
            box-shadow: 0 8px 20px rgba(0,0,0,0.02);
            transition: transform 0.15s ease, box-shadow 0.2s;
            border: 1px solid #eef2f6;
        }
        .card:hover {
            transform: translateY(-3px);
            box-shadow: 0 12px 28px rgba(0, 0, 0, 0.04);
        }
        .card-image {
            width: 100%;
            background: #e6edf4;
            position: relative;
            overflow: hidden;
        }
        .card-image img {
            display: block;
            width: 100%;
            height: auto;
            object-fit: cover;
            aspect-ratio: 16 / 10;  /* 统一比例,演示自适应 */
            background: #eef2f7;
            transition: opacity 0.35s ease, filter 0.2s;
        }
        /* 模糊占位效果 (可选) */
        img[data-src] {
            filter: blur(6px);
            opacity: 0.8;
            transform: scale(1.02);
        }
        img.loaded {
            filter: blur(0);
            opacity: 1;
            transform: scale(1);
        }
        .card-body {
            padding: 1rem 1.2rem 1.4rem;
        }
        .card-title {
            font-weight: 600;
            font-size: 1.2rem;
            margin-bottom: 6px;
            color: #11212e;
        }
        .card-desc {
            font-size: 0.9rem;
            color: #4f5e6e;
            line-height: 1.5;
            display: flex;
            justify-content: space-between;
            align-items: center;
        }
        .badge {
            background: #dbeafe;
            color: #1e4b8c;
            font-size: 0.7rem;
            padding: 0.25rem 0.9rem;
            border-radius: 20px;
            font-weight: 500;
            letter-spacing: 0.3px;
        }
        .badge.green {
            background: #d1fae5;
            color: #0b5e3c;
        }
        .info-panel {
            background: #f1f5f9;
            border-radius: 18px;
            padding: 1.25rem 1.8rem;
            margin-top: 2.2rem;
            display: flex;
            flex-wrap: wrap;
            align-items: center;
            justify-content: space-between;
            gap: 1rem 1.5rem;
            font-size: 0.95rem;
            border: 1px solid #e0e8f0;
        }
        .info-item {
            display: flex;
            align-items: baseline;
            gap: 6px;
            flex-wrap: wrap;
        }
        .info-item span:first-child {
            color: #455a6e;
            font-weight: 450;
        }
        .info-item .value {
            background: white;
            padding: 0.1rem 0.9rem;
            border-radius: 40px;
            font-weight: 500;
            color: #0b1f33;
            border: 1px solid #dae2ec;
        }
        #refreshInfo {
            background: #ffffff;
            border: 1px solid #ced8e4;
            padding: 0.5rem 1.4rem;
            border-radius: 40px;
            font-weight: 500;
            color: #1e2c41;
            cursor: pointer;
            transition: 0.15s;
            font-size: 0.9rem;
            display: inline-flex;
            align-items: center;
            gap: 6px;
        }
        #refreshInfo:hover {
            background: #e8edf5;
            border-color: #9aaec7;
        }
        .footnote {
            text-align: center;
            margin-top: 1.8rem;
            color: #64748b;
            font-size: 0.85rem;
            border-top: 1px solid #e9edf3;
            padding-top: 1.8rem;
        }
        @media (max-width: 600px) {
            .container { padding: 1.5rem; }
            h1 { font-size: 1.5rem; }
            .info-panel { flex-direction: column; align-items: stretch; }
        }
    </style>
</head>
<body>
<div class="container">
    <h1>
        📐 图片自适应优化
        <small>srcset + 尺寸感知</small>
    </h1>
    <div class="sub">
        ⚡ 基于屏幕宽度 &amp; 像素比 · 自动加载最适合的图片 · 支持延迟加载
    </div>
    <!-- 图片展示网格 -->
    <div class="demo-grid" id="imageGrid">
        <!-- 卡片 1 : 山川 -->
        <div class="card">
            <div class="card-image">
                <img class="optimized-img" 
                     data-src="https://picsum.photos/seed/landscape/600/375" 
                     src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='375' viewBox='0 0 600 375'%3E%3Crect fill='%23d9e2ec' width='600' height='375'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%2370849c' font-size='18' font-family='sans-serif'%3E加载中…%3C/text%3E%3C/svg%3E"
                     alt="自适应山川图片"
                     loading="lazy"
                     width="600" height="375">
            </div>
            <div class="card-body">
                <div class="card-title">🏔️ 山脉</div>
                <div class="card-desc">
                    <span>分辨率感知</span>
                    <span class="badge">16:10</span>
                </div>
            </div>
        </div>
        <!-- 卡片 2 : 城市 -->
        <div class="card">
            <div class="card-image">
                <img class="optimized-img" 
                     data-src="https://picsum.photos/seed/city/600/375" 
                     src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='375' viewBox='0 0 600 375'%3E%3Crect fill='%23cbd5e1' width='600' height='375'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%235f748c' font-size='18' font-family='sans-serif'%3E加载…%3C/text%3E%3C/svg%3E"
                     alt="自适应城市图片"
                     loading="lazy"
                     width="600" height="375">
            </div>
            <div class="card-body">
                <div class="card-title">🌆 城市夜景</div>
                <div class="card-desc">
                    <span>DPR 适配</span>
                    <span class="badge green">自动</span>
                </div>
            </div>
        </div>
        <!-- 卡片 3 : 海洋 -->
        <div class="card">
            <div class="card-image">
                <img class="optimized-img" 
                     data-src="https://picsum.photos/seed/ocean/600/375" 
                     src="data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='600' height='375' viewBox='0 0 600 375'%3E%3Crect fill='%23b9cadc' width='600' height='375'/%3E%3Ctext x='50%25' y='50%25' dominant-baseline='middle' text-anchor='middle' fill='%233f5a77' font-size='18' font-family='sans-serif'%3E加载…%3C/text%3E%3C/svg%3E"
                     alt="自适应海洋图片"
                     loading="lazy"
                     width="600" height="375">
            </div>
            <div class="card-body">
                <div class="card-title">🌊 海洋</div>
                <div class="card-desc">
                    <span>智能尺寸</span>
                    <span class="badge">webp 优先</span>
                </div>
            </div>
        </div>
    </div>
    <!-- 实时信息面板 -->
    <div class="info-panel" id="infoPanel">
        <div class="info-item">
            <span>📺 视口宽度</span>
            <span class="value" id="viewportWidth">—</span>
        </div>
        <div class="info-item">
            <span>🔍 像素比 (DPR)</span>
            <span class="value" id="devicePixelRatio">—</span>
        </div>
        <div class="info-item">
            <span>🖼️ 加载尺寸</span>
            <span class="value" id="loadedResolution">—</span>
        </div>
        <button id="refreshInfo" aria-label="刷新信息">⟳ 重新检测</button>
    </div>
    <div class="footnote">
        ⚡ 图片使用 <strong>srcset</strong> + <strong>sizes</strong> 自适应,根据设备宽度与 DPR 加载最适合版本。<br>
        支持延迟加载 (loading="lazy") 及模糊占位过渡。
    </div>
</div>
<script>
    (function() {
        "use strict";
        // ----- 核心:自适应图片优化脚本 -----
        // 原理:为每个图片生成 srcset (多分辨率) + sizes,并根据屏幕宽度/DPR 加载最佳图片
        // 同时支持渐进增强、占位模糊效果
        // 工具:获取设备像素比 (至少 1)
        const dpr = window.devicePixelRatio || 1;
        // 更新信息面板
        function updateInfo() {
            const vw = window.innerWidth;
            document.getElementById('viewportWidth').textContent = vw + 'px';
            document.getElementById('devicePixelRatio').textContent = dpr.toFixed(1) + 'x';
            // 显示已加载图片的实际尺寸 (取自最后一张加载完成的图片或取样)
            const imgs = document.querySelectorAll('.optimized-img');
            let loadedCount = 0;
            let lastNatural = { width: '—', height: '—' };
            imgs.forEach(img => {
                if (img.complete && img.naturalWidth > 0) {
                    loadedCount++;
                    lastNatural = { width: img.naturalWidth, height: img.naturalHeight };
                }
            });
            if (loadedCount > 0) {
                document.getElementById('loadedResolution').textContent = 
                    `${lastNatural.width} × ${lastNatural.height}`;
            } else {
                document.getElementById('loadedResolution').textContent = '等待加载…';
            }
        }
        // ----- 生成响应式 srcset 和 sizes (核心优化) -----
        function setupResponsiveImage(img) {
            // 获取原始图片URL基础 (移除尺寸部分,以便生成多尺寸)
            let src = img.getAttribute('data-src') || img.src;
            // 如果是 picsum 类型的链接,我们提取种子,构建多尺寸
            // 支持 picsum.photos 以及常规带尺寸的URL
            let baseUrl = src;
            // 针对 picsum 特殊处理: 从URL中解析种子和尺寸
            //  https://picsum.photos/seed/landscape/600/375
            // 转换成多尺寸: 使用 ?random 或者构建 400, 800, 1200 版本
            const picsumMatch = src.match(/^(https:\/\/picsum\.photos\/seed\/([^/]+))\/(\d+)\/(\d+)/i);
            if (picsumMatch) {
                const base = picsumMatch[1];  // https://picsum.photos/seed/landscape
                const defaultW = picsumMatch[3];
                const defaultH = picsumMatch[4];
                // 生成多组分辨率 (宽度): 320, 640, 960, 1280, 1920,按比例保持高度
                const widths = [320, 640, 960, 1280, 1920];
                const ratio = defaultH / defaultW;
                const srcsetParts = widths.map(w => {
                    const h = Math.round(w * ratio);
                    return `${base}/${w}/${h} ${w}w`;
                }).join(', ');
                img.setAttribute('srcset', srcsetParts);
                // 设置 sizes 属性: 根据视口宽度决定加载哪张 (响应式)
                //  (max-width: 480px) 100vw, (max-width: 768px) 80vw, 50vw
                // 这里简洁处理:如果屏幕<600用100vw,否则用50vw (实际根据布局)
                img.setAttribute('sizes', '(max-width: 600px) 100vw, (max-width: 1024px) 50vw, 33vw');
                // 默认 src 使用中等尺寸 (640×比例) 使初始加载不至于太大
                const mediumW = 640;
                const mediumH = Math.round(mediumW * ratio);
                img.src = `${base}/${mediumW}/${mediumH}`;
                // 同时更新 data-src 为原始大尺寸? 但我们已经用了srcset
                // 保留原始大尺寸作为后备: 使用 1280w
                img.setAttribute('data-fallback', `${base}/${1280}/${Math.round(1280 * ratio)}`);
            } else {
                // 对于其他图片 (非picsum),尝试从URL提取宽高并生成srcset
                //  普通图片也可能是静态路径,我们默认添加2x 3x 基于尺寸
                // 但此处为通用演示,如果图片不是picsum,则简单增加DPR srcset 
                // 大多数场景可使用 1x, 2x (基于原始尺寸)
                const originalSrc = img.getAttribute('data-src') || img.src;
                // 移除查询参数避免缓存
                const cleanSrc = originalSrc.split('?')[0];
                // 判断是否已有尺寸后缀,简单策略: 生成 @2x @3x  (通过查询参数模拟)
                // (真实的2x图片需服务器支持, 这里使用 ?dpr=2 模拟)
                if (!cleanSrc.includes('picsum')) {
                    // 构造基于DPR的srcset (1x, 2x, 3x)
                    const src1x = cleanSrc;
                    const src2x = cleanSrc + (cleanSrc.includes('?') ? '&' : '?') + 'dpr=2';
                    const src3x = cleanSrc + (cleanSrc.includes('?') ? '&' : '?') + 'dpr=3';
                    img.setAttribute('srcset', `${src1x} 1x, ${src2x} 2x, ${src3x} 3x`);
                    // sizes 可以省略,使用x描述符时浏览器自动选择
                } else {
                    // fallback 至少保留原始
                    img.setAttribute('srcset', `${cleanSrc} 1x`);
                }
                // 保留原始src
                img.src = cleanSrc;
            }
            // 让浏览器应用srcset (部分浏览器需要重新触发)
            // 强制重新解析srcset
            if (img.srcset) {
                img.offsetHeight; // 强制重绘
            }
        }
        // ----- 模糊占位 → 加载完成后清除 -----
        function handleImageLoad(img) {
            img.classList.add('loaded');
            // 移除模糊效果
            img.style.filter = 'none';
            // 更新信息面板 (取样)
            updateInfo();
        }
        function handleImageError(img) {
            // 如果加载失败,使用fallback
            const fallback = img.getAttribute('data-fallback');
            if (fallback && img.src !== fallback) {
                img.src = fallback;
            }
            img.classList.add('loaded'); // 仍然移除模糊
            img.style.filter = 'none';
        }
        // ----- 初始化所有图片 -----
        function initImages() {
            const images = document.querySelectorAll('.optimized-img');
            images.forEach((img, index) => {
                // 1. 应用响应式策略 (srcset, sizes)
                setupResponsiveImage(img);
                // 2. 绑定加载完成事件 (移除模糊)
                // 如果已经加载完成则直接处理
                if (img.complete && img.naturalWidth > 0) {
                    handleImageLoad(img);
                } else {
                    // 移除之前绑定的监听避免重复
                    img.removeEventListener('load', onLoad);
                    img.removeEventListener('error', onError);
                    // 定义处理函数
                    const onLoad = () => handleImageLoad(img);
                    const onError = () => handleImageError(img);
                    img.addEventListener('load', onLoad, { once: true });
                    img.addEventListener('error', onError, { once: true });
                }
                // 3. 支持延迟加载: 如果浏览器不支持 loading=lazy,我们自己实现简单懒加载? 
                // 但现代浏览器都支持,使用原生 lazy,并加上 IntersectionObserver 作为降级 (可选)
                if ('loading' in HTMLImageElement.prototype) {
                    img.setAttribute('loading', 'lazy');
                } else {
                    // 极简懒加载 polyfill: 使用 IntersectionObserver
                    // 这里为了轻量,只做演示,不再额外实现
                    // 但为了效果,我们手动设置loading = lazy 已经支持大部分
                }
            });
            // 首次信息更新
            setTimeout(updateInfo, 200);
        }
        // ----- 窗口变化重新检测 (视口宽度) -----
        let resizeTimer;
        function handleResize() {
            clearTimeout(resizeTimer);
            resizeTimer = setTimeout(() => {
                updateInfo();
            }, 150);
        }
        // 刷新按钮
        document.getElementById('refreshInfo').addEventListener('click', function(e) {
            e.preventDefault();
            updateInfo();
            // 同时重新检查所有图片状态
            const imgs = document.querySelectorAll('.optimized-img');
            imgs.forEach(img => {
                if (img.complete && img.naturalWidth > 0) {
                    handleImageLoad(img);
                }
            });
        });
        // ----- 启动 -----
        document.addEventListener('DOMContentLoaded', function() {
            initImages();
            updateInfo();
            window.addEventListener('resize', handleResize);
            // 针对移动端旋转
            window.addEventListener('orientationchange', function() {
                setTimeout(updateInfo, 300);
            });
        });
        // 如果DOM已加载完成 (脚本在底部,但以防万一)
        if (document.readyState !== 'loading') {
            initImages();
            updateInfo();
            window.addEventListener('resize', handleResize);
        }
        // 暴露一些信息用于调试
        window.__imgOptimizer = { updateInfo, initImages };
    })();
</script>
</body>
</html>

图片自适应机制说明

脚本通过组合多种现代Web技术,实现了图片的智能加载与显示,您可以把它理解为一位“图片管家”,它会在您浏览时,根据设备的实际情况,为您挑选最合适的图片。

  • 分辨率与尺寸适配:脚本会读取您的屏幕宽度和像素比(DPR),并使用 srcsetsizes 属性为图片生成多个版本(如320w、640w、1280w),浏览器会自动选择最匹配当前屏幕尺寸和分辨率的图片进行加载,避免加载过大或过小的图片。
  • 加载性能优化:集成了延迟加载(loading=“lazy”)特性,图片仅在即将进入可视区域时才开始加载,有效减少了初始页面的数据消耗,配合模糊占位图,在图片加载完成前会显示一个柔和过渡,避免了页面布局抖动,提升了用户体验。
  • 状态监控与反馈:页面底部的信息面板会实时显示当前的视口宽度、设备像素比以及已加载图片的实际分辨率,您可以通过“重新检测”按钮手动刷新这些信息,直观地看到不同设备或窗口大小变化下的自适应效果。

抱歉,评论功能暂时关闭!