doc-proxy
v1.0.4
Published
DocProxy,声明式构建和配置html文档
Maintainers
Readme
DocProxy
简介
DocProxy是一个返璞归真的html文档构建和处理库。 提供声明式构建、链式配置、组件化、样式隔离、WebComponents集成五大核心功能。 DocProxy不是MVVM框架,无脚手架,无虚拟DOM,无数据响应式,专注于高性能和对DOM节点精细控制的场景。
安装
- 原生脚本引入
<script src="doc-proxy.js"></script>- npm安装
npm i doc-proxy- 模块化导入
import {$$} from "doc-proxy"快速上手
<!DOCTYPE html>
<html lang="en">
<head>
<script src="doc-proxy.js"></script>
</head>
<body>
<div id="app"></div>
<script>
//数据
const data = [
{ id: 1, name: "张向明" },
{ id: 2, name: "蒋海艳" }
]
//标签代理
const { table, thead, tbody, tr, th, td } = $$
//构建节点
const node = table(
{ id: "table1", className: "list container", draggable: true },
thead(
tr(
th("学号"),
th("姓名")
)
),
tbody(
data.map( item => tr(
td(item.id),
td(item.name)
))
)
)
//挂载节点
$$("#app").render(node)
</script>
</body>
</html>核心概念
- 【节点代理】节点代理是DOM节点的代理,通过节点代理可以链式配置DOM节点的属性、样式、事件。
- 【真节点】真节点是指【节点代理】背后的真实DOM节点,每个节点代理一定会有对应的【真节点】。
- 【标签代理】标签代理是用来构建【节点代理】的函数,调用标签代理时,可以传入【节点代理】数组,因此可以递归构建出一棵DOM树。
- 【规则属性】规则属性是一个JSON对象,其键值会按照约定的方式传递到【真节点】的对象属性、标签属性、样式属性、事件。
- 【属性代理】属性代理是指通过【节点代理】的方法来访问【真节点】的属性(包括对象属性、标签属性、样式属性、事件)。如:nodeProxy.prop("title","提示")用来配置【真节点】的title属性。
- 【动态属性代理】动态属性代理是指在【节点代理】上的调用函数时,函数名就是【真节点】的属性名,如nodeProxy.title("提示"),【节点代理】的title函数对应的是真节点的title属性。
//解构【标签代理】
const { ul, li } = $$
//构建【节点代理】
const node = ul(
//【规则属性】的键值将传递到ul上
{ id:"ul1", className:"list container", draggable:true },
//子节点
li("项目1"),
li("项目2").attr("title","项目2"), //attr函数是一个通用的【属性代理】
li("项目3").title("项目3") //title函数是一个【动态属性代理】
)
//挂载节点
$$("#app").render(node)
标签代理和节点代理
【标签代理】是一种用于声明式构建DOM树的函数, 其函数名对应的是你想要构建的DOM元素的标签名。 标签代理的返回值是一个【节点代理】, 用于链式配置其代理的真节点的属性、样式、事件。 你可以从全局对象DocProxy(或别名$$)上解构出所有的标签代理。 标签代理可接收如下类型的参数:
- 【JSON】:为当前节点配置属性
- 【节点代理】:插入子节点
- 【DOM节点】:插入子节点
- 【字符串】:插入文本子节点
- 【数组】:自动展开,展开后根据元素本身的类型递归追加到上级节点
- 【函数】:自动运行,并根据返回值的类型递归追加到上级节点
//用标准语法从$$解构出标签代理
const { ul, li } = $$
//构建节点
const node = ul(
{ id:"ul1", className:"list" }, //JSON参数,表示为ul配置属性
li("项目1"), //节点代理参数,表示将LI插入UL
li("项目2"), //"项目2"是个字符串,表示配置LI的文本内容
[li("项目3"),li("项目4")], //数组将自动展开
()=>li("项目5"), //函数将自动运行,并将返回的LI插入到UL
()=>[li("项目6", li("项目7"))] //函数的返回值是一个数组,将自动展开并插入到UL
)
//挂载节点
$$("#app").render(node)
规则属性
标签代理可接收JSON类型的参数, 用于配置本节点的对象属性、标签属性、样式属性、事件, 我们称这个JSON对象为【规则属性】。规则属性的key约定如下:
- 首字母是下划线:配置标签属性,属性名将去除首个下划线,其余下滑先将转换成中划线;
- 首字母是美元符号:配置样式属性,属性名将去除首个美元符号,其余部分使用小驼峰命名;
- 首字母为on:配置事件;
- 其他情况:配置对象属性;
- 如果标签属性和样式属性的value是一个函数,则以函数的返回值为属性的值,函数中的this指向DOM节点。
const { ul, li } = $$
const node = ul(
//【规则属性】
{
id:"ul1", //对象属性id
_title:"提示文字", //标签属性title,属性名去除第一个下划线
_custom_attribute:"子定义的标签属性", //标签属性,属性名去除第一个下划线,其余下划线将转换成中划线
_draggable:()=>"true", //标签属性的value是函数,则以返回值为属性值
$border:"1px solid gray", //样式属性,属性名去除第一个美元符号
$backgroundColor:"lightgray", //样式属性,除第一美元符号外,其余部分用小驼峰命名
onclick:event=>{ //配置事件
console.log(event.target)
}
},
//配置子节点
li("项目1"),
li("项目2"),
li("项目3")
)
$$("#app").render(node)
属性前置范式
【标签代理】可以接收JSON类型的参数, 用来配置节点的属性, 其返回值是一个【节点代理】。 节点代理是一个拥有对象和函数双重身份的Proxy, 函数身份用来配置子节点。 如此便形成了一个比较优美的范式:tag(attribute)(...chidren), 我们称其为【属性前置范式】。
const { ul, li } = $$
//ul(obj)返回了一个节点代理,节点代理本身也是一个函数,这个函数用来追加子节点
const node = ul({id:"ul1",className:"class1 class2",title:"提示"})(
//配置子节点
li("项目1"),
li("项目2"),
li("项目3")
)
$$("#app").render(node)节点语法糖
如果要构建的DOM节点是一个仅包含文本内容的节点(如script和style),可以利用模板字符语法来减少一对括号。
const { div, style, ul, li } = $$
const node = div(
style`
li{
color:green;
font-size:20px;
}
`,
ul(
li`项目1`,
li`项目2`,
li`项目3`
)
)
$$("#app").render(node)
加解代理
除了通过标签代理构建节点代理外, 还可以通过$$函数将真节点包装成节点代理, 这一过程称为【加代理】。 通过节点代理的下标0, 可以得到真节点, 这一过程称为【解代理】。
//将一个真节点包装为节点代理,这一过程称为【加代理】
let nodeProxy1 = $$(document.querySelector("#app"))
//通过查询表达式简化加代理
let nodeProxy2 = $$("#app")
//通过节点代理的下标0,访问真节点,这一过程称为【解代理】
let node1 = nodeProxy1[0]
let node2 = nodeProxy2[0]
//两个代理是不相等的
console.log(nodeProxy1===nodeProxy2)
//两个代理代理了同一个真节点
console.log(node1 === node1)
属性代理
节点代理上提供了大量可链式调用的函数, 称为【属性代理】。 属性代理用来配置和获取节点的对象属性、标签属性、样式属性、事件。
- 【prop(name,value)】配置对象属性
- 【prop(name)】获取对象属性值
- 【prop(obj)】将obj的键值配置到节点对应的对象属性上
- 【arrt(name,value)】配置标签属性
- 【arrt(name,false)】移除标签属性
- 【arrt(name,fn)】以fn的返回值为value递归调用attr(name,value)
- 【attr(name)】获取标签属性值
- 【attr(obj)】将obj的键值配置到节点对应的标签属性上
- 【css(name,value)】配置样式属性
- 【css(name,fn)】以fn的返回值为value递归调用css(name,value)
- 【css(name)】获取样式属性值
- 【css(obj)】将obj的键值配置到节点对应的样式属性上
- 【class(class1)】添加class1
- 【class([class1,calss2])】添加class1和class2
- 【class(class1,false)】移除class1
- 【class(fn)】将fn的返回值添加到className
- 【class(class1,fn)】如果fn的返回值是false则移除class1,否则添加class1
- 【on(name,fn,option)】配置事件
- 特别注意:attr的第二个参数如何为false,会删除该标签属性,如果要将标签属性设置为false请使用字符串"false"
$$("#app") //加代理
.prop("innerHTML","内容") //配置对象属性
.attr("draggable","true") //配置标签属性
.class("class1") //配置class
.class("class2 class3 class4") //添加多个class
.class("class3 class4",false) //移除class
.css("display","block") //配置样式
.on("click",()=>console.log("单击")) //配置事件
.attr("title",function(){ //attr和css两个链函数的第二个参数可以是函数,函数的返回值就是属性值
let title = this.className ; //this指向当前真节点
return title;
})
//当prop、attr、css三个函数只有一个参数时,用来获取属性值
const node = $$("#app")
console.log(node.prop("innerHTML"))
console.log(node.attr("draggable"))
console.log(node.css("display"))
动态属性代理
可以通过【属性代理】配置节点的对象属性、标签属性、样式属性、事件。 除此之外DocProxy还提供了一套按函数名来配置属性的动态函数, 由于这套函数是完全由你的意图动态生成的, 因此我们称为【动态属性代理】。 动态属性代理的函数名约定如下:
- 以_开始的动态链函数用来配置节点的标签属性;
- 以$开始的动态链函数用来配置节点的样式;
- 以on开始的动态链函数用来配置节点的事件;
- 注意on与动态onxxx的区别,on使用的是addEventListener,而动态的onxxx使用的是属性赋值;
- 其他动态链函数用来配置节点的对象属性。
除了使用函数调用语法配置节点的属性外, 也可以直接使用等号赋值语法为这些属性赋值, 赋值语法同样遵守上述命名规则。 如nodeProxy.innerHTML("内容")与nodeProxy.innerHTML="内容"是同样的效果, 采用函数调用语法时,返回的是节点代理本身,因此支持链式写法。
$$("#app")
//配置对象属性,innerHTML既是函数名,也是对象属性名,函数的参数是对象属性值
.innerHTML("当前时间:"+new Date())
//配置标签属性,以下划线开始
._title("提示文字")
//配置样式,美元符号+小驼峰命名
.$padding(20).$backgroundColor("lightgray")
//以属性赋值的方式配置click事件
.onclick(()=>console.log("onclick"))
//以addEventListener方式配置事件
.on(
"click",
()=>console.log("addEventListener"),
{once:true}
)
//标签属性代理和样式属性代理的参数可以是一个函数,函数的返回值就是属性值
._draggable(function(){
let time = new Date().getTime()
return `\${time%2===0}`
});
//当动态属性代理无参时,将返回对应的属性值
const node = $$("#app");
console.log(node.innerHTML())
含值属性
对象的属性和函数可以用点号访问(obj.property), 也可以用中括号访问(obj["property"]), 用中括号访问可以脱离变量命名规则。 于是便为节点代理的【含值属性】打开了大门。 所谓含值属性是指将属性名和属性值整体打包成一个字符串, 然后用中括号表达式去访问这个字符串, 框架会拦截这个字符串, 将其解析成属性名和属性值。 节点代理包含如下七种含值属性:
- nodeProxy["#myid"] 将节点的id配置为myid
- nodeProxy[".myclass"] 将myclass追加到节点的className
- nodeProxy["title=提示"] 等号左边是对象属性名,右边是属性值
- nodeProxy["_draggable=true"] 等号左边是标签属性名(丢弃首个下划线),右边是属性值
- nodeProxy["$color=red"] 等号左边是样式属性名(丢弃首个美元号),右边是样式值
- nodeProxy["onclick==event=>console.log(event.target)"] 双等号左边是事件名含on,右边是事件处理程序
- nodeProxy["customPropery=={a:'aa',b:1}"] 双等号左边是属性名,右边是一个对象
$$(".app")
["#app"] //用#号配置id
[".class1"] //用.号追加className
["title=提示"] //等号左边是对象属性名,等号右边是属性值
["_draggable=true"] //等号左边是标签属性名(不含首个下划线),等号右边是属性值
["$color=red"] //等号左边是样式属性名(不含首个美元符),等号右边是样式值
["p1=1"] //使用单等号时,p1的值是字符串""1
["p2==1"] //使用双等号时,p2的值是数字1
console.log($$(".app")[0].p1.constructor)
console.log($$(".app")[0].p2.constructor)
const clickHandle = function(event){console.log(this)}
//通常我们会搭配【属性前置范式】
const { ul, li } = $$
$$(".app")["onclick==event=>console.log(this)"]( //框架会使用new Function(内容)将双等号后面的内容生成一个新的函数,并不推荐这种方式
ul.onclick(clickHandle)( //事件的配置建议使用属性代理,其他简单属性才使用含值属性
li("项目1"),
li("项目2")
)
)
标签代理含值属性
前面的章节我们已经学习到【节点代理】具备函数和对象双重身份。 本章节我们学习【标签代理】的函数和对象双重身份。 之前的章节我们一直将【标签代理】当函数使用,用于追加子节点, 其实【标签代理】也是一个对象,是对象就可以有自己的方法。
调用【标签代理】的方法,实质上是调用其构建的【节点代理】的同名的【属性代理】。 如:
//此时将div当对象使用,调用div的id方法,将返回一个id为mydiv的节点代理
div.id("mydiv")上例中已经创建了一个【节点代理】,后续可以链式的调用【节点代理】的其他方法,如:
//此处的id是【标签代理】的方法,会生成一个【节点代理】,title是这个【节点代理】的方法
div.id("mydiv").title("提示")上例中我们最终得到了一个id为"mydiv",title为"提示"的【节点代理】,而节点代理又可以当函数使用,于是便形成了如下经典的【属性前置】范式:
div.id("mydiv").title("提示")(
div("子节点1"),
div("子节点2")
)此外,我们还学习过节点代理的【含值属性】,标签代理同样支持【含值属性】,请看如下经典范式:
const { ul, li } = $$
const node = ul["#container"][".list"]["_style=background-color:yellow;color:red;"](
li["#item1"][".item"]("项目1"),
li["#item2"][".item"]("项目2"),
li["#item3"][".item"]("项目3")
)
$$("#app").render(node)
插入html字符串
虽然提倡用DocProxy声明式构建DOM树, 但也难免需要在节点上直接插入一个待解析的html字符串, 特别是在webpack等工程化环境中。 此时可以使用【节点代理】的html方法。
$$("#app").html(`
<h3>营利项目</h3>
<ul>
<li>项目1</li>
<li>项目2</li>
<li>项目3</li>
</ul>
`)事件处理
DocProxy的事件处理有两种方式:
1. 将事件当属性配置
nodeProxy
.onmousedown(event=>console.log("鼠标按下"))
.onmouseup(event=>console.log("鼠标松开"))2. 用on(type,fn,option)配置事件
【节点代理】的on函数是原生addEventListener的加强版, 会在内部生成一个新的事件处理函数, 由于该内部事件处理函数对外不可见, 因此无法通过removeEventListener移除。 需要使用AbortController的abort方法来移除该事件处理函数。
const controller = new AbortController()
$$("#text").on(
"keydown", //事件类型
function(event){ //事件处理函数
console.log(event.key)
},
{ //事件行为配置,每一项默认值为false
capture: false, //true表示只在捕获节点处理
once: false, //true表示只触发一次
passive: false, //true表示阻止调用event.preventDefault()
signal: controller.signal, //将来调用controller的abort函数时将移除事件
prevent: true, //true表示阻止默认行为
stop: true, //true表示阻止冒泡
self: true, //true表示只在本节点上触发,子节点不触发
letf: true, //true表示只在按下鼠标左键时才触发事件
right: false, //true表示只在按下鼠标右键时才触发事件
ctrl: true, //true表示只在按下了ctrl键不松才触发事件
alt: false, //true表示只在按下了alt键不松才触发事件
shift: false, //true表示只在按下了shift键不松才触发事件
meta: false, //true表示只在按下了meta键不松才触发事件
debounce: 200 //200毫秒防抖
}
)方法代理
节点代理的首字母大写的方法称为【方法代理】, 调用方法代理时,内部会先将首字母小写,然后调用真节点的同名方法。 特别注意【方法代理】的返回值是当前【节点代理】,而不是真节点的方法返回值。
//链式调用真节点的setAttribute方法
$$("#app")
.SetAttribute("title","提示文字")
.SetAttribute("draggable",true)属性后置范式
之前的章节已经介绍了【属性前置范式】和【属性代理】。 本章节介绍另一种范式。 当节点没有子节点时, 可以先配置节点的文本内容, 然后在利用属性代理链式配置节点的属性, 如此便形成一种范式:tag(content).property1(value).property2(value), 我们称其为【属性后置范式】。 并没有强制要求使用哪种范式, 两种范式搭配使用, 可以使DOM树的构建过程更美观清晰。 作范式抉择时建议从以下几点考虑:层次分明、尽量扁平、避免头重脚轻、整齐划一。
const { ul, li } = $$
//【属性前置范式】ul(obj)返回了一个节点代理,节点代理本身也是一个函数,这个函数用来配置子节点
const node = ul({ id:"ul1", className:"class1 class2", title:"提示" })(
//【属性后置范式】li(string)返回了一个节点代理,节点代理也是一个对象,这个对象上的函数用来配置属性
li("项目1").id("li1").className("item"), //链式写法
li("项目2").prop({ id:"li2", className:"item" }), //对象写法
li("项目3")({ id:"li3", className:"item" }) //使用规则属性
)
$$("#app").render(node)
动态构建
标签代理的参数可以是一个节点数组, 也可以是一个能返回节点(或节点数组)的函数, 从而实现对子节点的动态构建。 动态构建为组件化提供天然的支持。
//解构标签函数
const { div, label, ul, li } = $$
//准备数据
const data = ["item1","item2"]
//构建节点
const node = div(
div(
"当前时间:",
()=>label(new Date()) //无参函数会自动运行,并将返回值插入当前节点
),
ul(
append=>{ //函数会自动运行,参数append是一个钩子函数,指向上级节点代理的append方法
for( let i=0; i<5; i++ ){
append(li("项目"+i)) //此处append钩子函数实际调用的是ul节点代理的append方法
}
}
),
ul(
data.map(item => li(item)), //数组会自动展开
)
)
//挂载节点
$$("#app").render(node)
无参函数组件
所谓【函数组件】就是一个有返回值的函数, 命名为函数组件是为了区别于WebComponents组件。 函数组件的返回值通常是节点代理、字符串、属性配置对象, 以及上述类型的数组。
const { ul, li, div } = $$
//创建组件
function component() {
return ul(
li`项目1`,
li`项目2`,
li`项目3`
)
}
//使用组件
const node = div(
div("项目列表"),
component //等价与component(),框架会自动调用无参的函数组件
)
//渲染节点
$$("#app").render(node)
有参函数组件
无论是无参函数组件还是有参函数组件,其本质都是一个能返回节点代理(或节点数组)的函数,唯一的不同点是有参的函数组件在使用时必须手动传参调用。
const { ul, li, div, span } = $$
//定义组件
function component(data) {
return ul(
data.map(item =>
li(span(item.id),span(item.name))
)
)
}
//准备数据
const data = [{id:1,name:"项目1"},{id:2,name:"项目2"},{id:3,name:"项目3"}]
//使用组件
const node = div(
div("项目列表"),
component(data) //调用组件函数并传入参数
)
//渲染节点
$$("#app").render(node)
文档碎片
fragment是一个特殊的【标签代理】,其用途是创建一个文档碎片节点代理,其真节点是一个DocumentFragment实例。
const { fragment, ul, li } = $$
//创建文档碎片
const content = fragment(
li("项目1"),
li("项目2"),
li("项目3")
)
//使用文档碎片
const node = ul(
content
)
//渲染节点
$$("#app").render(node)
shadowRoot
DocProxy实现了对WebComponents API的全面支持。 你可以使用customElement这种重型的实现, 也可以仅使用shadowRoot这种轻型实现。 shadowRoot可以做到最完美的样式隔离和样式穿透。 可以使用以下几种方式实现shadowRoot:
- 调用容器节点代理的shadowNode(node)函数
- 调用容器节点代理的shadowHTML(html)函数
- 设置子节点的root=true,声明式启用shadowRoot
shadowNode函数
const { ul, li } = $$
$$("#container").shadowNode(
ul(
li("项目1"),
li("项目2")
)
)shadowHTML函数
$$("#container").shadowHTML(`
<ul>
<li>项目1</li>
<li>项目1</li>
</ul>
`)声明式启用shadowRoot
const { ul, li } = $$
//ul设置了root属性,将先在容器上创建shadowRoot,然后再将ul插入容器。
//注意root属性要配置到容器的子节点上,而不是配置到容器本身
$$("#container")(
ul.shadow(true)(
li("项目1"),
li("项目2")
)
)样式隔离
<!DOCTYPE html>
<html lang="en">
<head>
<script src="../doc-proxy.js"></script>
</head>
<body>
<div>这组件外边的div,样式没有被污染</div>
<div id="app"></div>
<script>
const { template, style, ul, li } = $$
const node = template.shadow(true)(
style`
li{
color:var(--color); /*使用外部定义的css变量*/
}`,
ul(
li`项目1`,
li`项目2`,
li`项目3`
)
)
$$("#app").render(node)
</script>
</body>
</html>
样式穿透
shadowRoot将隔离容器外部与内部的样式, 在通常情况下这是优点, 但有时确实需要在容器内部使用外部的样式。 使用浏览器原生支持的css变量,可以深度穿透多层ShadowRoot, 是目前最优的样式穿透方案。
<!DOCTYPE html>
<html lang="en">
<head>
<style>
:root{--color:green;}
</style>
<script src="../doc-proxy.js"></script>
</head>
<body>
<div id="app"></div>
<script>
const { fragment, style, ul, li } = $$
const node = fragment.shadow(true)(
style`
li{
color:var(--color); /*使用外部定义的css变量*/
}`,
ul(
li`项目1`,
li`项目2`,
li`项目3`
)
)
$$("#app").render(node)
</script>
</body>
</html>托管脚本
shadowRoot的子节点可以包含script块, 当子节点被插入到容器的shadowRoot时, 这些脚本块将被托管运行, 脚本块中的this指向容器, 脚本块中的root指向容器的shadowRoot。
const { template, ul, li, script } = $$
const node = template["shadow=true"](
ul(
li`项目1`,
li`项目2`,
li`项目3`
),
script`
this.onclick = ()=>{
console.log("this is ",this)
console.log("root is ",root)
}
`
)
$$("#app").render(node)插槽
const { style, template, header, main, footer, slot, div } = $$
//创建一个包含插槽的template,并配置roott为true
const temp = template["shadow=true"](
style`
::slotted(*){
padding:10px;
border:1px solid lightgray;
}
`,
header("页头"),
main(
slot //没有命名的插槽是一个默认插槽
),
footer(
slot["name=slot1"]
)
)
$$("#app")(temp)( //将template的内容插入shadowRoot
div("主体"), //插入默认插槽
div["slot=slot1"]("页脚") //插入具名插槽
)CustomElement
//定义CustomElement类,请参考w3c官网
//以下示例省略了生命周期函数
//此处完全使用原生代码来创建CustomElement,
//其实完全可以用DocProxy来构建shadowRoot的内容
class MyDiv extends HTMLElement {
constructor() {
super();
this.attachShadow({mode:"open"})
this.shadowRoot.innerHTML = `
<style>
:host{display:block;border:1px solid lightgray;}
*{padding:10px;}
header{border-bottom:1px solid lightgray;font-weight:bolder;s}
</style>
<header>这是一个webcomponent组件</header>
<main>
<slot></slot>
</main>`
}
}
//为CustomElement定义标签
customElements.define("my-div",MyDiv)
//解构webcomponent标签代理,注意my_div的下划线对应是my-div的中划线
const { my_div } = $$
//构建节点
const node = my_div("这是组件的内容")
//渲染节点
$$("#app").render(node)文档处理
DocProxy对常用的html文档处理函数做了轻量封装。
- 【nodeProxy[0]】通过节点代理的下标0可以获取代理的真实节点。
- 【nodeProxy[-1]】通过节点代理的下标-1可以获得真节点的shadowRoot。
- 【nodeProxy.html(value)】设置节点的innerHTML。
- 【nodeProxy.text(value)】设置节点的innerText。
- 【nodeProxy.append(...children)】添加子节点或配置属性。
- 【nodeProxy.prepend(...children)】在节点部的第一个子元素之前插入子节点
- 【nodeProxy.appendTo(container)】将当前节点追加到容器
- 【nodeProxy.prependTo(container)】将当前节点插入到容器的第一个子节点的前面
- 【nodeProxy.render(...chidren)】先清空本节点,在追加子节点
- 【nodeProxy.insert(value,where)】向当前节点插入子节点或html字符串内容
- 【nodeProxy.before(value)】在当前节点之前插入兄弟节点,value为空表示获取当前节点的上一个兄弟节点
- 【nodeProxy.after(value)】在当前节点之后插入兄弟节点,value为空表示获取当前节点的下一个兄弟节点
- 【nodeProxy.wrap(value)】用value来包裹当前节点
- 【nodeProxy.unwrap()】剥离当前节点的外层节点
- 【nodeProxy.up()】如果存在上一个兄弟节点,则与之交换位置
- 【nodeProxy.down()】如果存在下一个兄弟节点,则与之交换位置
- 【nodeProxy.top()】与父节点的第一个子节点交换位置
- 【nodeProxy.bottom()】与父节点的最后一个子节点交换位置
- 【nodeProxy.swap(target)】与父节点的指定子节点交互位置,target可以是下标、查询字符串、节点、节点代理
- 【nodeProxy.replace(target)】用当前节点替换目标节点
svg
const { svg, svg_path } = $$ //注意:除svg本身之外,其他svg元素的标签名都必须加svg_前缀
//构建节点
const node = svg["_width=100"]["_height=100"](
svg_path({
_d:"M0,0 L100,100",
_stroke:"red",
_stroke_width:1
})
)
//渲染节点
$$("#app").render(node)canvas集成
canvas节点本身与其他节点代理同样操作, canvas节点代理的context2d可以获取canvas的2d上下文(CanvasRenderingContext2D)的代理。 2d上下文代理上的方法用于配置与方法名同名的上下文属性, 若方法名首字母大写,则用来调用上下文方法。
$$("canvasid");
.width(200) //设置画布属性
.height(200) //设置画布属性
.context2d() //获取2d上下文代理
.lineWidth("1") //配置上下文属性
.strokeStyle("red") //配置上下文属性
.BeginPath() //调用上下文方法,首字母大写,无参
.MoveTo(50,50) //调用上下文方法,首字母大写,有参
.LineTo(100,100)
.ClosePath()
.Stroke()如果不喜欢链式配置, 或不喜欢方法名首字母大写, 或者其他框架在CanvasRenderingContext2D的prototype上定义的首字母大写的属性和方法, 则可使用nodeProxy.context2d(...steps)配置, steps是剩余参数,要求steps的每一个项的constructor都严格等于Array。
$$("canvasid")
.context2d(
1, "a", true, false, {}, //要求每个参数都是数组,这些不合法的参数不会抛出异常,但会被忽略
["lineWidth",1], //第[1]个元素不是数组,表示配置属性
["strokeStyle","red"], //第[1]个元素不是数组,表示配置属性
["beginPath"], //只有一个元素,表示调用无参方法,也可以写成["beginPath",[]]
["moveTo",[50,50]], //第[1]个元素是数组,表示以第[0]个元素为方法名,以第[1]个元素为参数(展开),调用方法
["lineTo",[100,100]],
["closePath"],
["stroke"]
)
