master
yutent 2024-03-26 18:30:07 +08:00
parent d6344dde16
commit 00889573db
5 changed files with 514 additions and 0 deletions

109
src/elem.js Normal file
View File

@ -0,0 +1,109 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2024/03/26 10:37:00
*/
import { $, h } from './utils.js'
export class Component {
#node
type = null
constructor(el) {
this.#node = el
this.type = el.tagName.toLowerCase()
}
get node() {
return this.#node
}
get textContent() {
return this.#node.textContent
}
set textContent(val) {
this.#node.textContent = val
}
#create(name, attr, child) {
let node = h(name, attr, child)
this.#node.appendChild(node)
return new Component(node)
}
append(...elems) {
for (let el of elems) {
this.#node.appendChild(el instanceof Component ? el.node : el)
}
return this
}
select(selector) {
let node = $(selector, this.#node)
if (node) {
return new Component(node)
}
return null
}
attr(key, val) {
let node = this.node
let out = {}
if (key) {
if (typeof key === 'string') {
if (val === void 0) {
return node.getAttribute(key)
} else {
key = { [key]: val }
}
}
h(node, key)
return this
}
// 无任何参数时, 返回节点所有的属性
for (let it of Array.from(node.attributes)) {
out[it.nodeName] = it.nodeValue
}
return out
}
g(...args) {
let el = this.#create('g')
if (args.length) {
el.append(...args)
}
return el
}
style(content) {
let node = this.select('style')
if (node === null) {
node = this.#create('style')
}
node.textContent += content
return node
}
rect(x, y, width, height) {
return this.#create('rect', { x, y, width, height })
}
text(x = 0, y = 0, text = '') {
return this.#create('text', { x, y }, text)
}
// 使用foreignObject实现文本自动换行等排版
autoText(x = 0, y = 0, width, height, text = '') {
return this.#create('foreignObject', { x, y, width, height }, text)
}
path(d) {
return this.#create('path', { d })
}
}

23
src/index.js Normal file
View File

@ -0,0 +1,23 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2024/03/26 10:07:25
*/
import { $, h, type } from './utils.js'
import { xmlns, doc, win } from './lib/constants.js'
import { Component } from './elem.js'
export function createSvg(el) {
if (el) {
if (type(el) === 'string') {
el = $(el)
}
h(el, { xmlns })
} else {
el = h('svg', { xmlns })
}
return new Component(el)
}

135
src/lib/color.js Normal file
View File

@ -0,0 +1,135 @@
/**
* {颜色格式转换}
* @author yutent<yutent.io@gmail.com>
* @date 2024/03/26 10:07:01
*/
// H: 色相, S: 饱和度, B/V: 亮度
export function hsb2rgb(hsb) {
let h = hsb.h
let s = Math.round((hsb.s * 255) / 100)
let v = Math.round((hsb.b * 255) / 100)
let r = 0
let g = 0
let b = 0
if (s === 0) {
r = g = b = v
} else {
let t1 = v
let t2 = ((255 - s) * v) / 255
let t3 = ((t1 - t2) * (h % 60)) / 60
//
if (h === 360) {
h = 0
}
if (h < 60) {
r = t1
g = t2 + t3
b = t2
} else if (h < 120) {
r = t1 - t3
g = t1
b = t2
} else if (h < 180) {
r = t2
g = t1
b = t2 + t3
} else if (h < 240) {
r = t2
g = t1 - t3
b = t1
} else if (h < 300) {
r = t2 + t3
g = t2
b = t1
} else if (h < 360) {
r = t1
g = t2
b = t1 - t3
}
}
r = Math.round(r)
g = Math.round(g)
b = Math.round(b)
return { r, g, b }
}
export function rgb2hex({ r, g, b }, a) {
let hex = [r, g, b].map(it => it.toString(16).padStart(2, '0')).join('')
if (a !== void 0) {
hex += (~~((a / 100) * 255)).toString(16)
}
return hex
}
export function hex2rgb(hex) {
let r, g, b, a
hex = hex.replace(/^#/, '')
switch (hex.length) {
case 3:
case 4:
r = hex[0].repeat(2)
g = hex[1].repeat(2)
b = hex[2].repeat(2)
a = (hex[3] || 'f').repeat(2)
break
case 6:
case 8:
r = hex.slice(0, 2)
g = hex.slice(2, 4)
b = hex.slice(4, 6)
a = hex.slice(6, 8) || 'ff'
break
}
r = parseInt(r, 16)
g = parseInt(g, 16)
b = parseInt(b, 16)
a = ~~((parseInt(a, 16) * 100) / 255)
return { r, g, b, a }
}
export function rgb2hsb({ r, g, b }) {
let hsb = { h: 0, s: 0, b: 0 }
let max = Math.max(r, g, b)
let min = Math.min(r, g, b)
let delta = max - min
hsb.b = max
hsb.s = max === 0 ? 0 : (delta * 255) / max
if (hsb.s === 0) {
hsb.h = -1
} else {
if (r === max) {
hsb.h = (g - b) / delta
} else if (g === max) {
hsb.h = 2 + (b - r) / delta
} else {
hsb.h = 4 + (r - g) / delta
}
}
hsb.h *= 60
if (hsb.h < 0) {
hsb.h += 360
}
hsb.s *= 100 / 255
hsb.b *= 100 / 255
return hsb
}
export function hex2hsb(hex) {
return rgb2hsb(hex2rgb(hex))
}

77
src/lib/constants.js Normal file
View File

@ -0,0 +1,77 @@
/**
* {一些常量}
* @author yutent<yutent.io@gmail.com>
* @date 2024/03/06 16:25:01
*/
export const doc = document
export const win = window
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 CSS_ATTR = {
'alignment-baseline': 1,
'baseline-shift': 1,
clip: 1,
'clip-path': 1,
'clip-rule': 1,
color: 1,
'color-interpolation': 1,
'color-interpolation-filters': 1,
'color-profile': 1,
'color-rendering': 1,
cursor: 1,
direction: 1,
display: 1,
'dominant-baseline': 1,
'enable-background': 1,
fill: 1,
'fill-opacity': 1,
'fill-rule': 1,
filter: 1,
'flood-color': 1,
'flood-opacity': 1,
font: 1,
'font-family': 1,
'font-size': 1,
'font-size-adjust': 1,
'font-stretch': 1,
'font-style': 1,
'font-variant': 1,
'font-weight': 1,
'glyph-orientation-horizontal': 1,
'glyph-orientation-vertical': 1,
'image-rendering': 1,
kerning: 1,
'letter-spacing': 1,
'lighting-color': 1,
marker: 1,
'marker-end': 1,
'marker-mid': 1,
'marker-start': 1,
mask: 1,
opacity: 1,
overflow: 1,
'pointer-events': 1,
'shape-rendering': 1,
'stop-color': 1,
'stop-opacity': 1,
stroke: 1,
'stroke-dasharray': 1,
'stroke-dashoffset': 1,
'stroke-linecap': 1,
'stroke-linejoin': 1,
'stroke-miterlimit': 1,
'stroke-opacity': 1,
'stroke-width': 1,
'text-anchor': 1,
'text-decoration': 1,
'text-rendering': 1,
'unicode-bidi': 1,
visibility: 1,
'word-spacing': 1,
'writing-mode': 1
}

170
src/utils.js Normal file
View File

@ -0,0 +1,170 @@
/**
* {}
* @author yutent<yutent.io@gmail.com>
* @date 2024/03/06 09:55:31
*/
import { xlink, xmlns, xhtmlns, HTML_TAGS, doc, win } from './lib/constants.js'
export function uuid(prefix = '') {
return prefix + Math.random().toString(16).slice(-8)
}
export function noop() {}
//驼峰转换为连字符线风格
export function hyphen(target) {
return target.replace(/([a-z\d])([A-Z]+)/g, '$1-$2').toLowerCase()
}
//连字符转换为驼峰风格
export function camelize(target) {
//提前判断,提高效率
if (target.indexOf('-') < 0) {
return target
}
return target.replace(/\-([a-z])/g, (m, s) => s.toUpperCase())
}
export function type(o) {
if (o === null) {
return null
}
return Object.prototype.toString.call(o).slice(8, -1).toLowerCase()
}
export function $(selector, container, multi) {
let fn = multi ? 'querySelectorAll' : 'querySelector'
if (container) {
return container[fn](selector)
}
return document.body[fn](selector)
}
export function $$(selector, container) {
return $(selector, container, true)
}
export function h(el, props = null, children) {
if (typeof el === 'string') {
el = doc.createElementNS(HTML_TAGS.includes(el) ? xhtmlns : xmlns, el)
}
if (props) {
for (let key in props) {
let val = props[key]
if (val === void 0) {
continue
}
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 (children) {
if (Array.isArray(children)) {
children = children.join('')
}
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 })
el.appendChild(f)
} else {
el.appendChild(doc.createTextNode(children))
}
el.normalize()
}
return el
}
window.h = h
export function clone(obj) {
if (typeof obj == 'function' || Object(obj) !== obj) {
return obj
}
let res = new obj.constructor()
for (let key in obj)
if (obj.hasOwnProperty(key)) {
res[key] = clone(obj[key])
}
return res
}
export function preload(src, f) {
let img = new Image()
img.onload = function () {
f.call(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 + "')"
}