svgdotjs / svgdom

Straightforward DOM implementation to make SVG.js run headless on Node.js
MIT License
269 stars 53 forks source link

How to export animated svg. #124

Closed liudonghua123 closed 1 month ago

liudonghua123 commented 1 month ago

Hi, I tried to rewrite readme-typing-svg/readme-typing-svg.demolab.com using pure front-end js. And I am not familar with svg and I used some code generated via GP4o. The main code is lib.ts and App.vue.

If I insert the svg element in the preview div, it's ok. But when I export svg string, it is like this.

<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink" width="435" height="500" viewBox="0 0 435 500">
    <rect width="435" height="500" fill="#00000000"></rect><text font-size="24" fill="#36BCF7FF" font-weight="400" font-family="Fira Code" letterSpacing="normal" x="10" y="238">
        <tspan dy="0" x="10">T</tspan>
    </text><text font-size="24" fill="#36BCF7FF" font-weight="400" font-family="Fira Code" letterSpacing="normal">
        <tspan dy="0" x="0">|</tspan>
    </text>
</svg>

What I expected is like readme-typing-svg.demolab.com exported svg.

<svg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' viewBox='0 0 435 50' style='background-color: #00000000;' width='435px' height='50px'>
    <path id='path0'>
        <!-- Single line -->
        <animate id='d0' attributeName='d' begin='0s;d0.end' dur='6000ms' fill='remove' values='m0,25 h0 ; m0,25 h435 ; m0,25 h435 ; m0,25 h0' keyTimes='0;0.66666666666667;0.83333333333333;1' />
    </path>
    <text font-family='"Fira Code", monospace' fill='#36BCF7' font-size='20' dominant-baseline='auto' x='0%' text-anchor='start' letter-spacing='normal'>
        <textPath xlink:href='#path0'>
            The five boxing wizards jump quickly
        </textPath>
    </text>
</svg>

The main code of svg generation of readme-typing-svg is main.php and RendererView.php.

Fuzzyma commented 1 month ago

You never add an animate-element to the svg. So there is no way it will show up in the exported svg. Your animation is done through js and not through svg. So you would need to include the script in your svg.

However, this is clearly not a bug. Please ask questions like this on stackoverflow

EDIT: for clarification: the animate() call in svg.js animates svg with javascript and not with the <animate> element. I am not even sure javascript is allowed to run from githubs readmeas. If you want to add an animate-element you can do canvas.add('<animate ...></animate>')

liudonghua123 commented 1 month ago

Well, I see, another question, how can I export the svg with the animation js code? I use SVGSVGElement.outerHTML now.

Maybe I need to rewrite the code like this.

import fs from "fs";

interface RenderOptions {
  text: string;
  duration: number;
  horizontallyCentered: boolean;
  fontSize: number;
  fontFamily: string;
  outputPath: string;
  width: number;
  height: number;
  textColor: string;
  backgroundColor: string;
}

function generateSVG(options: RenderOptions): string {
  const { text, duration, horizontallyCentered, fontSize, fontFamily, width, height, textColor, backgroundColor } =
    options;

  const x = horizontallyCentered ? "50%" : "0%";
  const textAnchor = horizontallyCentered ? "middle" : "start";
  const pathId = "path0";
  const animateId = "d0";
  const keyTimes = "0;0.66666666666667;0.83333333333333;1";
  const values = `m0,25 h0 ; m0,25 h${width} ; m0,25 h${width} ; m0,25 h0`;
  const fontFamilyEscaped = fontFamily.includes(" ") ? `"${fontFamily}"` : fontFamily;

  return `
<svg xmlns='http://www.w3.org/2000/svg'
    xmlns:xlink='http://www.w3.org/1999/xlink'
    viewBox='0 0 ${width} ${height}'
    style='background-color: ${backgroundColor};'
    width='${width}px' height='${height}px'>
    <path id='${pathId}'>
        <animate id='${animateId}' attributeName='d' begin='0s;${animateId}.end'
            dur='${duration}ms' fill='remove'
            values='${values}' keyTimes='${keyTimes}' />
    </path>
    <text font-family='${fontFamilyEscaped}' fill='${textColor}' font-size='${fontSize}'
        dominant-baseline='auto'
        x='${x}' text-anchor='${textAnchor}'
        letter-spacing='normal'>
        <textPath xlink:href='#${pathId}'>
            ${text}
        </textPath>
    </text>
</svg>`;
}

export function renderSvg(options: RenderOptions): void {
  const svgData = generateSVG(options);
  fs.writeFileSync(options.outputPath, svgData);
  console.log(`SVG file has been saved as ${options.outputPath}`);
}

// Example usage
const options: RenderOptions = {
  text: "The five boxing wizards jump quickly",
  duration: 6000,
  horizontallyCentered: false,
  fontSize: 20,
  fontFamily: "Fira Code, monospace",
  outputPath: "output.svg",
  width: 435,
  height: 50,
  textColor: "#36BCF7",
  backgroundColor: "#00000000",
};

renderSvg(options);
liudonghua123 commented 1 month ago

@Fuzzyma Thanks, I rewrite the svg creation using raw plain string concatation now. See the code .

Fuzzyma commented 1 month ago

Perfect! Probably fits your problem space better!