nextjs-image-art-direction

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Next.js Image: Art Direction

Next.js 图片:艺术指导

Art direction means showing completely different images based on viewport size — not just resizing the same image. Common use cases include homepage carousels with different assets for mobile vs desktop, switching from landscape (desktop) to portrait (mobile), or showing cropped vs full compositions.
艺术指导指的是根据视口尺寸展示完全不同的图片——而不仅仅是调整同一张图片的大小。常见使用场景包括首页轮播图在移动端和桌面端使用不同资源、从横版(桌面端)切换为竖版(移动端),或者展示裁剪版与完整构图的图片。

Art Direction vs Responsive Images

艺术指导 vs 响应式图片

ApproachPurposeImplementation
Art DirectionDifferent image content/composition
<picture>
with multiple
<source>
elements
Responsive ImagesSame image, different sizes
sizes
prop with
srcset
Use Art Direction When:
  • Homepage carousels with different images for mobile and desktop (e.g., square images on mobile, wide banner on desktop)
  • Mobile shows portrait crop, desktop shows landscape
  • Different focal points for different screen sizes
  • Completely different compositions are needed
  • Content hierarchy changes between breakpoints
  • Different image assets optimized for each viewport (e.g., mobile-optimized JPEGs vs desktop quality)
Use Responsive Images When:
  • Same image works at all sizes
  • Only the dimensions change
  • Standard responsive behavior is sufficient
方案用途实现方式
艺术指导不同图片内容/构图带有多个
<source>
元素的
<picture>
标签
响应式图片同一张图片,不同尺寸搭配
srcset
sizes
属性
适合使用艺术指导的场景:
  • 首页轮播图在移动端和桌面端使用不同图片(例如移动端用方形图,桌面端用宽幅横幅)
  • 移动端展示竖版裁剪图,桌面端展示横版图
  • 不同屏幕尺寸使用不同焦点
  • 需要完全不同的构图
  • 内容层级在断点间发生变化
  • 为每个视口优化不同的图片资源(例如移动端优化的JPEG vs 桌面端质量的图片)
适合使用响应式图片的场景:
  • 同一张图片适配所有尺寸
  • 仅尺寸发生变化
  • 标准响应式行为足够满足需求

Implementation with
getImageProps()

使用
getImageProps()
实现

The
getImageProps()
function (stable since Next.js 14.1.0) generates the necessary props without calling React
useState()
, making it ideal for art direction.
getImageProps()
函数(自Next.js 14.1.0起稳定)可生成所需属性,无需调用React
useState()
,非常适合用于艺术指导场景。

Step-by-Step Implementation

分步实现

jsx
import { getImageProps } from 'next/image'

export default function ArtDirectedImage() {
  // Common props shared across all image versions
  const common = { 
    alt: 'Mountain landscape', 
    sizes: '100vw' 
  }
  
  // Desktop version (landscape, higher quality)
  const {
    props: { srcSet: desktop },
  } = getImageProps({
    ...common,
    src: '/hero-desktop.jpg',
    width: 1440,
    height: 875,
    quality: 80,
  })
  
  // Mobile version (portrait, smaller dimensions)
  const {
    props: { srcSet: mobile, ...rest },
  } = getImageProps({
    ...common,
    src: '/hero-mobile.jpg',
    width: 750,
    height: 1334,
    quality: 70,
  })
  
  return (
    <picture>
      {/* Desktop: min-width 1000px */}
      <source media="(min-width: 1000px)" srcSet={desktop} />
      
      {/* Mobile: min-width 500px */}
      <source media="(min-width: 500px)" srcSet={mobile} />
      
      {/* Fallback img element (rendered if no media query matches) */}
      <img {...rest} style={{ width: '100%', height: 'auto' }} />
    </picture>
  )
}
jsx
import { getImageProps } from 'next/image'

export default function ArtDirectedImage() {
  // 所有图片版本共享的通用属性
  const common = { 
    alt: 'Mountain landscape', 
    sizes: '100vw' 
  }
  
  // 桌面端版本(横版,更高质量)
  const {
    props: { srcSet: desktop },
  } = getImageProps({
    ...common,
    src: '/hero-desktop.jpg',
    width: 1440,
    height: 875,
    quality: 80,
  })
  
  // 移动端版本(竖版,更小尺寸)
  const {
    props: { srcSet: mobile, ...rest },
  } = getImageProps({
    ...common,
    src: '/hero-mobile.jpg',
    width: 750,
    height: 1334,
    quality: 70,
  })
  
  return (
    <picture>
      {/* 桌面端:最小宽度1000px */}
      <source media="(min-width: 1000px)" srcSet={desktop} />
      
      {/* 移动端:最小宽度500px */}
      <source media="(min-width: 500px)" srcSet={mobile} />
      
      {/* 回退img元素(无媒体查询匹配时渲染) */}
      <img {...rest} style={{ width: '100%', height: 'auto' }} />
    </picture>
  )
}

Key Implementation Details

关键实现细节

Props to Vary by Breakpoint:
  • src
    : Different image file
  • width
    /
    height
    : Different dimensions
  • quality
    : Different compression levels
Common Props (Shared):
  • alt
    : Accessibility text (must work for all versions)
  • sizes
    : Responsive size hints for browser
HTML Structure:
  • <picture>
    wrapper element
  • <source>
    elements with
    media
    attribute for each breakpoint
  • <img>
    element last as fallback (required)
按断点调整的属性:
  • src
    : 不同的图片文件
  • width
    /
    height
    : 不同尺寸
  • quality
    : 不同压缩级别
通用属性(共享):
  • alt
    : 无障碍文本(必须适配所有版本)
  • sizes
    : 给浏览器的响应式尺寸提示
HTML结构:
  • <picture>
    包裹元素
  • 每个断点对应带有
    media
    属性的
    <source>
    元素
  • 最后放置
    <img>
    元素作为回退(必填)

Breakpoint Strategy

断点策略

Order matters! The browser uses the first matching
<source>
. List from largest to smallest (desktop-first) or smallest to largest (mobile-first).
顺序很重要!浏览器会使用第一个匹配的
<source>
。可以按从大到小(桌面优先)或从小到大(移动优先)的顺序排列。

Desktop-First (Largest to Smallest)

桌面优先(从大到小)

jsx
<picture>
  <source media="(min-width: 1000px)" srcSet={desktop} />
  <source media="(min-width: 500px)" srcSet={tablet} />
  <img {...rest} style={{ width: '100%', height: 'auto' }} />
</picture>
jsx
<picture>
  <source media="(min-width: 1000px)" srcSet={desktop} />
  <source media="(min-width: 500px)" srcSet={tablet} />
  <img {...rest} style={{ width: '100%', height: 'auto' }} />
</picture>

Mobile-First (Smallest to Largest)

移动优先(从小到大)

jsx
<picture>
  <source media="(max-width: 499px)" srcSet={mobile} />
  <source media="(max-width: 999px)" srcSet={tablet} />
  <img {...rest} style={{ width: '100%', height: 'auto' }} />
</picture>
jsx
<picture>
  <source media="(max-width: 499px)" srcSet={mobile} />
  <source media="(max-width: 999px)" srcSet={tablet} />
  <img {...rest} style={{ width: '100%', height: 'auto' }} />
</picture>

Common Pitfalls

常见陷阱

⚠️ Cannot Use
preload
or
loading="eager"

⚠️ 不可使用
preload
loading="eager"

These would cause all images to load immediately, defeating the purpose of art direction:
jsx
// BAD: Would load both desktop and mobile
getImageProps({
  src: '/desktop.jpg',
  preload: true, // Don't do this!
})

// BAD: Same problem
getImageProps({
  src: '/desktop.jpg',
  loading: 'eager', // Don't do this!
})
Solution: Use
fetchPriority="high"
if you need to prioritize the LCP image:
jsx
const common = { 
  alt: 'Hero image',
  fetchPriority: 'high', // Only load the matching image eagerly
}
这会导致所有图片立即加载,违背艺术指导的初衷:
jsx
// 错误:会同时加载桌面端和移动端图片
getImageProps({
  src: '/desktop.jpg',
  preload: true, // 请勿这样做!
})

// 错误:同样的问题
getImageProps({
  src: '/desktop.jpg',
  loading: 'eager', // 请勿这样做!
})
解决方案: 如果需要优先加载LCP(最大内容绘制)图片,请使用
fetchPriority="high"
jsx
const common = { 
  alt: 'Hero image',
  fetchPriority: 'high', // 仅会优先加载匹配的图片
}

⚠️ Alt Text Must Work for All Versions

⚠️ 替代文本必须适配所有版本

The
alt
text is shared across all image versions. Make sure it accurately describes all possible images:
jsx
// BAD: Only describes desktop version
const common = { alt: 'Wide panoramic mountain landscape' }

// GOOD: Describes both versions
const common = { alt: 'Mountain landscape with snow-capped peaks' }
alt
文本在所有图片版本中共享,请确保它能准确描述所有可能的图片:
jsx
// 错误:仅描述了桌面端版本
const common = { alt: 'Wide panoramic mountain landscape' }

// 正确:描述了所有版本
const common = { alt: 'Mountain landscape with snow-capped peaks' }

⚠️ Cannot Use
placeholder
Prop

⚠️ 不可使用
placeholder
属性

getImageProps()
doesn't support the
placeholder
prop because the placeholder would never be removed. Handle loading states manually if needed.
getImageProps()
不支持
placeholder
属性,因为占位符永远不会被移除。如果需要加载状态,请手动处理。

⚠️ Ensure Images Exist for All Breakpoints

⚠️ 确保所有断点的图片都存在

Missing images will cause broken image icons on certain devices. Always test on actual devices or browser dev tools with different viewport sizes.
缺失的图片会导致某些设备上显示损坏的图片图标。请务必在实际设备或浏览器开发者工具的不同视口尺寸下进行测试。

Complete Example: Hero Section

完整示例:英雄区域

jsx
import { getImageProps } from 'next/image'

export default function Hero() {
  const common = { 
    alt: 'Team collaboration in modern office',
    sizes: '100vw',
    fetchPriority: 'high',
  }
  
  // Large desktop: Full office scene
  const { props: { srcSet: desktop } } = getImageProps({
    ...common,
    src: '/hero-office-wide.jpg',
    width: 1920,
    height: 1080,
    quality: 85,
  })
  
  // Tablet: Focused team shot
  const { props: { srcSet: tablet } } = getImageProps({
    ...common,
    src: '/hero-team-focused.jpg',
    width: 1024,
    height: 768,
    quality: 80,
  })
  
  // Mobile: Single person portrait
  const { props: { srcSet: mobile, ...rest } } = getImageProps({
    ...common,
    src: '/hero-person-portrait.jpg',
    width: 750,
    height: 1334,
    quality: 75,
  })
  
  return (
    <section className="relative">
      <picture>
        <source media="(min-width: 1200px)" srcSet={desktop} />
        <source media="(min-width: 768px)" srcSet={tablet} />
        <source media="(min-width: 500px)" srcSet={mobile} />
        <img 
          {...rest} 
          className="w-full h-auto object-cover"
          style={{ maxHeight: '80vh' }}
        />
      </picture>
      <div className="absolute inset-0 flex items-center justify-center">
        <h1 className="text-white text-4xl font-bold drop-shadow-lg">
          Welcome to Our Platform
        </h1>
      </div>
    </section>
  )
}
jsx
import { getImageProps } from 'next/image'

export default function Hero() {
  const common = { 
    alt: 'Team collaboration in modern office',
    sizes: '100vw',
    fetchPriority: 'high',
  }
  
  // 大桌面端:完整办公室场景
  const { props: { srcSet: desktop } } = getImageProps({
    ...common,
    src: '/hero-office-wide.jpg',
    width: 1920,
    height: 1080,
    quality: 85,
  })
  
  // 平板端:聚焦团队的照片
  const { props: { srcSet: tablet } } = getImageProps({
    ...common,
    src: '/hero-team-focused.jpg',
    width: 1024,
    height: 768,
    quality: 80,
  })
  
  // 移动端:单人肖像
  const { props: { srcSet: mobile, ...rest } } = getImageProps({
    ...common,
    src: '/hero-person-portrait.jpg',
    width: 750,
    height: 1334,
    quality: 75,
  })
  
  return (
    <section className="relative">
      <picture>
        <source media="(min-width: 1200px)" srcSet={desktop} />
        <source media="(min-width: 768px)" srcSet={tablet} />
        <source media="(min-width: 500px)" srcSet={mobile} />
        <img 
          {...rest} 
          className="w-full h-auto object-cover"
          style={{ maxHeight: '80vh' }}
        />
      </picture>
      <div className="absolute inset-0 flex items-center justify-center">
        <h1 className="text-white text-4xl font-bold drop-shadow-lg">
          Welcome to Our Platform
        </h1>
      </div>
    </section>
  )
}

Advanced: CSS Background Images

进阶:CSS背景图片

You can use
getImageProps()
to optimize background images with
image-set()
:
jsx
import { getImageProps } from 'next/image'

function getBackgroundImage(srcSet = '') {
  const imageSet = srcSet
    .split(', ')
    .map((str) => {
      const [url, dpi] = str.split(' ')
      return `url("${url}") ${dpi}`
    })
    .join(', ')
  return `image-set(${imageSet})`
}

export default function HeroBackground() {
  const {
    props: { srcSet },
  } = getImageProps({
    alt: '',
    width: 1920,
    height: 1080,
    src: '/hero-bg.jpg',
    quality: 80,
  })
  
  const backgroundImage = getBackgroundImage(srcSet)
  
  return (
    <main 
      style={{ 
        height: '100vh', 
        width: '100vw',
        backgroundImage,
        backgroundSize: 'cover',
        backgroundPosition: 'center',
      }}
    >
      <h1>Content Here</h1>
    </main>
  )
}
你可以使用
getImageProps()
结合
image-set()
优化背景图片:
jsx
import { getImageProps } from 'next/image'

function getBackgroundImage(srcSet = '') {
  const imageSet = srcSet
    .split(', ')
    .map((str) => {
      const [url, dpi] = str.split(' ')
      return `url("${url}") ${dpi}`
    })
    .join(', ')
  return `image-set(${imageSet})`
}

export default function HeroBackground() {
  const {
    props: { srcSet },
  } = getImageProps({
    alt: '',
    width: 1920,
    height: 1080,
    src: '/hero-bg.jpg',
    quality: 80,
  })
  
  const backgroundImage = getBackgroundImage(srcSet)
  
  return (
    <main 
      style={{ 
        height: '100vh', 
        width: '100vw',
        backgroundImage,
        backgroundSize: 'cover',
        backgroundPosition: 'center',
      }}
    >
      <h1>Content Here</h1>
    </main>
  )
}

Quick Reference

快速参考

DO

建议做法

  • Use
    getImageProps()
    for multiple image versions
  • Share
    alt
    and
    sizes
    across all versions
  • Order
    <source>
    elements correctly (first match wins)
  • Use
    fetchPriority="high"
    for LCP images (not
    preload
    )
  • Test on actual devices or responsive mode in dev tools
  • Ensure all image files exist for defined breakpoints
  • 使用
    getImageProps()
    处理多版本图片
  • 在所有版本间共享
    alt
    sizes
    属性
  • 正确排序
    <source>
    元素(第一个匹配的会被使用)
  • 为LCP图片使用
    fetchPriority="high"
    (而非
    preload
  • 在实际设备或开发者工具的响应式模式下测试
  • 确保所有定义的断点对应的图片文件都存在

DON'T

禁止做法

  • Use
    preload
    prop (loads all images)
  • Use
    loading="eager"
    (loads all images)
  • Use
    placeholder
    prop with
    getImageProps()
  • Write alt text that only describes one version
  • Forget to include the final
    <img>
    element
  • Use art direction when simple responsive images suffice
  • 使用
    preload
    属性(会加载所有图片)
  • 使用
    loading="eager"
    (会加载所有图片)
  • getImageProps()
    中使用
    placeholder
    属性
  • 编写仅描述一个版本的替代文本
  • 忘记添加最终的
    <img>
    元素
  • 在简单响应式图片足够的情况下使用艺术指导

References

参考资料