如何将Vue VNode呈现为字符串

问题描述

我正在尝试在Vue组件中使用CSS蒙版。我需要完成下面的toSvg函数的实现。这样会将Vue VNode从this.$slots.default转换为SVG字符串。

<script>
export default {
  computed: {
    maskImage() {
      const svg = this.toSvg(this.$slots.default);
      const encodedSvg = btoa(svg);
      return `url('data:image/svg+xml;base64,${encodedSvg}')`;
    },},methods: {
    toSvg(vnode) {
      // Todo: How can I convert the VNode to a string like the one below?
      // In React,I Could use const svg = ReactDOMServer.renderToStaticmarkup(vnode);
      return `<svg viewBox="0 0 260 68" xmlns="http://www.w3.org/2000/svg">
          <rect x="80" y="32" width="160" height="12" rx="2" />
          <rect width="180" height="20" rx="2" />
          <rect x="80" y="52" width="95" height="12" rx="2" />
          <rect y="26" width="68" height="42" rx="2" />
      </svg>`;
    },render(createElement) {
    return createElement("div",{
      attrs: {
        class: "skeleton",style: `-webkit-mask-image: ${this.maskImage}; mask-image: ${this.maskImage};`,});
  },};
</script>

<style lang="scss">
.skeleton {
  animation: skeleton-animation 2s infinite linear;
  background: linear-gradient(to right,hsl(30,1,99) 0%,2,95) 30%,95) 70%,99) 100%) 0 0 / 200% 100% hsl(30,95);
  overflow: hidden;
  position: relative;

  width: 200px;
  height: 100px;

  @keyframes skeleton-animation {
    100% {
      background-position: -200% 0;
    }
  }
}
</style>

用法示例:

<u-skeleton>
  <svg viewBox="0 0 260 68" xmlns="http://www.w3.org/2000/svg">
    <rect x="80" y="32" width="160" height="12" rx="2" />
    <rect width="180" height="20" rx="2" />
    <rect x="80" y="52" width="95" height="12" rx="2" />
    <rect y="26" width="68" height="42" rx="2" />
  </svg>
</u-skeleton>

显示为:

enter image description here

解决方法

使用Vue.extend构造一个SVG组件 constructor ,在构造函数的render function内部,它呈现slots.default

下一步是创建其实例,然后创建mount()并获取已编译的html。

Vue.component('v-test',{
  computed: {
    maskImage() {
      let vnodes = this.$slots.default
      let SVGConstructor = Vue.extend({
        render: function (h,context) {
          return h('svg',{
            attrs: {
              xmlns: 'http://www.w3.org/2000/svg'
            }
          },vnodes)
        }
      })
      let instance = new SVGConstructor()
      instance.$mount()
      const encodedSvg = btoa(instance.$el.outerHTML);
      return `url('data:image/svg+xml;base64,${encodedSvg}')`;
    }
  },render(createElement) {
    return createElement("div",{
      attrs: {
        class: "skeleton",style: `-webkit-mask-image: ${this.maskImage}; mask-image: ${this.maskImage};`,},})
  },})

new Vue({
  el: '#app'
})
.skeleton {
  animation: skeleton-animation 2s infinite linear;
  background: linear-gradient(to right,#fcfcfc 0%,#f3f2f2 30%,#f3f2f2 70%,#fcfcfc 100%) 0 0 / 200% 100% #f3f2f2;
  overflow: hidden;
  position: relative;
  width: 200px;
  height: 100px;
}
@keyframes skeleton-animation {
  100% {
    background-position: -200% 0;
  }
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/vue/2.5.17/vue.js"></script>
<div id="app">
  <v-test>
    <svg viewBox="0 0 260 68" xmlns="http://www.w3.org/2000/svg">
      <rect x="80" y="32" width="160" height="12" rx="2" />
      <rect width="180" height="20" rx="2" />
      <rect x="80" y="52" width="95" height="12" rx="2" />
      <rect y="26" width="68" height="42" rx="2" />
    </svg>
  </v-test>
  <hr>
  <v-test>
    <svg viewBox="0 0 260 68" x="0" y="0" xmlns="http://www.w3.org/2000/svg">
      <rect x="80" y="32" width="160" height="12" rx="2" />
      <rect width="180" height="20" rx="2" />
      <rect x="80" y="52" width="95" height="12" rx="2" />
      <rect y="26" width="68" height="42" rx="2" />
    </svg>
    <svg viewBox="0 0 260 68" x="20" y="-20" xmlns="http://www.w3.org/2000/svg">
      <rect x="80" y="32" width="160" height="12" rx="2" />
      <rect width="180" height="20" rx="2" />
      <rect x="80" y="52" width="95" height="12" rx="2" />
      <rect y="26" width="68" height="42" rx="2" />
    </svg>
  </v-test>
</div>