我一整天都在尝试创建一个可重用的 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;
代码按原样使用
<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>