vue+html2canvas生成寄几想要的海报

需求:

点击弹出一个弹窗,其中是某个作品内容的海报,需要呈现作品的内容+二维码

 

思路:

获取作品内容渲染到弹窗中,生成包含分享链接的二维码,将整个界面转为图片,用户可以长按保存,并扫描识别。

 

方案及步骤:

1.引入html2canvas实现生成图片的功能

npm install --save html2canvas

 

2.引入vue-qrcode实现生成二维码的功能

因为我用的是vue2,因此使用此插件专用与vue2的分支:npm install vue@2 @chenfengyuan/vue-qrcode@1

如果是vue3,可直接引用最新版:npm install vue@3 qrcode@1 @chenfengyuan/vue-qrcode@2

 

3.弹窗使用vant的popup组件。具体页面实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
<!-- 分享作品预览 -->
<template>
  <van-popup
    v-model="visible"
    get-container="body"
    :style="{ backgroundColor: 'transparent', overflowY: 'visible' }"
    @opened="popupDown"
  >
    <div :class="$style.preview" @click="visible = false">
      <div id="j-canvas" :class="$style.canvas">
        <div :class="$style.popupDetail">
          <div :class="$style.detailImgInfo">
            <div :class="$style.detailImg">
              <img
                v-if="imageBase"
                :src="imageBase"
                style="width: 2.34rem; height: 2.46rem; margin-top: -0.1rem;"
 
              />
            </div>
            <div :class="[$style.detailInfo, $style.detailInfoFirst]">
              作者名称:{{ detailInfo.submitter }}
            </div>
            <div :class="$style.detailInfo">
              作品编号:{{ detailInfo.code_num }}
            </div>
          </div>
          <div :class="$style.detailTip"></div>
          <div :class="$style.detailTipInfo">
            <div :class="$style.detailTipInfoDiv">
              <van-field
                v-model="detailInfo.content"
                rows="5"
                autosize
                label=""
                type="textarea"
                disabled
              />
            </div>
          </div>
          <div :class="$style.footer">
            <img
              src="./images/logo.png"
              style="width: 2.06rem; height: 1.41rem"
            />
            <img
              src="./images/qrcode.png"
              style="width: 2.61rem; height: 0.92rem"
            />
            <div :class="$style.detailQrCode">
              <qrcode
                :value="`${baseURL}?code_num=${detailInfo.code_num}`"
              ></qrcode>
            </div>
          </div>
        </div>
      </div>
      <div :class="$style.screenshot"><img :src="screenUrl" /></div>
    </div>
  </van-popup>
</template>
 
<script>
import html2canvas from "html2canvas";
 
export default {
  name: "SharePreview",
  props: {
    detailInfo: {
      type: Object,
      default() {
        return {};
      },
    },
  },
  data() {
    return {
      screenUrl: "",
      visible: false,
      baseURL: window.location.origin,
      obj: {
        imgHasLoaded: false,
        popupHasLoaded: false
      },
      imageBase: ""
    };
  },
  computed: {
    canvasParams: function() {
      const { imgHasLoaded, popupHasLoaded } = this.obj;
      return { imgHasLoaded, popupHasLoaded };
    }
  },
  watch: {
    detailInfo() {
      const { image } = this.detailInfo;
      if (image) {
        this.imageToBase64(image);
      }
    },
    canvasParams() {
      const { imgHasLoaded, popupHasLoaded } = this.canvasParams;
      // 图片生成(确保有二维码&图片后再生成图片)
      if (imgHasLoaded && popupHasLoaded) {
        this.getImg();
      }
    }
  },
  methods: {
    imgLoaded() {
      this.obj.imgHasLoaded = true;
    },
    popupDown() {
      this.obj.popupHasLoaded = true;
    },
    getImg() {
      html2canvas(document.querySelector("#j-canvas"), {
        backgroundColor: "#000"
      }).then(canvas => {
        this.screenUrl = canvas.toDataURL();
      });
    },
    async imageToBase64(src) {
      const response = await fetch(src);
      const blob = await response.blob();
      const reader = new FileReader();
      reader.onloadend = () => {
        this.imageBase = reader.result;
      };
      reader.readAsDataURL(blob);
      this.obj.imgHasLoaded = true;
    }
  },
};
</script>
 
<style lang="less" module>
.screenshot {
  width: 7.2rem;
  height: 10.8rem;
  position: absolute;
  top: 0.7rem;
  left: 0;
  overflow: hidden;
  opacity: 0;
 
  img {
    .size(100%);
  }
}
.preview {
  position: relative;
 
  &::before {
    .background-fill("./images/popup-close.png", 0.56rem, right 0);
 
    content: "";
    display: block;
    height: 0.7rem;
    pointer-events: none;
  }
 
  &::after {
    .size(6.71rem, 0.34rem);
    .background-fill("./images/image-preview-tip.png");
 
    content: "";
    display: block;
    margin: 0.3rem auto 0;
    pointer-events: none;
  }
  .canvas {
    .size(100%);
  }
 
  .popupDetail {
    .background-fill("./images/popup-detail-bg.png");
    width: 7.2rem;
    height: 10.8rem;
    padding-top: 0;
  }
  .detailBody {
    margin: 0 auto;
  }
 
  .detailImgInfo {
    width: 3.97rem;
    margin: 0 auto;
    padding-top: 0.61rem;
    .detailImg {
      display: flex;
      justify-content: center;
      align-items: center;
      width: 3.97rem;
      height: 4.34rem;
      .background-fill("./images/img_bg.png");
    }
    .detailInfo {
      font-size: 0.24rem;
      white-space: nowrap;
      margin-bottom: 0.1rem;
      color: #ffe58f;
      padding-left: 0.5rem;
    }
    .detailInfoFirst {
      margin-top: -0.6rem;
    }
  }
  .detailTip {
    width: 2.86rem;
    height: 0.67rem;
    .background-fill("./images/title_s.png");
    margin: 0 auto 0.2rem;
  }
  .detailTipInfo {
    width: 5.88rem;
    height: 2.24rem;
    background-color: rgba(0, 0, 0, 0.2);
    border: 1px solid #ffdda3;
    border-radius: 4px;
    margin: 0 auto 0.48rem;
    color: #fff1d1;
    font-size: 0.18rem;
    line-height: 0.28rem;
    padding: 0.3rem 0.24rem;
    overflow: hidden;
    .detailTipInfoDiv {
      width: 5.4rem;
      height: 1.64rem;
      overflow: hidden;
      textarea {
        color: #fff1d1;
        -webkit-text-fill-color: #fff1d1 !important;
        overflow: hidden;
      }
    }
  }
  .footer {
    display: flex;
    justify-content: center;
    align-items: center;
  }
  .detailQrCode {
    width: 1.12rem;
    height: 1.05rem;
    .background-fill("./images/qrcode_bg.png");
    display: flex;
    justify-content: left;
    align-items: center;
    padding-left: 0.05rem;
    margin-left: 0.14rem;
    canvas {
      width: 0.92rem !important;
      height: 0.92rem !important;
    }
  }
}
</style>

  

4.其他页面使用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
    <share-preview ref="preview" :detailInfo="detailInfo" />
 
 
// 引入
import SharePreview from "@/components/Popup/SharePreview";
 
// 调用方法唤醒
    shareWork(item) {
      // 是否登录
      if (this.token) {
        if (item.id) {
          this.detailInfo = item;
          this.$refs.preview.visible = true;
          this.toShare();
        } else {
          this.$toast("敬请期待");
        }
      } else {
        this.$refs.login.show();
      }
    },

  

5.tips:

①因为作品数据是通过接口请求的,其中包含了作品的图片,因此要确保图片加载完毕后才生成图片,否则会因为异步的原因,在图片还没有渲染时就生成图片,保存后发现并没有作品图。尝试方案时首先使用了img的@load进行监听,但并无效果,所以换了方案,即拿到图片地址后先转为base64格式,然后赋值到页面,保证图片一定是渲染好的。

②二维码的生成也同样,因为可能会有生成时间上的差异,所以要保证二维码生成了,再去生成完整的海报图片。这里是使用了popup提供的@opened事件,监听弹窗渲染完毕打开后,再去生成图片。

③为了同时保证作品图片与二维码都生成并渲染好之后,再去生成海报,所以在data中加了一个对象obj,其中的imgHasLoaded与popupHasLoaded就是为了监听这两件事是否处理完的。在watch中监听canvasParams(也就是computed中配置的这两个值)的状态,确保两者都进行完毕后,再去执行图片生成的操作,经过测试,这个方案是可行的。

④在图片转base64格式时,可能会存在跨域的问题,需要后端同学配合处理,否则若不允许跨域,是无法成功转换格式的。

⑤生成图片的方式很直接,要生成的内容即代码中配置了id为j-canvas的div中的全部内容,可以注意到与它同级的div中放了一个图片,这个图片就是通过插件生成的海报。将这个div定位到弹窗上方,透明度调为0,这个功能就这么完成了。

 

posted @   芝麻小仙女  阅读(449)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具
点击右上角即可分享
微信分享提示