🚀 improve svg generation
This commit is contained in:
@@ -24,7 +24,7 @@ try {
|
|||||||
} catch (err) {}
|
} catch (err) {}
|
||||||
|
|
||||||
for (const [key, grid] of Object.entries(grids))
|
for (const [key, grid] of Object.entries(grids))
|
||||||
it(`should generate ${key} gif`, () => {
|
it(`should generate ${key} svg`, () => {
|
||||||
const chain = [snake, ...getBestRoute(grid, snake)!];
|
const chain = [snake, ...getBestRoute(grid, snake)!];
|
||||||
|
|
||||||
const gif = createSvg(grid, chain, drawOptions, gifOptions);
|
const gif = createSvg(grid, chain, drawOptions, gifOptions);
|
||||||
|
|||||||
@@ -27,13 +27,14 @@ export const createGrid = (
|
|||||||
) => {
|
) => {
|
||||||
const svgElements: string[] = [];
|
const svgElements: string[] = [];
|
||||||
const styles = [
|
const styles = [
|
||||||
`rect.c{
|
`.c{
|
||||||
shape-rendering: geometricPrecision;
|
shape-rendering: geometricPrecision;
|
||||||
outline: 1px solid ${colorBorder};
|
|
||||||
outline-offset: -1px;
|
|
||||||
rx: ${sizeBorderRadius};
|
rx: ${sizeBorderRadius};
|
||||||
ry: ${sizeBorderRadius};
|
ry: ${sizeBorderRadius};
|
||||||
fill:${colorEmpty}
|
fill:${colorEmpty};
|
||||||
|
stroke-width: 1px;
|
||||||
|
stroke: ${colorBorder};
|
||||||
|
animation: none ${duration}ms linear infinite;
|
||||||
}`,
|
}`,
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -55,14 +56,13 @@ export const createGrid = (
|
|||||||
`${percent(t + 0.0001)}%,100%{fill:${colorEmpty}}` +
|
`${percent(t + 0.0001)}%,100%{fill:${colorEmpty}}` +
|
||||||
"}",
|
"}",
|
||||||
|
|
||||||
`#${id}{fill:${fill};animation: ${animationName} linear ${duration}ms infinite}`
|
`.c.${id}{fill:${fill};animation-name: ${animationName}}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
svgElements.push(
|
svgElements.push(
|
||||||
h("rect", {
|
h("rect", {
|
||||||
id,
|
class: ["c", id].filter(Boolean).join(" "),
|
||||||
class: "c",
|
|
||||||
x: x * s + m,
|
x: x * s + m,
|
||||||
y: y * s + m,
|
y: y * s + m,
|
||||||
width: d,
|
width: d,
|
||||||
|
|||||||
@@ -25,26 +25,20 @@ export type Options = {
|
|||||||
cells?: Point[];
|
cells?: Point[];
|
||||||
};
|
};
|
||||||
|
|
||||||
const createCells = ({ width, height }: Grid) =>
|
const getCellsFromGrid = ({ width, height }: Grid) =>
|
||||||
Array.from({ length: width }, (_, x) =>
|
Array.from({ length: width }, (_, x) =>
|
||||||
Array.from({ length: height }, (_, y) => ({ x, y }))
|
Array.from({ length: height }, (_, y) => ({ x, y }))
|
||||||
).flat();
|
).flat();
|
||||||
|
|
||||||
export const createSvg = (
|
const createLivingCells = (
|
||||||
grid0: Grid,
|
grid0: Grid,
|
||||||
chain: Snake[],
|
chain: Snake[],
|
||||||
drawOptions: Options,
|
drawOptions: Options
|
||||||
gifOptions: { frameDuration: number }
|
|
||||||
) => {
|
) => {
|
||||||
const width = (grid0.width + 2) * drawOptions.sizeCell;
|
|
||||||
const height = (grid0.height + 5) * drawOptions.sizeCell;
|
|
||||||
|
|
||||||
const duration = gifOptions.frameDuration * chain.length;
|
|
||||||
|
|
||||||
const cells: (Point & {
|
const cells: (Point & {
|
||||||
t: number | null;
|
t: number | null;
|
||||||
color: Color | Empty;
|
color: Color | Empty;
|
||||||
})[] = (drawOptions.cells ?? createCells(grid0)).map(({ x, y }) => ({
|
})[] = (drawOptions.cells ?? getCellsFromGrid(grid0)).map(({ x, y }) => ({
|
||||||
x,
|
x,
|
||||||
y,
|
y,
|
||||||
t: null,
|
t: null,
|
||||||
@@ -64,13 +58,29 @@ export const createSvg = (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return cells;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const createSvg = (
|
||||||
|
grid: Grid,
|
||||||
|
chain: Snake[],
|
||||||
|
drawOptions: Options,
|
||||||
|
gifOptions: { frameDuration: number }
|
||||||
|
) => {
|
||||||
|
const width = (grid.width + 2) * drawOptions.sizeCell;
|
||||||
|
const height = (grid.height + 5) * drawOptions.sizeCell;
|
||||||
|
|
||||||
|
const duration = gifOptions.frameDuration * chain.length;
|
||||||
|
|
||||||
|
const cells = createLivingCells(grid, chain, drawOptions);
|
||||||
|
|
||||||
const elements = [
|
const elements = [
|
||||||
createGrid(cells, drawOptions, duration),
|
createGrid(cells, drawOptions, duration),
|
||||||
createStack(
|
createStack(
|
||||||
cells,
|
cells,
|
||||||
drawOptions,
|
drawOptions,
|
||||||
grid0.width * drawOptions.sizeCell,
|
grid.width * drawOptions.sizeCell,
|
||||||
(grid0.height + 2) * drawOptions.sizeCell,
|
(grid.height + 2) * drawOptions.sizeCell,
|
||||||
duration
|
duration
|
||||||
),
|
),
|
||||||
createSnake(chain, drawOptions, duration),
|
createSnake(chain, drawOptions, duration),
|
||||||
@@ -83,21 +93,32 @@ export const createSvg = (
|
|||||||
height,
|
height,
|
||||||
].join(" ");
|
].join(" ");
|
||||||
|
|
||||||
return [
|
const style = elements
|
||||||
|
.map((e) => e.styles)
|
||||||
|
.flat()
|
||||||
|
.join("");
|
||||||
|
|
||||||
|
const svg = [
|
||||||
`<svg
|
`<svg
|
||||||
${toAttribute({
|
${toAttribute({
|
||||||
viewBox,
|
viewBox,
|
||||||
width,
|
width,
|
||||||
height,
|
height,
|
||||||
xmlns: "http://www.w3.org/2000/svg",
|
xmlns: "http://www.w3.org/2000/svg",
|
||||||
})}
|
})}
|
||||||
>`,
|
>`,
|
||||||
|
|
||||||
"<style>",
|
"<style>",
|
||||||
...elements.map((e) => e.styles).flat(),
|
optimizeCss(style),
|
||||||
"</style>",
|
"</style>",
|
||||||
|
|
||||||
...elements.map((e) => e.svgElements).flat(),
|
...elements.map((e) => e.svgElements).flat(),
|
||||||
|
|
||||||
"</svg>",
|
"</svg>",
|
||||||
].join("\n");
|
].join("");
|
||||||
|
|
||||||
|
return optimizeSvg(svg);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const optimizeCss = (css: string) => css;
|
||||||
|
const optimizeSvg = (svg: string) => svg;
|
||||||
|
|||||||
@@ -42,17 +42,16 @@ export const createSnake = (
|
|||||||
|
|
||||||
const m = (sizeCell - s) / 2;
|
const m = (sizeCell - s) / 2;
|
||||||
|
|
||||||
const r = Math.min(4.5, (4 * s) / sizeDot).toFixed(2);
|
const r = Math.min(4.5, (4 * s) / sizeDot);
|
||||||
|
|
||||||
return h("rect", {
|
return h("rect", {
|
||||||
class: "s",
|
class: `s s${i}`,
|
||||||
id: `s${i}`,
|
x: m.toFixed(1),
|
||||||
x: m,
|
y: m.toFixed(1),
|
||||||
y: m,
|
width: s.toFixed(1),
|
||||||
width: s,
|
height: s.toFixed(1),
|
||||||
height: s,
|
rx: r.toFixed(1),
|
||||||
rx: r,
|
ry: r.toFixed(1),
|
||||||
ry: r,
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -60,9 +59,10 @@ export const createSnake = (
|
|||||||
`transform:translate(${x * sizeCell}px,${y * sizeCell}px)`;
|
`transform:translate(${x * sizeCell}px,${y * sizeCell}px)`;
|
||||||
|
|
||||||
const styles = [
|
const styles = [
|
||||||
`rect.s{
|
`.s{
|
||||||
shape-rendering: geometricPrecision;
|
shape-rendering:geometricPrecision;
|
||||||
fill:${colorSnake};
|
fill:${colorSnake};
|
||||||
|
animation: none linear ${duration}ms infinite
|
||||||
}`,
|
}`,
|
||||||
|
|
||||||
...snakeParts.map((positions, i) => {
|
...snakeParts.map((positions, i) => {
|
||||||
@@ -78,10 +78,7 @@ export const createSnake = (
|
|||||||
.join("") +
|
.join("") +
|
||||||
"}",
|
"}",
|
||||||
|
|
||||||
`#${id}{` +
|
`.s.${id}{${transform(positions[0])};animation-name: ${animationName}}`,
|
||||||
`${transform(positions[0])};` +
|
|
||||||
`animation: ${animationName} linear ${duration}ms infinite` +
|
|
||||||
"}",
|
|
||||||
];
|
];
|
||||||
}),
|
}),
|
||||||
].flat();
|
].flat();
|
||||||
|
|||||||
@@ -16,7 +16,12 @@ export const createStack = (
|
|||||||
duration: number
|
duration: number
|
||||||
) => {
|
) => {
|
||||||
const svgElements: string[] = [];
|
const svgElements: string[] = [];
|
||||||
const styles = [];
|
const styles = [
|
||||||
|
`.u{
|
||||||
|
animation: none linear ${duration}ms infinite;
|
||||||
|
fill:transparent;
|
||||||
|
}`,
|
||||||
|
];
|
||||||
|
|
||||||
const stack = cells
|
const stack = cells
|
||||||
.slice()
|
.slice()
|
||||||
@@ -26,14 +31,20 @@ export const createStack = (
|
|||||||
const m = width / stack.length;
|
const m = width / stack.length;
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (const { color, t } of stack) {
|
for (const { color, t } of stack) {
|
||||||
const x = ((i * width) / stack.length).toFixed(2);
|
const x = (i * width) / stack.length;
|
||||||
const id = "t" + (i++).toString(36);
|
const id = "u" + (i++).toString(36);
|
||||||
const animationName = "a" + id;
|
const animationName = "a" + id;
|
||||||
// @ts-ignore
|
// @ts-ignore
|
||||||
const fill = colorDots[color];
|
const fill = colorDots[color];
|
||||||
|
|
||||||
svgElements.push(
|
svgElements.push(
|
||||||
h("rect", { id, height: sizeDot, width: (m + 0.6).toFixed(2), x, y })
|
h("rect", {
|
||||||
|
class: `u ${id}`,
|
||||||
|
height: sizeDot,
|
||||||
|
width: (m + 0.6).toFixed(1),
|
||||||
|
x: x.toFixed(1),
|
||||||
|
y,
|
||||||
|
})
|
||||||
);
|
);
|
||||||
styles.push(
|
styles.push(
|
||||||
`@keyframes ${animationName} {` +
|
`@keyframes ${animationName} {` +
|
||||||
@@ -41,7 +52,7 @@ export const createStack = (
|
|||||||
`${percent(t + 0.0001)}%,100%{fill:${fill}}` +
|
`${percent(t + 0.0001)}%,100%{fill:${fill}}` +
|
||||||
"}",
|
"}",
|
||||||
|
|
||||||
`#${id}{fill:transparent;animation: ${animationName} linear ${duration}ms infinite}`
|
`.u.${id}{animation-name:${animationName}}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user