本节教程介绍 SVG 中组合的基础知识,包括如何:

学习

Permalink to "学习"

在图案制作中,图案基元是由两个或更多元素组成的排列。本演示的图案基元将是一系列围绕某点旋转的文本。<defs><use> 元素将从单个 <text> 元素组合出一个图案基元。

<defs> 元素包含 SVG 元素但不渲染它们。以下示例在 <defs> 元素中定义了一个 <text> 元素。<defs> 中的元素可以通过 id 来引用。

const helloDefs = svg`
<defs>
<text id="chars">Hello defs!</text>
</defs>
`;

要复用之前在 <defs> 中定义的 <text> 元素,请将 <use> 元素的 href 属性设置为 #chars,如下例所示。

const helloDefs = svg`
<defs>
<text id="chars">Hello defs!</text>
</defs>
<use href="#chars"></use>
`;

<defs><use> 组合如此强大的一个关键特性是,应用于 <use> 的属性和特性不会影响 <defs> 中被引用的元素。这使得开发者可以叠加 transform 等属性。

在下面的示例中,<defs> 中的 <text> 元素没有旋转。但是,我们可以对 <use> 应用旋转,而不会影响 <defs><text> 元素的布局。

const helloDefs = svg`
<defs>
<text id="chars">Hello defs!</text>
</defs>
<use
href="#chars"
transform="rotate(180, 0,0)">
</use>
`;

最后,<g> 是一个特殊元素,它会将自己的属性应用到所有子元素。当我们需要将某个属性分发给多个元素时,这非常有用。

在下面的示例中,<g> 元素将把它的 transform 应用到每个子元素。在这种情况下,<use> 元素将同时进行_旋转_和_平移_。

const helloGroups = svg`
<defs>
<text id="chars">Hello defs!</text>
</defs>
<g transform="translate(50, 50)">
<use
href="#chars"
transform="rotate(${currRotation}, 0,0)">
</use>
</g>
`;

实践

Permalink to "实践"

repeat-pattern 添加两个新的响应式属性:num-printsrotation-offset

num-prints 属性将影响图案基元中有多少个印迹。rotation-offset 属性将为图案基元提供初始旋转角度。

export class RepeatPattern extends LitElement {
@property({type: Number, attribute: "num-prints"}) numPrints = 7;
@property({
type: Number,
attribute: "rotation-offset",
}) rotationOffset = 0;
...
}
export class RepeatPattern extends LitElement {
static properties = {
chars: {type: String},
numPrints: {type: Number, attribute: 'num-prints'},
rotationOffset: {
type: Number,
attribute: 'rotation-offset',
},
};

constructor() {
super();
this.chars = 'lit';
this.numPrints = 7;
this.rotationOffset = 0;
}
...
}

createElement 中的 text 元素提供一个 id

const createElement = (
chars: string,
): SVGTemplateResult => svg`
<text
id="chars"
dominant-basline="hanging"
font-family="monospace"
font-size="24px">
${chars}
</text>`;
const createElement = (chars) => svg`
<text
id="chars"
dominant-basline="hanging"
font-family="monospace"
font-size="24px">
${chars}
</text>`;

render 中的 <defs> 内部调用 createElement

render() {
return html`
<svg>
<defs>
${createElement(this.chars)}
</defs>
...
</svg>
`;
}

创建一个名为 createMotif 的函数,用于生成一系列旋转的文本。 createMotif 可以接受两个参数:numPrints 表示元素将被打印的次数,以及一个可选的 offset 属性,表示初始旋转偏移量。 createMotif 应该根据要打印的 numPrints 计算旋转角度,并将旋转应用到引用 #chars<use> 元素上。

const createMotif = (
numPrints: number,
offset: number = 0
): SVGTemplateResult => {
const rotation = 360 / numPrints;

const prints = [];
let currRotation = offset;
for (let index = 0; index < numPrints; index++) {
currRotation += rotation;
prints.push(svg`
<use
href="#chars"
transform="rotate(${currRotation}, 0, 0)">
</use>
`);
}
};
const createMotif = (numPrints, offset = 0) => {
const rotation = 360 / numPrints;

const prints = [];
let currRotation = offset;
for (let index = 0; index < numPrints; index++) {
currRotation += rotation;
prints.push(svg`
<use
href="#chars"
transform="rotate(${currRotation}, 0, 0)">
</use>
`);
}
};

prints 列表包裹在一个 <g> 元素中。通过将 <g>transform 属性设置为 translate(50, 50),将组向右下方移动 50px,以使整个图案基元保持在画面内。这会将 <g> 中的 transform 应用到所有印迹上。返回这个组。

const createMotif = (numPrints: number, offset: number = 0): SVGTemplateResult => {
...
return svg`<g transform="translate(50, 50)">${prints}</g>`;
}
const createMotif = (numPrints, offset = 0) => {
...
return svg`<g transform="translate(50, 50)">${prints}</g>`;
}

最后,在 repeat-patternrender 函数中调用 createMotif

@customElement('repeat-pattern')
export class RepeatPattern extends LitElement {
...

render() {
return html`
<svg height="100%" width="100%">
<defs>
${createElement(this.chars)}
</defs>
${createMotif(
this.numPrints,
this.rotationOffset,
)}
</svg>
`;
}
}
export class RepeatPattern extends LitElement {
...

render() {
return html`
<svg height="100%" width="100%">
<defs>
${createElement(this.chars)}
</defs>
${createMotif(
this.numPrints,
this.rotationOffset,
)}
</svg>
`;
}
}

完成本节后,你将准备好学习如何使用裁剪路径和通过组来组合图形。

扩展练习

Permalink to "扩展练习"

Lit 的响应式属性也可以通过属性来设置。尝试在 index.html 中的 <repeat-pattern> HTML 元素上修改 charsrotation-offsetnum-prints 属性吧!