本节教程介绍 SVG 中组合的基础知识,包括如何:
<defs> 定义可复用的结构<use> 克隆 <defs> 中的结构<use> 上的属性修改克隆的结构<g>(组)将属性应用于多个结构在图案制作中,图案基元是由两个或更多元素组成的排列。本演示的图案基元将是一系列围绕某点旋转的文本。<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>`; 为 repeat-pattern 添加两个新的响应式属性:num-prints 和 rotation-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-pattern 的 render 函数中调用 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> `; }} 完成本节后,你将准备好学习如何使用裁剪路径和通过组来组合图形。
Lit 的响应式属性也可以通过属性来设置。尝试在 index.html 中的 <repeat-pattern> HTML 元素上修改 chars、rotation-offset 和 num-prints 属性吧!