1. 规则
Lunacy图形规则,都是由Canvas与Path标签组成(纯块,暂不考虑文本,图片控件)
2. 实现方式
采用生成dom,获取dom属性的方式对图形进行拆解
const root = document.createElement('div')
root.innerHTML = htmlStr
3. HTML规则
HTML中,使用单标签组件如(),多个并排会被html解析为嵌套,所以需要处理单标签转为双闭合标签
// 替换行内标签
function replaceInlineLabel(htmlStr) {
// 正则替换
// return htmlStr.replace(/\<(.*)\s([A-z0-9\s\.=\-"]*\s)*([A-z\.="0-9#]*)[^\>]+\/>/g, '<$1 $2 $3><\/$1>')
// 由于正则效率较低,且目前只有Path的情况,进行简单处理即可
return htmlStr.replace(/\/>/g, '></path>')
}
4.分析需要支持自适应
如果转换的图形要实现自适应,那么有关位置形状的属性必须都要是百分比的形式
发现Lunacy的图形裁剪路径都是path,而css中的cli-path只有polygon或者其他能使用百分比的属性才能支持
5.实现path2polygon
将path转为polygon
研究了一下,发现转换规则:MZ直接去除,然后以L为分割单位,依次排 x y, x2 y2, x3 y3, …
function path2polygon(path, width, height) {
path = path.replace(/M|Z/ig, '')
const pathArr = path.split('L')
const polygon = pathArr.reduce((arr, item) => {
const [x, y] = item.split(' ')
arr.push(`${
(x / width * 100).toFixed(2) * 1}% ${
(y / height * 100).toFixed(2) * 1}%`)
return arr
}, [])
return polygon.join(',')
}
6.实现解析XAML
function XAML2CSS(htmlStr, name, width, height) {
const root = document.createElement('div')
root.innerHTML = htmlStr
root.style.display = 'none'
const box = root.getElementsByTagName('canvas')[0]
const boxW = box.getAttribute('width')
const boxH = box.getAttribute('height')
const deviationL = (width - boxW) / 2
const deviationT = (height - boxH) / 2
let domItems = ''
let cssItems = ``
let nodeData = []
let index = 0
function dfs(nodes, left, top) {
for (let i = 0; i < nodes.length; i++) {
const node = nodes[i]
const nodeName = node.nodeName.toLowerCase()
let nodeL = left + Number(node.getAttribute('canvas.left') || 0)
let nodeT = top + Number(node.getAttribute('canvas.top') || 0)
if (nodeName === 'canvas') {
dfs(node.children, nodeL, nodeT)
} else if (nodeName === 'path') {
index++
// 宽比例
const itemW = node.getAttribute('width')
const itemWScale = itemW / width
// 高比例
const itemH = node.getAttribute('height')
const itemHScale = itemH / height
const item = {
nodeName,
background: node.getAttribute('fill'),
clipPath: node.getAttribute('data'),
transform: `translate(${
nodeL}px, ${
nodeT}px)`,
left: (nodeL / width * 100).toFixed(2) * 1 + '%',
top: (nodeT / height * 100).toFixed(2) * 1 + '%',
width: (itemWScale * 100).toFixed(2) * 1 + '%',
height: (itemHScale * 100).toFixed(2) * 1 + '%',
transition: `all 300ms ease ${
index * 100}ms`
}
nodeData.push(item)
const polygon = path2polygon(item.clipPath, itemW, itemH)
cssItems = cssItems + `
.${
name}>div:nth-child(${
index}){
clip-path: polygon(${
polygon});
background: ${
item.background};
left: ${
item.left};
top: ${
item.top};
width: ${
item.width};
height: ${
item.height};
transition: ${
item.transition};
}
`
domItems += '<div></div>\n'
}
}
}
dfs(root.children, deviationL, deviationT)
let css = `
.${
name}{
transition: all 300ms ease 300ms;
position: relative;
width: 100%;
height: 100%;
background: ${
box.getAttribute('background')};
}
.${
name}>div{
position: absolute;
left: 0;
top: 0;
width:0;
height:0;
clip-path: polygon(0% 0%, 0% 0%, 0% 0%, 0% 0%);
}
${
cssItems}
`
const style = `
<style>
${
css}
</style>
`
const dom = `
<div class="${
name}">
${
domItems}
</div>
`
return {
style,
css,
dom,
nodeData,
amount: index
}
}
结果
demo地址
使用:转换的宽高只是用于背景大小参照,已支持自适应
https://yuan30-1304488464.cos.ap-guangzhou.myqcloud.com/blog/demo/XAML2HTML.html