使用预定义图案pattern填充SVG元素

2018.12/SVG

SVG中的pattern元素,让预定义图形以固定间隔在x轴和y轴上重复或平铺,从而覆盖需要涂色或添加纹理的区域。

在defs中预定义pattern图案元素,在图形元素上使用属性fill="url(#id)"stroke="url(#id)"来引用填充或描边的图案。

DOM工具

操作SVG及其图形元素DOM的方法,包括选择、创建、设置属性等。

const tool = (root => {
const doc = root.document
const xlink = 'http://www.w3.org/1999/xlink'
const xmlns = 'http://www.w3.org/2000/svg'
const tool = {}

tool.select = (el, str) => {
if (!str) {
str = el
el = doc
}
return doc.querySelector(str)
}

tool.selectAll = (el, str) => {
if (!str) {
str = el
el = doc
}
return doc.querySelectorAll(str)
}

tool.attr = (el, attr) => {
for (let key in attr) {
const val = String(attr[key])
if (val) {
if (key.substring(0, 6) == 'xlink:') {
el.setAttributeNS(xlink, key.substring(6), val)
} else if (key.substring(0, 4) == "xml:") {
el.setAttributeNS(xmlns, key.substring(4), val)
} else {
el.setAttribute(key, val)
}
} else {
el.removeAttribute(key)
}
}
}

tool.create = el => doc.createElementNS(xmlns, el)

return tool
})(window || this)

纹理类

class LineTexture {
constructor(args) {
this.size = args.size || 20
this.stroke = args.stroke || '#a9aca5'
this.strokeWidth = args.strokeWidth || 2
this.background = args.background || ''
this.orientations = args.orientations || ['2/8']
this.svg = args.svg
this.id = this.random()
this.svg && this.init()
}

path(orientation) {
const s = this.size
switch (orientation) {
case '0/8':
return `M${s/4},0 l0,${s} M${3/4*s},0 l0,${s}`
case '1/8':
return `M${s/4},0 l${s/2},${s} M${-s/4},0 l${s/2},${s} M${s*3/4},0 l${s/2},${s}`
case '2/8':
// 省略...
}
}

url() {
return `url(#${this.id})`
}

init() {
const svg = this.svg.nodeType ? this.svg : tool.select(this.svg)
const defs = tool.select(svg, 'defs')
const pattern = tool.create('pattern')

tool.attr(pattern, {
id: this.id,
width: this.size,
height: this.size,
patternUnits: 'userSpaceOnUse'
})

if (this.background) {
const rect = tool.create('rect')
tool.attr(rect, {
width: this.size,
height: this.size,
fill: this.background
})
pattern.appendChild(rect)
}

this.orientations.forEach(o => {
const path = tool.create('path')
tool.attr(path, {
stroke: this.stroke,
'stroke-width': this.strokeWidth,
'stroke-linecap': 'square',
d: this.path(o)
})
pattern.appendChild(path)
})

defs.appendChild(pattern)
}

random() {
return Math.random().toString(36)
.replace(/[^a-z]+/g, '')
.slice(0, 5)
}
}

实现

选择SVG画布,设置其尺寸;找到全部path路径子元素;为每个path初始化纹理实例对象LineTexture,并设置纹理pattern的id引用填充。

const svg = tool.select('#tibet')
const width = Math.min(svg.parentNode.clientWidth, 410)
const height = width * 440 / 820
tool.attr(svg, {
width,
height
})
const pathList = tool.selectAll(svg, 'path')
const colors = ['#f2f6ed', '#f3f6ee', '#f3f7ef', '#f4f7f0', '#f5f8f1', '#f5f8f2', '#f6f9f3']

Array.prototype.forEach.call(pathList, (path, i) => {
const url = new LineTexture({
svg,
background: colors[i],
orientations: [`${i}/8`]
}).url()

tool.attr(path, {
fill: url
})
})

参考:
http://snapsvg.io/
https://riccardoscalco.it/textures/