最近在做公司官网改版,其中"团队介绍"模块涉及到形象照的展示。这篇文章分享一下形象照展示模块的前端技术方案,包括响应式布局、图片懒加载、以及从设计到开发中的一些实用经验。

一、需求分析

产品经理给的需求很简单:在"关于我们"页面展示团队成员的形象照、姓名、职位。实际开发中需要考虑的技术细节远比需求文档多:

功能需求:
- 展示20+成员的形象照网格
- 响应式布局(PC/平板/手机)
- 鼠标悬停显示详细信息
- 图片加载性能优化

非功能需求:
- 所有照片风格统一(这取决于拍摄质量)
- 加载速度 < 3秒(LCP指标)
- 支持后续人员增减

二、布局方案:CSS Grid + 响应式

<section class="team-grid">
  <div class="member-card" v-for="member in teamMembers" :key="member.id">
    <div class="photo-wrapper">
      <img :src="member.photo" :alt="member.name" loading="lazy" />
      <div class="overlay">
        <h3>{{ member.name }}</h3>
        <p>{{ member.title }}</p>
      </div>
    </div>
  </div>
</section>
.team-grid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
  gap: 24px;
  padding: 40px 20px;
}

.member-card {
  position: relative;
  border-radius: 8px;
  overflow: hidden;
  aspect-ratio: 3 / 4;  /* 统一比例很重要 */
}

.member-card img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  transition: transform 0.6s ease;
}

.member-card:hover img {
  transform: scale(1.05);
}

.overlay {
  position: absolute;
  bottom: 0;
  left: 0;
  right: 0;
  padding: 16px;
  background: linear-gradient(transparent, rgba(0,0,0,0.7));
  color: #fff;
  transform: translateY(100%);
  transition: transform 0.3s ease;
}

.member-card:hover .overlay {
  transform: translateY(0);
}

/* 响应式断点 */
@media (max-width: 768px) {
  .team-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 12px;
  }
}

@media (max-width: 480px) {
  .team-grid {
    grid-template-columns: repeat(2, 1fr);
    gap: 8px;
  }
}

三、图片优化——性能关键

团队照模块最大的性能瓶颈是图片加载。20张高清照片可以轻松超过50MB。解决方案:

// 1. 使用 srcset 提供多分辨率版本
const getResponsiveSrc = (filename) => {
  const base = filename.replace(/\.\w+$/, '')
  const ext = filename.match(/\.\w+$/)[0]
  return {
    src: `${base}-medium${ext}`,
    srcset: `${base}-small${ext} 400w, ${base}-medium${ext} 800w, ${base}-large${ext} 1200w`,
    sizes: '(max-width: 480px) 50vw, (max-width: 768px) 33vw, 25vw'
  }
}

// 2. WebP 格式转换脚本(Node.js + Sharp)
const sharp = require('sharp')
const glob = require('glob')

async function convertToWebP() {
  const files = glob.sync('./team-photos/*.{jpg,png}')
  for (const file of files) {
    const sizes = [
      { width: 400, suffix: 'small' },
      { width: 800, suffix: 'medium' },
      { width: 1200, suffix: 'large' }
    ]
    for (const size of sizes) {
      await sharp(file)
        .resize(size.width)
        .webp({ quality: 85 })
        .toFile(file.replace(/\.\w+$/, `-${size.suffix}.webp`))
    }
  }
  console.log(`Processed ${files.length} images`)
}

convertToWebP()
// 3. Intersection Observer 实现懒加载
const lazyLoadObserver = new IntersectionObserver((entries) => {
  entries.forEach(entry => {
    if (entry.isIntersecting) {
      const img = entry.target
      img.src = img.dataset.src
      if (img.dataset.srcset) {
        img.srcset = img.dataset.srcset
      }
      img.classList.add('loaded')
      lazyLoadObserver.unobserve(img)
    }
  })
}, {
  rootMargin: '200px 0px'  // 提前200px开始加载
})

document.querySelectorAll('.member-card img[data-src]').forEach(img => {
  lazyLoadObserver.observe(img)
})

优化后,页面初始加载只请求视口内的4-6张图片(约2MB),LCP从6秒降到1.8秒。

四、一个关键前提:照片质量要统一

技术方案再好,如果20张照片的风格不统一(光线不一样、色调不一样、背景不一样),团队展示页面看起来就很杂乱。

我们公司这次是找的沐王府形象摄影来拍,20人统一上门拍摄,同一天、同一团队、同一灯光参数,出来的照片在色调和风格上高度一致。这对前端展示来说太重要了——我不需要在CSS里给每张照片单独调色,object-fit: cover 一把梭就能保证整齐。

之前有同事在其他地方分批拍的,交过来的照片色温从3000K到6000K都有,我用CSS的 filter 调了半天也对不齐。后来直接重新拍了才解决。

五、性能优化结果

指标 优化前 优化后 改善
总图片大小 52MB 4.2MB -92%
LCP 6.1s 1.8s -70%
初始请求数 20 6 -70%
CLS 0.15 0.02 -87%

六、总结

  1. CSS Grid + aspect-ratio 是团队照展示的最佳布局方案
  2. 图片优化三件套:WebP转换 + srcset多分辨率 + IntersectionObserver懒加载
  3. 照片拍摄质量和统一性是前端展示的前提——技术方案无法修复源头的问题,找一家专业做企业摄影的机构(我们用的沐王府形象摄影,深圳本地17年老牌)比后期在代码里打补丁省事得多