master
yutent 2024-03-27 18:36:53 +08:00
parent 30c7d671a9
commit f5321e219a
3 changed files with 156 additions and 81 deletions

View File

@ -3,16 +3,34 @@
* @author yutent<yutent.io@gmail.com> * @author yutent<yutent.io@gmail.com>
* @date 2024/03/26 10:37:00 * @date 2024/03/26 10:37:00
*/ */
import {} from './lib/constants.js'
import { $, h } from './utils.js' import { $, uuid, h, preload, hyphen } from './utils.js'
export class Component { export class Component {
#node #node
type = null type
id
defs
constructor(el) { constructor(el) {
this.#node = el this.#node = el
this.type = el.tagName.toLowerCase()
let _type = el.tagName.toLowerCase()
if (_type !== 'defs') {
this.id = uuid(_type)
el.id = this.id
}
this.type = _type
if (_type === 'svg') {
let defs = this.select('defs')
if (!defs) {
defs = this.#create('defs')
}
this.defs = defs
}
} }
get node() { get node() {
@ -27,6 +45,10 @@ export class Component {
this.#node.textContent = val this.#node.textContent = val
} }
get children() {
return Array.from(this.#node.children)
}
#create(name, attr, child) { #create(name, attr, child) {
let node = h(name, attr, child) let node = h(name, attr, child)
this.#node.appendChild(node) this.#node.appendChild(node)
@ -53,15 +75,16 @@ export class Component {
let out = {} let out = {}
if (key) { if (key) {
let attrs = key
if (typeof key === 'string') { if (typeof key === 'string') {
if (val === void 0) { if (val === void 0) {
return node.getAttribute(key) return node.getAttribute(key)
} else { } else {
key = { [key]: val } attrs = { [key]: val }
} }
} }
h(node, key) h(node, attrs)
return this return this
} }
@ -73,8 +96,35 @@ export class Component {
return out return out
} }
move(x, y) {
this.attr({ x, y })
return this
}
size(width, height) {
this.attr({ width, height })
return this
}
transform(transform) {
this.attr({ transform })
return this
}
/* ------------------------------ */ /* ------------------------------ */
/**
* 引入defs中的组件
*/
use(id) {
if (id instanceof Component) {
id = id.id
} else if (id.startsWith('#')) {
id = id.slice(1)
}
return this.#create('use', { 'xlink:href': '#' + id })
}
g(...args) { g(...args) {
let el = this.#create('g') let el = this.#create('g')
if (args.length) { if (args.length) {
@ -92,26 +142,93 @@ export class Component {
return node return node
} }
symbol(vx, vy, vw, vh) {
return this.#create('symbol', { viewBox: [vx, vy, vw, vh].join(' ') })
}
rect(x, y, width, height) { rect(x, y, width, height) {
return this.#create('rect', { x, y, width, height }) return this.#create('rect', { x, y, width, height })
} }
/**
* 多边形, 传入多个坐标点
*/
polygon(...points) {
if (points.length) {
points = points.flatMap(p => p.join(',')).join(' ')
return this.#create('polygon', { points })
}
throw new Error('polygon() argument is undefined')
}
text(x = 0, y = 0, text = '') { text(text = '', x = 0, y = 0) {
return this.#create('text', { x, y }, text) return this.#create('text', { x, y }, text)
} }
// 使用foreignObject实现文本自动换行等排版 // 使用foreignObject实现文本自动换行等排版
autoText(x = 0, y = 0, width, height, text = '') { autoText(text = '', x = 0, y = 0, width, height) {
return this.#create('foreignObject', { x, y, width, height }, text) return this.#create('foreignObject', { x, y, width, height }, text)
} }
line(x1, y1, x2, y2, attr = {}) { line(x1, y1, x2, y2, attr = { stroke: '#000', strokeWidth: 1 }) {
return this.#create('line', { x1, y1, x2, y2, ...attr }) return this.#create('line', { x1, y1, x2, y2, ...attr })
} }
/**
* 拆线, 传入多组坐标
*/
polyline(...points) { polyline(...points) {
if (points.length) {
points = points.flatMap(p => p.join(',')).join(' ')
return this.#create('polyline', { points }) return this.#create('polyline', { points })
} }
throw new Error('polyline() argument is undefined')
}
circle(cx = 0, cy = 0, r = 1) {
return this.#create('circle', { cx, cy, r })
}
/**
* 椭圆
* @param cx, cy 圆心坐标
* @param rx, ry XY轴半径
*/
ellipse(cx, cy, rx, ry) {
return this.#create('ellipse', { cx, cy, rx, ry })
}
image(href, { x, y, width, height } = {}) {
let el = this.#create('image', {
preserveAspectRatio: 'none',
href,
x,
y,
width,
height
})
if (width === void 0 || height === void 0) {
preload(href, function (img) {
h(el.node, {
width: img.naturalWidth,
height: img.naturalHeight
})
})
}
return el
}
pattern(x, y, width, height, viewBox = []) {
let props = { patternUnits: 'userSpaceOnUse', x, y, width, height }
if (viewBox.length) {
props.viewBox = viewBox.join(' ')
} else {
props.viewBox = [x, y, width, height].join(' ')
}
return this.#create('pattern', props)
}
path(d) { path(d) {
return this.#create('path', { d }) return this.#create('path', { d })

View File

@ -10,7 +10,14 @@ export const xlink = 'http://www.w3.org/1999/xlink'
export const xmlns = 'http://www.w3.org/2000/svg' export const xmlns = 'http://www.w3.org/2000/svg'
export const xhtmlns = 'http://www.w3.org/1999/xhtml' export const xhtmlns = 'http://www.w3.org/1999/xhtml'
export const HTML_TAGS = ['div', 'span', 'p'] export const HTML_TAGS = { div: 1, span: 1, p: 1 }
export const SPEC_ATTR = {
viewBox: 1,
preserveAspectRatio: 1,
patternUnits: 1,
patternTransform: 1,
patternContentUnits: 1
}
export const CSS_ATTR = { export const CSS_ATTR = {
'alignment-baseline': 1, 'alignment-baseline': 1,

View File

@ -4,7 +4,16 @@
* @date 2024/03/06 09:55:31 * @date 2024/03/06 09:55:31
*/ */
import { xlink, xmlns, xhtmlns, HTML_TAGS, doc, win } from './lib/constants.js' import {
xlink,
xmlns,
xhtmlns,
HTML_TAGS,
SPEC_ATTR,
CSS_ATTR,
doc,
win
} from './lib/constants.js'
export function uuid(prefix = '') { export function uuid(prefix = '') {
return prefix + Math.random().toString(16).slice(-8) return prefix + Math.random().toString(16).slice(-8)
@ -47,7 +56,7 @@ export function $$(selector, container) {
export function h(el, props = null, children) { export function h(el, props = null, children) {
if (typeof el === 'string') { if (typeof el === 'string') {
el = doc.createElementNS(HTML_TAGS.includes(el) ? xhtmlns : xmlns, el) el = doc.createElementNS(HTML_TAGS[el] ? xhtmlns : xmlns, el)
} }
if (props) { if (props) {
@ -56,17 +65,17 @@ export function h(el, props = null, children) {
if (val === void 0) { if (val === void 0) {
continue continue
} }
if (!SPEC_ATTR[key]) {
key = hyphen(key) key = hyphen(key)
}
if (val === null) { if (val === null) {
el.removeAttribute(key) el.removeAttribute(key)
} else { } else {
if (key.slice(0, 6) == 'xlink:') { if (CSS_ATTR[key]) {
el.setAttributeNS(xlink, key.slice(6), val) el.style.cssText += `${key}:${props[key]};`
} else if (key.slice(0, 4) == 'xml:') { continue
el.setAttributeNS(xmlns, key.slice(4), val)
} else {
el.setAttribute(key, val)
} }
el.setAttribute(key, val)
} }
} }
} }
@ -77,11 +86,7 @@ export function h(el, props = null, children) {
} }
if (el.tagName.toLowerCase() === 'foreignobject') { if (el.tagName.toLowerCase() === 'foreignobject') {
let f = h('div', { xmlns: 'http://www.w3.org/1999/xhtml' }) let f = h('div', null, children)
let p = h('p', null, children)
f.appendChild(p)
h(el, { xmlns })
el.appendChild(f) el.appendChild(f)
} else { } else {
el.appendChild(doc.createTextNode(children)) el.appendChild(doc.createTextNode(children))
@ -106,65 +111,11 @@ export function clone(obj) {
return res return res
} }
export function preload(src, f) { export function preload(src, callback) {
let img = new Image() let img = new Image()
img.onload = function () { img.onload = function () {
f.call(img) callback(img)
} }
img.src = src img.src = src
} }
function repush(arr, item) {
let l = arr.length - 1 // 要减1, 最后如果本身在最后, 不用变
for (let i = 0; i < l; i++) {
if (arr[i] === item) {
;[arr[i], arr[l]] = [arr[l], arr[i]]
break
}
}
}
export function cacher(fn, scope, postprocessor) {
function newf(...args) {
let key = args.join('\u2400'),
cache = (newf.cache = newf.cache || {}),
count = (newf.count = newf.count || [])
if (cache.hasOwnProperty(key)) {
repush(count, key)
return postprocessor ? postprocessor(cache[key]) : cache[key]
}
count.length >= 1e3 && delete cache[count.shift()]
count.push(key)
cache[key] = fn.apply(scope, args)
return postprocessor ? postprocessor(cache[key]) : cache[key]
}
return newf
}
export function jsonFiller(root, o) {
for (let i = 0, ii = root.length; i < ii; i++) {
let item = {
type: root[i].type,
attr: root[i].attr()
},
children = root[i].children()
o.push(item)
if (children.length) {
jsonFiller(children, (item.childNodes = []))
}
}
}
export function extend(origin, target) {
let methods = Object.getOwnPropertyNames(target).filter(
n => n !== 'constructor'
)
for (let k of methods) {
Object.defineProperty(origin, k, { value: target[k] })
}
}
export function url(id) {
return "url('#" + id + "')"
}