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>
* @date 2024/03/26 10:37:00
*/
import { $, h } from './utils.js'
import {} from './lib/constants.js'
import { $, uuid, h, preload, hyphen } from './utils.js'
export class Component {
#node
type = null
type
id
defs
constructor(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() {
@ -27,6 +45,10 @@ export class Component {
this.#node.textContent = val
}
get children() {
return Array.from(this.#node.children)
}
#create(name, attr, child) {
let node = h(name, attr, child)
this.#node.appendChild(node)
@ -53,15 +75,16 @@ export class Component {
let out = {}
if (key) {
let attrs = key
if (typeof key === 'string') {
if (val === void 0) {
return node.getAttribute(key)
} else {
key = { [key]: val }
attrs = { [key]: val }
}
}
h(node, key)
h(node, attrs)
return this
}
@ -73,8 +96,35 @@ export class Component {
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) {
let el = this.#create('g')
if (args.length) {
@ -92,25 +142,92 @@ export class Component {
return node
}
symbol(vx, vy, vw, vh) {
return this.#create('symbol', { viewBox: [vx, vy, vw, vh].join(' ') })
}
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)
}
// 使用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)
}
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 })
}
/**
* 拆线, 传入多组坐标
*/
polyline(...points) {
return this.#create('polyline', { points })
if (points.length) {
points = points.flatMap(p => p.join(',')).join(' ')
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) {

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 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 = {
'alignment-baseline': 1,

View File

@ -4,7 +4,16 @@
* @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 = '') {
return prefix + Math.random().toString(16).slice(-8)
@ -47,7 +56,7 @@ export function $$(selector, container) {
export function h(el, props = null, children) {
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) {
@ -56,17 +65,17 @@ export function h(el, props = null, children) {
if (val === void 0) {
continue
}
key = hyphen(key)
if (!SPEC_ATTR[key]) {
key = hyphen(key)
}
if (val === null) {
el.removeAttribute(key)
} else {
if (key.slice(0, 6) == 'xlink:') {
el.setAttributeNS(xlink, key.slice(6), val)
} else if (key.slice(0, 4) == 'xml:') {
el.setAttributeNS(xmlns, key.slice(4), val)
} else {
el.setAttribute(key, val)
if (CSS_ATTR[key]) {
el.style.cssText += `${key}:${props[key]};`
continue
}
el.setAttribute(key, val)
}
}
}
@ -77,11 +86,7 @@ export function h(el, props = null, children) {
}
if (el.tagName.toLowerCase() === 'foreignobject') {
let f = h('div', { xmlns: 'http://www.w3.org/1999/xhtml' })
let p = h('p', null, children)
f.appendChild(p)
h(el, { xmlns })
let f = h('div', null, children)
el.appendChild(f)
} else {
el.appendChild(doc.createTextNode(children))
@ -106,65 +111,11 @@ export function clone(obj) {
return res
}
export function preload(src, f) {
export function preload(src, callback) {
let img = new Image()
img.onload = function () {
f.call(img)
callback(img)
}
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 + "')"
}