Next.js SVG 图像蒙版(可重用组件)

问题描述 投票:0回答:1

我一整天都在尝试创建一个可重用的 Next.js SVG 图像遮罩组件,仅对本地存储的图像使用顺风/内联样式。我希望能够将 svg 传递给它和图像并输出蒙版图像,并能够通过 classNames 定义宽度、高度...这是我到目前为止所拥有的,但我没有任何运气。 ..

我的 svg 形状就是这个梯形形状:

<svg
    width="auto"
    height="100%"
    viewBox="0 0 313 709"
    fill="none"
>
   <path d="M0 210.338L314 0V507.419L0 709V210.338Z" fill="#404040"/>
</svg>

我希望像这样调用组件:

import svgShape from "@/assets/svg's/AuthImageShape.svg"
import SignInImage from "@/assets/images/SignInImage.png"

<MaskedImage svgPath={svgShape} imageSrc={SignInImage} /> 
// process(strip) svg of everything but it's path
export const extractPath = (svgString: string): string | null => {
    try {
        const parser = new DOMParser()
        const svgDoc = parser.parseFromString(svgString, "image/svg+xml")
        const pathElement = svgDoc.querySelector("path")
        if (pathElement) {
            return pathElement.outerHTML
        } else {
            console.error("Invalid SVG shape: No path element found")
            return null
        }
    } catch (error) {
        console.error("Error parsing SVG shape:", error)
        return null
    }
}

// Reusable component for masking images.
"use client"
import React, { useEffect, useState } from "react"
import { StaticImageData } from "next/image"

interface MaskedImageProps {
  svgPath: string
  imageSrc: any
  maskId?: string
  className?: string
}



const MaskedImage: React.FC<MaskedImageProps> = ({ svgPath, imageSrc, maskId = 'mask', className }) => {

const [pathString, setPathString] = useState<string | null>(null)

          useEffect(() => {
                if (!svgShape) {
                    console.error("No SVG shape provided")
                    return
                }

                const path = extractPath(svgShape)
                if (path) {
                    setPathString(path)
                } else {
                    console.error("Failed to extract path from SVG shape")
                }
            }, [svgShape])

        if (!pathString) {
            return null
        }

  return (
    <div className={`maskedImageContainer ${className}`}>
      <svg width="0" height="0">
        <defs>
          <clipPath id={maskId} clipPathUnits="objectBoundingBox">
            <path d={svgPath} fill="transparent" />
          </clipPath>
        </defs>
      </svg>
      <div style={{ clipPath: `url(#${maskId})` }}>
        <img src={imageSrc} alt="Masked" />
      </div>
    </div>
  );
};

export default MaskedImage;

reactjs svg tailwind-css mask
1个回答
0
投票

代码按原样使用

<svg>
字符串作为
d
元素的
<clipPath>
<path>
属性值。我们想要使用从
extractPath()
:

中提取的字符串路径
<path d={pathString} fill="transparent" />

此外,

extractPath()
返回
<path>
元素XML字符串,但如果我们要将其传递给
d
属性,我们实际上需要它的
d
属性值:

return pathElement.getAttribute('d');

由于设置了

<clipPath>
,因此
clipPathUnits="objectBoundingBox"
的大小为零。这是因为父级
<svg>
0
width
height
。我们想要删除这个属性:

<clipPath id={maskId}>

最后,我们可以使用

useMemo
来获得更好的性能:

const pathString = useMemo(() => {

// process(strip) svg of everything but it's path
const extractPath = (svgString) => {
  try {
    const parser = new DOMParser();
    const svgDoc = parser.parseFromString(svgString, 'image/svg+xml');
    const pathElement = svgDoc.querySelector('path');
    if (pathElement) {
      return pathElement.getAttribute('d');
    } else {
      console.error('Invalid SVG shape: No path element found');
      return null;
    }
  } catch (error) {
    console.error('Error parsing SVG shape:', error);
    return null;
  }
};

const { useMemo } = React;

const MaskedImage = ({ svgPath, imageSrc, maskId = 'mask', className }) => {
  const pathString = useMemo(() => {
    if (!svgShape) {
      console.error('No SVG shape provided');
      return;
    }

    const path = extractPath(svgShape);
    if (path) {
      return path;
    } else {
      console.error('Failed to extract path from SVG shape');
    }
  }, [svgShape]);

  if (!pathString) {
    return null;
  }

  return (
    <div className={`maskedImageContainer ${className}`}>
      <svg width="0" height="0">
        <defs>
          <clipPath id={maskId}>
            <path d={pathString} fill="transparent" />
          </clipPath>
        </defs>
      </svg>
      <div style={{ clipPath: `url(#${maskId})` }}>
        <img src={imageSrc} alt="Masked" />
      </div>
    </div>
  );
};

const svgShape = `<svg
    width="auto"
    height="100%"
    viewBox="0 0 313 709"
    fill="none"
>
   <path d="M0 210.338L314 0V507.419L0 709V210.338Z" fill="#404040"/>
</svg>`;

const SignInImage = 'https://picsum.photos/1000/1000';

ReactDOM.createRoot(document.getElementById('app')).render(
  <MaskedImage svgPath={svgShape} imageSrc={SignInImage} />
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/18.3.1/umd/react.production.min.js" integrity="sha512-QVs8Lo43F9lSuBykadDb0oSXDL/BbZ588urWVCRwSIoewQv/Ewg1f84mK3U790bZ0FfhFa1YSQUmIhG+pIRKeg==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/18.3.1/umd/react-dom.production.min.js" integrity="sha512-6a1107rTlA4gYpgHAqbwLAtxmWipBdJFcq8y5S/aTge3Bp+VAklABm2LO+Kg51vOWR9JMZq1Ovjl5tpluNpTeQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>

<div id="app"></div>

© www.soinside.com 2019 - 2024. All rights reserved.