XAML形状转CSS

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
    }
  }

结果

  1. Lunacy设计稿

  2. 转换后的效果

  3. 支持过渡

demo地址

使用:转换的宽高只是用于背景大小参照,已支持自适应

https://yuan30-1304488464.cos.ap-guangzhou.myqcloud.com/blog/demo/XAML2HTML.html

猜你喜欢

转载自blog.csdn.net/weixin_43840202/article/details/121114174
今日推荐