Kirby
首页文章随笔书签提示词AI 助手
更多

关于图片上使用渐变模糊的探索

2024-08-25·
次浏览

AI 摘要

生成中...

在苹果的 App Store 中,许多应用程序截图上采用了渐变模糊效果,营造出一种柔和舒适的视觉效果。这种效果类似于在图片上叠加一层逐渐模糊的面纱,提供了视觉清晰度和深度感。

1
2
3

最简单的实现方式

从图片底部开始,逐渐增加模糊效果,直到顶部。没有叠加任何的颜色,只是在图片上叠加了不同程度的模糊图层。

Page.tsx
const getGradientStyle = (blur: number, opacity: number) => {
  return {
    backdropFilter: `blur(${blur}px)`,
    WebkitBackdropFilter: `blur(${blur}px)`,
    background: `linear-gradient(to top, rgba(0,0,0,${opacity}) 20%, rgba(0,0,0,0) 100%)`,
  }
}
 
;<div className="will-change-filter absolute inset-x-0 bottom-0 isolate z-10 h-1/3 max-h-[100px] min-h-[64px]">
  <div style={getGradientStyle(12, 0.8)} className="gradient-mask absolute inset-0 blur-md"></div>
  <div style={getGradientStyle(6, 0.6)} className="gradient-mask absolute inset-0 blur-[6px]"></div>
  <div style={getGradientStyle(3, 0.4)} className="gradient-mask absolute inset-0 blur-[3px]"></div>
  <div style={getGradientStyle(2, 0.2)} className="gradient-mask absolute inset-0 blur-[2px]"></div>
  <div style={getGradientStyle(1, 0.1)} className="gradient-mask absolute inset-0 blur-[1px]"></div>
</div>

相关的的CSS代码:

global.css
.gradient-mask {
  -webkit-mask-image: linear-gradient(0deg, #000 0, transparent);
  mask-image: linear-gradient(0deg, #000 0, transparent);
}

另外,还可以像下面图片中这样,从图片底部吸取颜色,作为渐变模糊的颜色,更接近苹果商店中的效果。这种方法,比较适合图片的下半部分区域有大面积的主色,看起来才会自然、融合。否则,还不如第一种效果。

4

两种效果对比:

first image
second image

结合吸取颜色的实现方式

Page.tsx
function Card({
  title,
  media,
  description,
  year,
  type,
  location,
  camera,
  poster,
}: GalleryCardType) {
  const [isPlaying, setIsPlaying] = useState(false)
  const [isHovered, setIsHovered] = useState(false)
  const [extractedColor, setExtractedColor] = useState<string | null>(null)
  const videoRef = useRef<HTMLVideoElement>(null)
  const cardRef = useRef<HTMLDivElement>(null)
 
  {
    /* 从图片底部提取颜色 */
  }
  const extractColor = (imgSrc: string): Promise<string> => {
    return new Promise((resolve) => {
      const img = new Image()
      img.crossOrigin = 'Anonymous'
      img.onload = () => {
        const canvas = document.createElement('canvas')
        const ctx = canvas.getContext('2d')
        if (!ctx) throw new Error('无法创建 canvas 上下文')
 
        canvas.width = img.width
        canvas.height = img.height
        ctx.drawImage(img, 0, 0, img.width, img.height)
 
        const imageData = ctx.getImageData(0, img.height - 1, img.width, 1).data
        const r = imageData[0]
        const g = imageData[1]
        const b = imageData[2]
        const color = `rgb(${r},${g},${b})`
 
        resolve(color)
      }
      img.src = imgSrc
    })
  }
 
  useEffect(() => {
    if (type === 'image') {
      extractColor(media).then(setExtractedColor)
    }
  }, [media, type])
 
  const getGradientStyle = (blur: number, opacity: number) => {
    if (!extractedColor) {
      return {
        backdropFilter: `blur(${blur}px)`,
        WebkitBackdropFilter: `blur(${blur}px)`,
        background: `linear-gradient(to top, rgba(0,0,0,${opacity}) 20%, rgba(0,0,0,0) 100%)`,
      }
    }
 
    const rgbMatch = extractedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/)
    if (!rgbMatch) {
      return {
        backdropFilter: `blur(${blur}px)`,
        WebkitBackdropFilter: `blur(${blur}px)`,
        background: `linear-gradient(to top, rgba(0,0,0,${opacity}) 20%, rgba(0,0,0,0) 100%)`,
      }
    }
 
    const [, r, g, b] = rgbMatch.map(Number)
    return {
      backdropFilter: `blur(${blur}px)`,
      WebkitBackdropFilter: `blur(${blur}px)`,
      background: `linear-gradient(to top, rgba(${r},${g},${b},${opacity}) 20%, rgba(${r},${g},${b},0) 100%)`,
    }
  }
 
  return (
    <div
      ref={cardRef}
      className="group overflow-hidden rounded-xl p-px"
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => setIsHovered(false)}
    >
      <div className="group relative overflow-hidden">
        <svg width="0" height="0">
          <filter id="round-corners">
            <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur" />
            <feColorMatrix
              in="blur"
              mode="matrix"
              values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9"
              result="goo"
            />
            <feComposite in="SourceGraphic" in2="goo" operator="atop" />
          </filter>
        </svg>
        <div
          className="relative"
          style={{
            filter: 'url(#round-corners)',
          }}
        >
          {renderMedia()}
 
          {/* 渐变模糊效果 */}
          {(!isPlaying || type === 'image') && (
            <div className="will-change-filter absolute inset-x-0 bottom-0 isolate z-10 h-1/3 min-h-[64px] max-h-[100px]">
              <div
                style={getGradientStyle(12, 0.8)}
                className="gradient-mask absolute inset-0 blur-md"
              ></div>
              <div
                style={getGradientStyle(6, 0.6)}
                className="gradient-mask absolute inset-0 blur-[6px]"
              ></div>
              <div
                style={getGradientStyle(3, 0.4)}
                className="gradient-mask absolute inset-0 blur-[3px]"
              ></div>
              <div
                style={getGradientStyle(2, 0.2)}
                className="gradient-mask absolute inset-0 blur-[2px]"
              ></div>
              <div
                style={getGradientStyle(1, 0.1)}
                className="gradient-mask absolute inset-0 blur-[1px]"
              ></div>
            </div>
          )}
        </div>
      </div>
    </div>
  )
}

从底部向上20%的位置开始渐变。

解决图像角落泛白问题

如果图像的底部2个角落出现泛白,可以尝试下面的方法。

  {/* 这段代码的作用是创建一个SVG滤镜,用于在图像上创建圆角。 */}
  <svg width="0" height="0">
    <filter id="round-corners">
      <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur" />
      <feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0  0 1 0 0 0  0 0 1 0 0  0 0 0 19 -9" result="goo" />
      <feComposite in="SourceGraphic" in2="goo" operator="atop" />
    </filter>
  </svg>
  {/* 下面这段代码的作用是将创建的SVG滤镜应用到一个相对定位的div上。 */}
  <div
    className="relative"
    style={{
      filter: 'url(#round-corners)',
    }}
  >

Figma 中实现

借助一个插件,就可以轻松做到了:Progressive Blur

5

上一篇Framer Motion 速查手册post,
下一篇使用 FigmaEX 调整 Figma UI3 界面post,