cover-image.md
组件 · COMPONENT

封面图 CoverImage

封面三态:加载中只露父容器的暗底,就绪后 350ms 淡入,失败回退到线稿。杜绝「先画线稿、封面到了再跳变盖上」的闪跳。

三种结局
无封面 → 直接画线稿
正常 → 暗底上淡入 350ms
失效 → 回退线稿
import { CoverImage } from './components/v4/CoverImage.jsx'

const ACCENT = 'var(--zzm-accent, #A6402F)'
const COVER = 'data:image/svg+xml;utf8,' + encodeURIComponent(
  '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 320 180"><rect width="320" height="180" fill="#F6F6F4"/>' +
  '<text x="24" y="92" font-family="-apple-system,PingFang SC,sans-serif" font-size="22" font-weight="700" fill="#161616">上传的课程封面</text>' +
  '<rect x="24" y="108" width="28" height="3" fill="#A6402F"/></svg>',
)

const Fallback = () => (
  <svg viewBox="0 0 240 135" preserveAspectRatio="xMidYMid slice" style={{ position: 'absolute', inset: 0, width: '100%', height: '100%', opacity: 0.5 }}>
    <polyline points="18,110 62,48 120,92 178,40 224,104" fill="none" stroke="rgba(255,255,255,.2)" strokeWidth="1" />
    <circle cx="120" cy="92" r="2.6" fill={ACCENT} />
  </svg>
)

const Frame = ({ label, children }) => (
  <div>
    <div style={{ aspectRatio: '16/9', borderRadius: 12, background: '#131316', position: 'relative', overflow: 'hidden' }}>{children}</div>
    <div style={{ marginTop: 8, fontSize: 12, color: '#8B8B85' }}>{label}</div>
  </div>
)

export default function CoverImageStates() {
  return (
    <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))', gap: 16, width: '100%' }}>
      <Frame label="无封面 → 直接画线稿"><CoverImage src={null} fallback={<Fallback />} /></Frame>
      <Frame label="正常 → 暗底上淡入 350ms"><CoverImage src={COVER} fallback={<Fallback />} /></Frame>
      <Frame label="失效 → 回退线稿"><CoverImage src="/v4/__missing-cover__.png" fallback={<Fallback />} /></Frame>
    </div>
  )
}

属性

属性取值默认说明
srcstring · null封面地址;为空直接渲染 fallback
fallbackReactNodenull无封面或加载失败时的兜底(通常是星座线稿)
fit'cover' · 'contain''cover'导学封面用 contain 保证完整展示
fadeMsnumber350淡入时长

父容器负责暗底、比例和圆角(aspect-ratio: 16/9; background: #131316; position: relative; overflow: hidden);图片绝对定位铺满。

一个真实踩过的坑

图片的 load 事件不冒泡,React 的 onLoad 要等 commit 后才绑定。磁盘缓存命中时,字节比监听先到,load 已经触发过了,组件会永远卡在「加载中」——图片明明就绪却 opacity: 0,回访用户只看见一片黑。所以这个组件挂载后先检查 el.complete,没就绪才补挂原生的 load / error 监听。复刻时别换回 onLoad

铁律

✓ 要封面按上传原图呈现
✗ 不要提供「蒙层」参数——叠字可读性由那颗字自己解决