add
This commit is contained in:
		
							parent
							
								
									4b75a06c22
								
							
						
					
					
						commit
						163115c8d5
					
				@ -36,3 +36,7 @@
 | 
			
		||||
    transform: rotate(360deg);
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
input {
 | 
			
		||||
  width: 100%;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										105
									
								
								src/App.tsx
									
									
									
									
									
								
							
							
						
						
									
										105
									
								
								src/App.tsx
									
									
									
									
									
								
							@ -8,6 +8,7 @@ type ImageWatermarkProps = {
 | 
			
		||||
  fontColor?: string;
 | 
			
		||||
  opacity?: number;
 | 
			
		||||
  spacing?: number;
 | 
			
		||||
  isOrigin?: boolean;
 | 
			
		||||
};
 | 
			
		||||
const ImageWatermark = ({
 | 
			
		||||
  file,
 | 
			
		||||
@ -16,8 +17,8 @@ const ImageWatermark = ({
 | 
			
		||||
  fontColor = "#000",
 | 
			
		||||
  opacity = 0.2,
 | 
			
		||||
  spacing = 50,
 | 
			
		||||
  isOrigin = false,
 | 
			
		||||
}: ImageWatermarkProps) => {
 | 
			
		||||
  // const [src, setSrc] = useState("");
 | 
			
		||||
  const canvasRef = useRef<HTMLCanvasElement>(null);
 | 
			
		||||
  const imgRef = useRef(new Image());
 | 
			
		||||
 | 
			
		||||
@ -31,21 +32,26 @@ const ImageWatermark = ({
 | 
			
		||||
    img.src = src;
 | 
			
		||||
    img.onload = () => {
 | 
			
		||||
      // 获取设备像素比
 | 
			
		||||
      // const devicePixelRatio = window.devicePixelRatio || 1;
 | 
			
		||||
      const devicePixelRatio = 1;
 | 
			
		||||
 | 
			
		||||
      const width = isOrigin ? img.width : 1200;
 | 
			
		||||
      const height = isOrigin ? img.height : (img.height / img.width) * width;
 | 
			
		||||
 | 
			
		||||
      // 设置canvas的物理像素尺寸
 | 
			
		||||
      canvas.width = img.width * devicePixelRatio;
 | 
			
		||||
      canvas.height = img.height * devicePixelRatio;
 | 
			
		||||
      canvas.width = width;
 | 
			
		||||
      canvas.height = height;
 | 
			
		||||
 | 
			
		||||
      // 缩放绘图上下文以适应设备像素比
 | 
			
		||||
      context.scale(devicePixelRatio, devicePixelRatio);
 | 
			
		||||
 | 
			
		||||
      // 绘制图片
 | 
			
		||||
      context.drawImage(img, 0, 0, img.width, img.height);
 | 
			
		||||
      context.drawImage(img, 0, 0, width, height);
 | 
			
		||||
 | 
			
		||||
      // 根据图片尺寸动态计算字体大小
 | 
			
		||||
      const finallyFontSize = Math.min(width, height) / fontSize;
 | 
			
		||||
 | 
			
		||||
      // 设置水印样式
 | 
			
		||||
      context.font = `${fontSize}px Arial`;
 | 
			
		||||
      context.font = `${finallyFontSize}px Arial`;
 | 
			
		||||
      context.fillStyle = fontColor;
 | 
			
		||||
      context.globalAlpha = opacity;
 | 
			
		||||
      context.textAlign = "center";
 | 
			
		||||
@ -54,24 +60,24 @@ const ImageWatermark = ({
 | 
			
		||||
      // 计算水印文本的宽度和高度
 | 
			
		||||
      const textMetrics = context.measureText(text);
 | 
			
		||||
      const textWidth = textMetrics.width;
 | 
			
		||||
      const textHeight = fontSize;
 | 
			
		||||
      const textHeight = finallyFontSize;
 | 
			
		||||
 | 
			
		||||
      // 计算水印的间距和偏移
 | 
			
		||||
      const stepX = textWidth + spacing;
 | 
			
		||||
      const stepY = textHeight + spacing;
 | 
			
		||||
      const stepX = textWidth + finallyFontSize + spacing;
 | 
			
		||||
      const stepY = textHeight + finallyFontSize + spacing;
 | 
			
		||||
      const angle = Math.PI / 4; // 45度角
 | 
			
		||||
 | 
			
		||||
      // 保存当前绘图状态
 | 
			
		||||
      context.save();
 | 
			
		||||
 | 
			
		||||
      // 旋转canvas以绘制斜角水印
 | 
			
		||||
      context.translate(img.width / 2, img.height / 2);
 | 
			
		||||
      context.translate(width / 2, height / 2);
 | 
			
		||||
      context.rotate(-angle);
 | 
			
		||||
      context.translate(-img.width / 2, -img.height / 2);
 | 
			
		||||
      context.translate(-width / 2, -height / 2);
 | 
			
		||||
 | 
			
		||||
      // 绘制斜角、重复的水印文本
 | 
			
		||||
      for (let x = -img.width; x < img.width * 2; x += stepX) {
 | 
			
		||||
        for (let y = -img.height; y < img.height * 2; y += stepY) {
 | 
			
		||||
      for (let x = -width; x < width * 2; x += stepX) {
 | 
			
		||||
        for (let y = -height; y < height * 2; y += stepY) {
 | 
			
		||||
          context.fillText(text, x, y);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
@ -86,11 +92,13 @@ const ImageWatermark = ({
 | 
			
		||||
  }, [file, text, fontSize, fontColor, opacity, spacing]);
 | 
			
		||||
 | 
			
		||||
  const onDownload = () => {
 | 
			
		||||
    const canvas = canvasRef.current!;
 | 
			
		||||
    const a = document.createElement("a");
 | 
			
		||||
    a.href = canvas.toDataURL("image/png");
 | 
			
		||||
    a.download = file.name;
 | 
			
		||||
    a.click();
 | 
			
		||||
    if (confirm("确定下载图片?")) {
 | 
			
		||||
      const canvas = canvasRef.current!;
 | 
			
		||||
      const a = document.createElement("a");
 | 
			
		||||
      a.href = canvas.toDataURL("image/png");
 | 
			
		||||
      a.download = file.name;
 | 
			
		||||
      a.click();
 | 
			
		||||
    }
 | 
			
		||||
  };
 | 
			
		||||
 | 
			
		||||
  return (
 | 
			
		||||
@ -99,6 +107,7 @@ const ImageWatermark = ({
 | 
			
		||||
      style={{
 | 
			
		||||
        width: "100%", // 设置CSS尺寸
 | 
			
		||||
        height: "auto", // 设置CSS尺寸
 | 
			
		||||
        marginBottom: "20px",
 | 
			
		||||
      }}
 | 
			
		||||
      onClick={onDownload}
 | 
			
		||||
    />
 | 
			
		||||
@ -108,11 +117,32 @@ const ImageWatermark = ({
 | 
			
		||||
function App() {
 | 
			
		||||
  const [files, setFiles] = useState<File[]>([]);
 | 
			
		||||
 | 
			
		||||
  const [keyword, setKeyword] = useState<string>("水印");
 | 
			
		||||
  const [fontSize, setFontSize] = useState(16);
 | 
			
		||||
  const [fontColor, setFontColor] = useState("#000");
 | 
			
		||||
  const [opacity, setOpacity] = useState(0.2);
 | 
			
		||||
  const [spacing, setSpacing] = useState(50);
 | 
			
		||||
  const [keyword, setKeyword] = useState<string>(
 | 
			
		||||
    localStorage.getItem("keyword") || "水印"
 | 
			
		||||
  );
 | 
			
		||||
  const [fontSize, setFontSize] = useState(
 | 
			
		||||
    Number(localStorage.getItem("fontSize")) || 16
 | 
			
		||||
  );
 | 
			
		||||
  const [fontColor, setFontColor] = useState(
 | 
			
		||||
    localStorage.getItem("fontColor") || "#000000"
 | 
			
		||||
  );
 | 
			
		||||
  const [opacity, setOpacity] = useState(
 | 
			
		||||
    Number(localStorage.getItem("opacity")) || 0.2
 | 
			
		||||
  );
 | 
			
		||||
  const [spacing, setSpacing] = useState(
 | 
			
		||||
    Number(localStorage.getItem("spacing")) || 50
 | 
			
		||||
  );
 | 
			
		||||
  const [isOrigin, setIsOrigin] = useState(
 | 
			
		||||
    localStorage.getItem("isOrigin") === "true"
 | 
			
		||||
  );
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    localStorage.setItem("keyword", keyword);
 | 
			
		||||
    localStorage.setItem("fontSize", String(fontSize));
 | 
			
		||||
    localStorage.setItem("fontColor", fontColor);
 | 
			
		||||
    localStorage.setItem("opacity", String(opacity));
 | 
			
		||||
    localStorage.setItem("spacing", String(spacing));
 | 
			
		||||
  }, [keyword, fontSize, fontColor, opacity, spacing]);
 | 
			
		||||
 | 
			
		||||
  const onSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
 | 
			
		||||
    setFiles(Array.from(e.target.files || []));
 | 
			
		||||
@ -121,7 +151,14 @@ function App() {
 | 
			
		||||
  return (
 | 
			
		||||
    <div className="App">
 | 
			
		||||
      <div
 | 
			
		||||
        style={{ gap: 10, display: "grid", gridTemplate: "'a b' / auto 1fr" }}
 | 
			
		||||
        style={{
 | 
			
		||||
          padding: 16,
 | 
			
		||||
          gap: 10,
 | 
			
		||||
          display: "grid",
 | 
			
		||||
          gridTemplate: "'a b' / auto 1fr",
 | 
			
		||||
          alignItems: "flex-start",
 | 
			
		||||
          justifyItems: "flex-start",
 | 
			
		||||
        }}
 | 
			
		||||
      >
 | 
			
		||||
        <label htmlFor="file">文件</label>
 | 
			
		||||
        <input
 | 
			
		||||
@ -139,14 +176,14 @@ function App() {
 | 
			
		||||
          value={keyword}
 | 
			
		||||
          onChange={(e) => setKeyword(e.target.value)}
 | 
			
		||||
        />
 | 
			
		||||
        <label htmlFor="fontSize">字体大小</label>
 | 
			
		||||
        <label htmlFor="fontSize">文字大小</label>
 | 
			
		||||
        <input
 | 
			
		||||
          type="range"
 | 
			
		||||
          id="fontSize"
 | 
			
		||||
          value={fontSize}
 | 
			
		||||
          min={12}
 | 
			
		||||
          max={48}
 | 
			
		||||
          onChange={(e) => setFontSize(Number(e.target.value))}
 | 
			
		||||
          value={50 - fontSize}
 | 
			
		||||
          min={3}
 | 
			
		||||
          max={50}
 | 
			
		||||
          onChange={(e) => setFontSize(50 - Number(e.target.value))}
 | 
			
		||||
        />
 | 
			
		||||
        <label htmlFor="fontColor">字体颜色</label>
 | 
			
		||||
        <input
 | 
			
		||||
@ -170,11 +207,18 @@ function App() {
 | 
			
		||||
          type="range"
 | 
			
		||||
          id="spacing"
 | 
			
		||||
          value={spacing}
 | 
			
		||||
          min={10}
 | 
			
		||||
          min={0}
 | 
			
		||||
          max={100}
 | 
			
		||||
          step={10}
 | 
			
		||||
          onChange={(e) => setSpacing(Number(e.target.value))}
 | 
			
		||||
        />
 | 
			
		||||
        <label htmlFor="isOrigin">是否使用原图</label>
 | 
			
		||||
        <input
 | 
			
		||||
          id="isOrigin"
 | 
			
		||||
          type="checkbox"
 | 
			
		||||
          checked={isOrigin}
 | 
			
		||||
          onChange={(e) => setIsOrigin(e.target.checked)}
 | 
			
		||||
        />
 | 
			
		||||
      </div>
 | 
			
		||||
 | 
			
		||||
      {files.map((file) => (
 | 
			
		||||
@ -186,6 +230,7 @@ function App() {
 | 
			
		||||
          opacity={opacity}
 | 
			
		||||
          key={file.name}
 | 
			
		||||
          text={keyword}
 | 
			
		||||
          isOrigin={isOrigin}
 | 
			
		||||
        />
 | 
			
		||||
      ))}
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user