当前位置:   article > 正文

uniapp_canvas生成海报_微信小程序海报_uniapp小程序生成海报

uniapp小程序生成海报

uniapp_vue3版本做微信小程序项目,生成海报功能,使用canvas进行实现,适配各种型号手机。

一、实现效果(手机展示海报页、海报操作页、海报图片)

      

这张海报宽690px,长1180px,是由背景图片以及文字组成的,均可替换。

二、代码实现

创建一个poster.vue文件,内容如下:

此代码script使用setup,样式使用scss。使用时把 state ->myObj->bgImg值换成 '你背景图的地址'即可。

  1. <template>
  2. <!--页面属性配置节点,设置页面跟字体大小 -->
  3. <page-meta :root-font-size="fontSize + 'px'"></page-meta>
  4. <view class="posPoster">
  5. <view class="percard">
  6. <canvas v-show="isshow" canvas-id="myCanvas" :style="{ width: canvasWidth, height: canvasHeight }"></canvas>
  7. </view>
  8. <!-- 底部按钮占位,防止底部按钮挡住海报 -->
  9. <view class="bottomPosHeight"></view>
  10. </view>
  11. <view class="sureEvaluate" @tap="shareFriends">海报操作动作面板</view>
  12. </template>
  13. <script setup>
  14. import { reactive, toRefs, computed, onMounted, ref, watchEffect } from 'vue';
  15. import { onLoad } from '@dcloudio/uni-app';
  16. const state = reactive({
  17. fontSize: '',
  18. isshow: true,
  19. isShowBtn: false,
  20. myObj: {
  21. introduction: '海报二级标题',
  22. bgImg: '你背景图的地址', //背景图
  23. name: '开心小老虎',
  24. subject: '幼儿园',
  25. grade: '大班',
  26. time: '5月9日 8:00-9:00',
  27. position: '北京 XXX XXX XXX XXX',
  28. nameA: '姓名',
  29. subjectA: '年级',
  30. gradeA: '班级',
  31. timeA: '上课时间',
  32. positionA: '上课地点',
  33. share: `我的世界我做主,开心就好!`
  34. },
  35. canvasWidth: 690, //画布宽度
  36. canvasHeight: 1180, //画布高度
  37. ratio: 0, //计算UI设计稿和你手机的屏幕宽度比例(例如UI设计稿是750宽度 你手机是350宽度 比例就是2 那么你画布画图时候 所有的尺寸大小、宽高、位置、定位左右上下都需要除以 / 比例2 )
  38. widths: '',
  39. heights: '',
  40. imgs: ''
  41. });
  42. const { isShowBtn, fontSize, isshow, myObj, canvasWidth, canvasHeight, ratio, widths, heights, imgs } = toRefs(state);
  43. // #ifdef MP-WEIXIN
  44. /*
  45. *微信小程序获得手机信息(布局视口宽度),微信官方已不在维护
  46. */
  47. wx.getSystemInfo({
  48. success: function (res) {
  49. const rootFontSize = (res.windowWidth * 100) / 750;
  50. state.fontSize = rootFontSize;
  51. }
  52. });
  53. /**
  54. * 海报操作面板
  55. */
  56. const shareFriends = () => {
  57. wx.showShareImageMenu({
  58. path: state.imgs,
  59. success: (res) => {
  60. console.log('操作成功:', res);
  61. },
  62. fail: (err) => {
  63. console.log('操作取消:', err);
  64. }
  65. });
  66. };
  67. // #endif
  68. /**
  69. * 计算海报尺寸,适配各种手机,因为我的项目没有使用rpx用的rem所以需要换算,后来为了提出单独组件,在scss里用了rpx。
  70. */
  71. onLoad((option) => {
  72. uni.getSystemInfo({
  73. success: (res) => {
  74. state.canvasWidth = (res.screenWidth / 750) * 690 + 'px';
  75. state.widths = (res.screenWidth / 750) * 690;
  76. state.ratio = 750 / res.screenWidth;
  77. state.canvasHeight = (res.screenWidth / 750) * 1180 + 'px';
  78. state.heights = (res.screenWidth / 750) * 1180;
  79. }
  80. });
  81. uni.showLoading({
  82. title: '海报生成中...'
  83. });
  84. downImgUrl();
  85. });
  86. const downImgUrl = () => {
  87. uni.getImageInfo({
  88. src: state.myObj.bgImg,
  89. success: function (res) {
  90. state.myObj.bgImg = res.path;
  91. drawPageImg();
  92. state.isShowBtn = true;
  93. }
  94. });
  95. };
  96. //画一个带圆角矩形
  97. const ctxCircular = (ctx, img, x, y, width, height, r, shadow) => {
  98. ctx.beginPath(); //开始绘制
  99. ctx.save(); //保存(canvas)状态
  100. ctx.moveTo(x + r, y);
  101. ctx.lineTo(x + width - r, y);
  102. ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
  103. ctx.lineTo(x + width, y + height - r);
  104. ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5);
  105. ctx.lineTo(x + r, y + height);
  106. ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI);
  107. ctx.lineTo(x, y + r);
  108. ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
  109. if (shadow == 1) {
  110. ctx.shadowBlur = 20; // 模糊效果程度的
  111. ctx.shadowColor = 'red'; // 阴影颜色
  112. }
  113. ctx.fill(); //对当前路径中的内容进行填充
  114. ctx.clip(); //从原始画布中剪切任意形状和尺寸
  115. ctx.closePath(); //关闭一个路径
  116. ctx.drawImage(img, x, y, width, height);
  117. ctx.restore(); //恢复(canvas)状态
  118. ctx.globalCompositeOperation = 'source-over';
  119. };
  120. //画一个矩形也就是整个海报的背景
  121. const ctxRectangle = (ctx, x, y, width, height, r, gnt) => {
  122. ctx.beginPath();
  123. ctx.save(); //保存状态
  124. ctx.moveTo(x + r, y);
  125. ctx.lineTo(x + width - r, y);
  126. ctx.arc(x + width - r, y + r, r, Math.PI * 1.5, Math.PI * 2);
  127. ctx.lineTo(x + width, y + height - r);
  128. ctx.arc(x + width - r, y + height - r, r, 0, Math.PI * 0.5);
  129. ctx.lineTo(x + r, y + height);
  130. ctx.arc(x + r, y + height - r, r, Math.PI * 0.5, Math.PI);
  131. ctx.lineTo(x, y + r);
  132. ctx.arc(x + r, y + r, r, Math.PI, Math.PI * 1.5);
  133. ctx.fillStyle = gnt;
  134. ctx.fill(); //对当前路径中的内容进行填充
  135. ctx.closePath(); //关闭一个路径
  136. };
  137. // 文字
  138. const ctxText = (ctx, textFont, textAlign, textFillStyle, textName, x, y) => {
  139. ctx.beginPath();
  140. ctx.save(); //保存状态
  141. //字体
  142. (ctx.font = textFont),
  143. //字体样式
  144. (ctx.textAlign = textAlign);
  145. //字体颜色
  146. ctx.fillStyle = textFillStyle;
  147. //填充字体
  148. ctx.fillText(textName || '', x, y);
  149. ctx.globalCompositeOperation = 'source-over';
  150. };
  151. // 文字
  152. const ctxTextWrap = (ctx, text, x, y, w, size) => {
  153. //自动换行介绍
  154. var temp = '';
  155. var row = [];
  156. let gxqm = '';
  157. if (text) {
  158. gxqm = text;
  159. } else {
  160. gxqm = '未设置个性签名';
  161. }
  162. let gexingqianming = gxqm.split('');
  163. for (var a = 0; a < gexingqianming.length; a++) {
  164. if (ctx.measureText(temp).width < w) {
  165. } else {
  166. row.push(temp);
  167. temp = '';
  168. }
  169. temp += gexingqianming[a];
  170. }
  171. row.push(temp);
  172. ctx.font = `${size}px arail`;
  173. ctx.textAlign = 'left';
  174. ctx.fillStyle = '#333';
  175. ctx.globalCompositeOperation = 'source-over';
  176. for (var b = 0; b < row.length; b++) {
  177. ctx.fillText(row[b] || '', x, y + (b + 1) * 20);
  178. }
  179. };
  180. // 文字
  181. const ctxTextWrapA = (ctx, text, x, y, w, size, color) => {
  182. //自动换行介绍
  183. var temp = '';
  184. var row = [];
  185. let gxqm = '';
  186. if (text) {
  187. gxqm = text;
  188. } else {
  189. gxqm = '';
  190. }
  191. let gexingqianming = gxqm.split('');
  192. for (var a = 0; a < gexingqianming.length; a++) {
  193. if (ctx.measureText(temp).width < w) {
  194. } else {
  195. row.push(temp);
  196. temp = '';
  197. }
  198. temp += gexingqianming[a];
  199. }
  200. row.push(temp);
  201. ctx.font = `${size}px arail`;
  202. ctx.textAlign = 'left';
  203. ctx.fillStyle = `#${color}`;
  204. ctx.globalCompositeOperation = 'source-over';
  205. for (var b = 0; b < row.length; b++) {
  206. ctx.fillText(row[b] || '', x, y + (b + 1) * 20);
  207. }
  208. };
  209. // 文字
  210. const ctxTextWrapB = (ctx, text, x, y, w, size, color) => {
  211. //自动换行介绍
  212. var temp = '';
  213. var row = [];
  214. let gxqm = '';
  215. if (text) {
  216. gxqm = text;
  217. } else {
  218. gxqm = '未设置个性签名';
  219. }
  220. let gexingqianming = gxqm.split('');
  221. for (var a = 0; a < gexingqianming.length; a++) {
  222. if (ctx.measureText(temp).width < w) {
  223. } else {
  224. row.push(temp);
  225. temp = '';
  226. }
  227. temp += gexingqianming[a];
  228. }
  229. row.push(temp);
  230. ctx.font = `arail normal bold ${size}px sans-serif`;
  231. ctx.textAlign = 'left';
  232. ctx.fillStyle = `#${color}`;
  233. ctx.globalCompositeOperation = 'source-over';
  234. for (var b = 0; b < row.length; b++) {
  235. ctx.fillText(row[b] || '', x, y + (b + 1) * 20);
  236. }
  237. };
  238. // 使用画布绘制页面
  239. const drawPageImg = () => {
  240. // 生成画布
  241. const ctx = reactive(uni.createCanvasContext('myCanvas'));
  242. // 绘制背景
  243. ctx.drawImage(
  244. state.myObj.bgImg, //图像资源
  245. 0 / state.ratio, //图像的左上角在目标canvas上 X 轴的位置
  246. 0 / state.ratio, //图像的左上角在目标canvas上 Y 轴的位置
  247. 690 / state.ratio, //在目标画布上绘制图像的宽度
  248. 1180 / state.ratio //在目标画布上绘制图像的高度
  249. );
  250. let numA = Math.floor(0.28 * state.fontSize);
  251. ctxTextWrapB(ctx, state.myObj.introduction, 62 / state.ratio, 374 / state.ratio, 600 / state.ratio, Math.floor(0.38 * state.fontSize), '333');
  252. ctxTextWrapA(ctx, state.myObj.nameA, 62 / state.ratio, 473 / state.ratio, 400 / state.ratio, numA, '666');
  253. ctxTextWrapA(ctx, state.myObj.subjectA, 62 / state.ratio, 551 / state.ratio, 400 / state.ratio, numA, '666');
  254. ctxTextWrapA(ctx, state.myObj.gradeA, 62 / state.ratio, 629 / state.ratio, 400 / state.ratio, numA, '666');
  255. ctxTextWrapA(ctx, state.myObj.timeA, 62 / state.ratio, 707 / state.ratio, 400 / state.ratio, numA, '666');
  256. ctxTextWrapA(ctx, state.myObj.positionA, 62 / state.ratio, 785 / state.ratio, 400 / state.ratio, numA, '666');
  257. let num = Math.floor(0.32 * state.fontSize);
  258. ctxTextWrapA(ctx, state.myObj.name, 215 / state.ratio, 474 / state.ratio, 400 / state.ratio, num, '333');
  259. ctxTextWrapA(ctx, state.myObj.subject, 215 / state.ratio, 552 / state.ratio, 400 / state.ratio, num, '333');
  260. ctxTextWrapA(ctx, state.myObj.grade, 215 / state.ratio, 630 / state.ratio, 400 / state.ratio, num, '333');
  261. ctxTextWrapA(ctx, state.myObj.time, 215 / state.ratio, 708 / state.ratio, 400 / state.ratio, num, '333');
  262. ctxTextWrapA(ctx, state.myObj.position, 215 / state.ratio, 786 / state.ratio, 400 / state.ratio, num, '333');
  263. ctxTextWrapA(ctx, state.myObj.share, 62 / state.ratio, 275 / state.ratio, 550 / state.ratio, Math.floor(0.36 * state.fontSize), '333');
  264. ctx.draw(
  265. false,
  266. (() => {
  267. setTimeout(() => {
  268. // 将canvas 变成图片方便发送给好友或者保存
  269. uni.canvasToTempFilePath({
  270. canvasId: 'myCanvas',
  271. destWidth: state.canvasWidth * 2, //展示图片尺寸=画布尺寸1*像素比2
  272. destHeight: state.canvasHeight * 2,
  273. quality: 1,
  274. fileType: 'jpg',
  275. success: (res) => {
  276. uni.hideLoading();
  277. state.imgs = res.tempFilePath;
  278. },
  279. fail: function (error) {
  280. uni.hideLoading();
  281. uni.showToast({
  282. icon: 'none',
  283. position: 'bottom',
  284. title: '绘制图片失败'
  285. });
  286. }
  287. });
  288. }, 100);
  289. })()
  290. );
  291. };
  292. </script>
  293. <style scoped lang="scss">
  294. .posPoster {
  295. width: 750rpx;
  296. height: 1338rpx;
  297. position: relative;
  298. margin-top: 205rpx;
  299. }
  300. .percard {
  301. width: 690rpx;
  302. height: 1180rpx;
  303. overflow: hidden;
  304. position: absolute;
  305. left: 50%;
  306. transform: translate(-50%, 0);
  307. }
  308. .sureEvaluate {
  309. width: 690rpx;
  310. height: 88rpx;
  311. line-height: 88rpx;
  312. border-radius: 53rpx;
  313. background: #e6e8ff;
  314. @include fcsw(#5662f6, 32, center);
  315. position: fixed;
  316. bottom: 50rpx;
  317. left: 30rpx;
  318. border: none;
  319. outline: none;
  320. box-shadow: none;
  321. z-index: 999;
  322. }
  323. .bottomPosHeight {
  324. width: 750rpx;
  325. height: 138rpx;
  326. }
  327. </style>

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/weixin_40725706/article/detail/751199
推荐阅读