diff --git a/src/elem.js b/src/elem.js index 538ac12..9551803 100644 --- a/src/elem.js +++ b/src/elem.js @@ -3,16 +3,34 @@ * @author yutent * @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 X、Y轴半径 + */ + 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) { diff --git a/src/lib/constants.js b/src/lib/constants.js index 1340994..4f240b6 100644 --- a/src/lib/constants.js +++ b/src/lib/constants.js @@ -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, diff --git a/src/utils.js b/src/utils.js index 13cd930..aaea146 100644 --- a/src/utils.js +++ b/src/utils.js @@ -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 + "')" -}