微信开发--微信开放标签复用方案

大家好,我是小黑

微信h5,小程序开发已经成为前端攻城狮的日常开发之一了,相信大家也遇到过这样的需求,h5中打开跳转APP,小程序。解决方案有很多种,最直接的是直接使用微信jssdk自带的开放标签,这个开放标签很好用,但是并不友好。最近小黑就遇到一个需求,一个页面少说也要写5,6个开放标签,一开始以为没什么大问题,然后做的时候,我是这样的

image.png

这tm一大堆问题啊,下面一一说清楚

问题一:与微信有强耦合

如果是纯做h5的小伙伴要注意了,打开小程序要跟微信强耦合的,所以在默认浏览器打开就要做一层适配,例如点击按钮跳应用宝链接等

问题二:样式隔离

首先上一段官方的示例代码

<wx-open-launch-weapp
  id="launch-btn"
  username="gh_xxxxxxxx"
  path="pages/home/index?user=123&action=abc"
>
  <script type="text/wxtag-template">
    <style>.btn { padding: 12px }</style>
    <button class="btn">打开小程序</button>
  </script>
</wx-open-launch-weapp>
复制代码

可以看到,样式必须是写在标签内的class或者行内样式才能生效

特别注意,标签不支持rem,vw等适配样式,这就是关键坑所在,有一个解决方案是自己写一个px转换函数,也就是自己根据屏幕宽度计算px值,这样适配问题解决了,但是复用问题呢?

问题三:无法封装复用

正如问题二最后提出的,由于样式class必须卸载标签内,又要另外去转换px值,复用就成为了一个非常让人窒息的问题

小黑的解决思路与实现

目前小黑用了vue3开发,针对以上问题,小黑突然有了灵感,以下代码跟vue3框架耦合,但相信思路应该可以通用到其它框架,思路如下

1. 先把微信标签要包含的内容(子元素)在标签外渲染出来
2. 遍历子元素获取样式,转成行内样式style
3. 把子元素插入到微信标签
复制代码

这么看思路可能有点抽象,下面来一个一个实现吧(vue3代码),我要封装一个组件,直接以slot形式使用微信标签

微信标签要包含的内容(子元素)在标签外渲染出来

例如给这个东西image.png包上微信标签 模板很好构造,用一个div包着图片,然后右下角再加上一个图片加上定位就可以了

<template>
    <div :class="$style['vip-box']">
        <img :src="src" :class="$style['img']" />
        <img src="@/assets/images/detail-comment-item-vip.jpg" :class="$style['vip']" />
    </div>
</template>
<style module>
    .vip-box {
      width: 30px;
      height: 30px;
      border-radius: 6px;
      position: relative;
    }
    .img {
      width: 100%;
      height: 100%;
      border-radius: 6px;
    }
    .vip {
      position: absolute;
      right: 0;
      bottom: 0;
      width: 11px;
      height: 11px;
      border-radius: 0px 0px 6px 0px;
    }
</style>

// 这是简单的代码结构
复制代码

如果直接这么写,你会发现样式一点都没生效,上面说了标签内写类名或者行内样式才有效,为了方便,省略了path之类的参数,这个读者可以自行添加封装

<wx-open-launch-weapp>
  <div v-is="'script'" type="text/wxtag-template">
    <div :class="$style['vip-box']">
        <img :src="src" :class="$style['img']" />
        <img src="@/assets/images/detail-comment-item-vip.jpg" :class="$style['vip']" />
    </div>
  </div>
</wx-open-launch-weapp>
复制代码

下面给出我的大概代码

<template>
  <div ref="slotContent" :class="$style['hidden']">
    <slot></slot>
  </div>
  <div>
    <wx-open-launch-weapp>
      <div v-is="'script'" type="text/wxtag-template">
        <div class="wx-container" :class="['wx-container-' + hash]"></div>
      </div>
    </wx-open-launch-weapp>
  </div>
</template>

<style module>
.hidden {
  position: absolute;
  left: -9999px;
  top: -9999px;
}
</style>
复制代码

这个.hidden类名,和额外的div,就是为了把真实的内容先渲染出来,第一步任务已经完成了,hash的作用是为了产生一个独一无二的class好让我们插入到指定的标签中

遍历子元素样式,转换成行内样式

1.获取子元素

要确保子元素被渲染到页面,所以获取子元素要在onMounted中执行

onMounted(() => {
  hash.value = getHash()
  const children = slotContent.value && slotContent.value.children
  // 如果有子元素
  if (children.length > 0) {
    // 复制样式
    copyStyle(children[0])
    const ele = children[0]
    // 子元素插入到微信标签内
    document.querySelector(`.wx-container-${hash.value}`)?.appendChild(ele)
  }
})
复制代码

2. 转换样式

这是关键的一步,这里多亏了神器getCurrentStyle,使转换成为了可能!

// 样式遍历
function copyStyle(ele: any) {
  const el = ele
  const childrens = el && el.children || []
  // 设置当前元素的样式
  setStyle(el)
  // 设置完毕后把当前的class删掉,其实单纯用作标签删不删都可以,但是如果这个组件还想做微信生态外的兼容,就要删掉防止class的样式和行内发生冲突
  el.removeAttribute('class')
  // 遍历子元素,复制样式
  if (childrens.length > 0) {
    [...childrens].forEach(child => {
      copyStyle(child)
    })
  }
}

// 样式转换成行内样式
function setStyle(ele: any) {
  // 兼容写法
  let s: any
  if (ele.currentStyle) {
    s = ele.currentStyle;
  } else {
    s = window.getComputedStyle(ele, null);
  }
  if (props.usefulStyle && props.usefulStyle.length > 0) {
    console.log("usefulStyle")
    props.usefulStyle.forEach((key: string | number) => {
      const el = ele
      // css前缀转换
      const k = transCss3Style(ele, key)
      // getCurrentStyle的key可能是index,把这种key排除
      const t = typeof parseInt(k)
      if (t !== 'number' || isNaN(k)) {
        el.style[k] = s[k]
      }
    })
  } else {
    Object.keys(s).forEach(key => {
      const el = ele
      // css前缀转换
      const k = transCss3Style(ele, key)
      // getCurrentStyle的key可能是index,把这种key排除
      const t = typeof parseInt(k)
      if (t !== 'number' || isNaN(k)) {
        el.style[k] = s[k]
      }
    })
  }
}

// 这个是简单的css前缀转换
function transCss3Style(ele: any, style: any): any {
  const s = style[0].toUpperCase() + style.slice(1, style.length)
  const transformNames: Record<string, any> = {
    webkit: 'webkitTransform',
    Moz: 'MozTransform',
    O: 'OTransform',
    ms: 'msTransform',
    standard: 'transform'
  };

  for (var key in transformNames) {
    if (ele.style[transformNames[key] + s] !== undefined) {
      return transformNames[key] + s;
    }
  }

  return style;
}

// 生成随机hash值
function getHash(): string {
  const ar = ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']
  const hs = [];
  const al = ar.length;
  for (let i = 0; i < 16; i += 1) {
    hs.push(ar[Math.floor(Math.random() * al)]);
  }

  return hs.join('');
}
复制代码
props.usefulStyle的意义

其实就是外部要传入一个有效的样式数组,为什么要这个东西呢,因为小黑在开发的过程中发现,getCurrentStyle在ios15不会返回string类型的key样式键值对,只会返回以index下标的键值对,这是一个兼容性问题

所以在使用的时候是这样的

<OpenMiniProgram>
  <div :class="$style['vip-box']">
    <img :src="src" :class="$style['img']" />
    <img src="@/assets/images/detail-comment-item-vip.jpg" :class="$style['vip']" />
  </div>
</OpenMiniProgram>
复制代码

OpenMiniProgram就是我们上面封装的组件名称

3. 插入到开放标签

上面的onMounted中有这么一段代码

const ele = children[0]
// 子元素插入到微信标签内
document.querySelector(`.wx-container-${hash.value}`)?.appendChild(ele)
复制代码

其作用就是把复制好样式后的子元素根据hash值获取类型插入到对应的微信标签中,如此一来,我们的组件就简单完成了

最后

上面是一个微信标签复用方案,当然也有额外的性能损耗就是必须独立渲染一遍子元素出来,还要手动执行dom操作,违背了数据驱动的思想,亲爱的你是否是更好的解决方案,欢迎留言交流

猜你喜欢

转载自juejin.im/post/7036576944615325710