如何将 Vue VNode 渲染为字符串

How to render a Vue VNode to a String

我正在尝试在我的 Vue 组件中使用 CSS 掩码。我需要完成下面toSvg函数的实现。这会将来自 this.$slots.default 的 Vue VNode 渲染为 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%, hsl(30, 2, 95) 30%, hsl(30, 2, 95) 70%, hsl(30, 1, 99) 100%) 0 0 / 200% 100% hsl(30, 2, 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>

显示为:

使用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>