前端使用uniapp制作H5自定义海报,本来以为挺简单的一常用功能画布,因为之前也在 H5 和小程序做过类似功能,所以直接上手干了,没想到还是遇到了一些坑,特此记录希望能够帮到大家。
实现功能讲解:
海报实现的效果图,上面的微信头像,昵称和下面的文案,二维码都是动态的,也就是说是需要后端生成的,包括背景图片,也需要随机生成,然后用户长按界面能够保存,识别二维码,分享功能。
实现:
创建一个画布,给画布设置 style 。
<canvas style="" canvas-id="myCanvas" id="myCanvas"></canvas>
canvas {
background-color: #fff;
border: 1px solid #d0d0d0;
width: 100vw;
height: 100vh;
position: absolute;
left: 100%;
}
创建画布实例并开始绘画。
var context = uni.createCanvasContext('myCanvas');
所有数据信息都由后端传过来,下面关于图片素材有两种情况,
1.如果图片允许跨域,可以通过 uni.getImageInfo 方法获取图片路径放到画布里面;
2.如果图片不允许跨域,需要先转成base64格式,然后通过 context.drawImage() 放到画布里面
以上图片两个报错,都是图片不支持跨域导致的。开发者工具可能可以正确画布出来并导出为图片,但是手机画布导出的时候就会报错,这个时候就需要转成base64 了,先尝试用代码转,如果不能转,就在站点 在线转换,然后把base64保存到文件里面。
有人可能想到把图片放到代码包里面做成本地图片,就绕过跨域问题了,但是这个方法我尝试的时候发现小程序可以,H5不可以,很遗憾。如果有H5本地图片画布成功的也可以跟我分享一下经验,谢谢。
H5实现长按识别图片功能:
用户长按图片自然就会有系统默认的弹窗可以识别或者保存分享了,不需要代码实现。
下面看看完整的实现的代码吧:(干净整洁,有注释,喜欢的点个赞!)
<template>
<!--pages/poster/poster.wxml-->
<view class="page">
<canvas style="" canvas-id="myCanvas" id="myCanvas"></canvas>
<view class="container">
<image :src="tempFilePath" mode="widthFix"></image>
</view>
</view>
</template>
<script>
import {
IMAGES_PATH
} from '@/common/const.js';
import {
getPoste
} from '@/common/api.js';
import {
pathToBase64,
base64ToPath
} from '../../js_sdk/gsq-image-tools/image-tools/index.js'
import base64_img from "./base64_img.js";
export default {
data() {
return {
tempFilePath: ''
};
},
onLoad: function(options) {
var that = this;
var context = uni.createCanvasContext('myCanvas');
getPoster(options.id)
.then(res => {
var {
img,
avatar,
nickname,
avatar_content,
active_describe
} = res.data.data || {};
var right_bottom_txt_base64 =
"data:image/png;base64"
pathToBase64(avatar)
.then(base64_avatar => {
// 背景底图
context.drawImage(base64_img[img], 0, 0, 200, 387, 400, 774)
// 用户昵称
context.setFillStyle('#233582');
context.fillText(nickname, 90, 132);
// 文字上面横线 1
context.setFillStyle('#233582')
context.fillRect(16, 146, 166, 1.2)
// 文字 判断长度换行处理
context.setFontSize(9)
if (active_describe.length > 18) {
let tt1 = active_describe.substr(0, 17);
let tt2 = active_describe.substr(17, active_describe.length - 1);
context.fillText(tt1, 16, 166);
context.fillText(tt2, 16, 181);
} else {
context.fillText(active_describe, 20, 166, 320);
}
// 文字下面横线 2
context.fillRect(16, 196, 166, 1.2)
// 右下角的文字用图片,因为在背景图的话会失真模糊
context.drawImage(right_bottom_txt_base64, 100, 230, 90, 30)
// 规定一个圆形的位置,里面放头像图片
let avatarurl_width = 40
let avatarurl_heigth = 40
let avatarurl_x = 80
let avatarurl_y = 80
context.arc(avatarurl_width / 2 + avatarurl_x, avatarurl_heigth / 2 + avatarurl_y,
avatarurl_width / 2,
0, Math.PI * 2, false)
context.clip()
context.drawImage(base64_avatar, avatarurl_x, avatarurl_y, avatarurl_width, avatarurl_heigth)
context.restore()
// 保存画布,生成图片指定大小的图片,并返回图片路径
// 有个坑 : H5端 Canvas 内绘制的图像需要支持跨域访问才能成功。(本代码的解决方案:把图片转成base64)
context.draw(true, () => {
uni.canvasToTempFilePath({
x: 0,
y: 0,
width: 200,
height: 387,
destWidth: getApp().windowWidth,
destHeight: getApp().windowHeight,
canvasId: 'myCanvas',
fileType: "jpg",
quality: 1,
success(res) {
console.log('绘制成功-------', res)
uni.hideLoading();
that.tempFilePath = res.tempFilePath;
},
fail(err) {
console.log('绘制失败', err)
}
});
});
})
})
},
methods: {
// 小程序的场景使用,小程序支持本地图片
getImageInfo(url) {
return new Promise(function(resolve, reject) {
uni.getImageInfo({
src: url,
success: function(res) {
console.log(res);
resolve(res);
},
fail: function(err) {
console.log(err);
uni.showToast({
title: '生成失败',
icon: 'none'
});
}
});
});
},
// 小程序的场景使用,长按保存图片
saveImg(e) {
let url = this.tempFilePath;
uni.saveFile({
tempFilePath: url,
success: function(res) {
var savedFilePath = res.savedFilePath;
uni.showToast({
title: "图片保存成功",
icon: "none"
});
}
});
},
// 小程序场景使用,获取图片信息,保存到相册
downImage() {
const url = this.images1;
uni.getImageInfo({
src: url,
success: res => {
let path = res.path;
uni.saveImageToPhotosAlbum({
filePath: path,
success: res => {
uni.showToast({
title: '保存成功',
icon: 'none'
});
},
fail: res => {
console.log(res);
}
});
},
fail: res => {
console.log(res);
}
});
}
}
};
</script>
<style>
canvas {
background-color: #fff;
border: 1px solid #d0d0d0;
width: 100vw;
height: 100vh;
position: absolute;
left: 100%;
}
.container {
width: 100vw;
align-items: center;
overflow: auto;
background: #fefefe;
}
.container image {
width: 100%;
position: absolute;
top: 0;
}
</style>
app.vue 设置全局常数,屏幕的宽度和高度
onLaunch: function(info) {
var that=this;
uni.getSystemInfo({
success: function (res) {
that.windowWidth =res.windowWidth;
that.windowHeight =res.windowHeight;
console.log('----this---www',this.windowWidth);
}
});
//网络状态监听
// network();
console.log('App Launch');
},
./base64_img.js 代码,里面放 base64 图片, 是背景图的数组,后端随机生成1~5,拿下标画布就行。
'../../js_sdk/gsq-image-tools/image-tools/index.js' 代码,图片转base64 函数封装,无需修改。
function getLocalFilePath(path) {
if (path.indexOf('_www') === 0 || path.indexOf('_doc') === 0 || path.indexOf('_documents') === 0 || path.indexOf('_downloads') === 0) {
return path
}
if (path.indexOf('file://') === 0) {
return path
}
if (path.indexOf('/storage/emulated/0/') === 0) {
return path
}
if (path.indexOf('/') === 0) {
var localFilePath = plus.io.convertAbsoluteFileSystem(path)
if (localFilePath !== path) {
return localFilePath
} else {
path = path.substr(1)
}
}
return '_www/' + path
}
export function pathToBase64(path) {
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
if (typeof FileReader === 'function') {
var xhr = new XMLHttpRequest()
xhr.open('GET', path, true)
xhr.responseType = 'blob'
xhr.onload = function() {
if (this.status === 200) {
let fileReader = new FileReader()
fileReader.onload = function(e) {
resolve(e.target.result)
}
fileReader.onerror = reject
fileReader.readAsDataURL(this.response)
}
}
xhr.onerror = reject
xhr.send()
return
}
var canvas = document.createElement('canvas')
var c2x = canvas.getContext('2d')
var img = new Image
img.onload = function() {
canvas.width = img.width
canvas.height = img.height
c2x.drawImage(img, 0, 0)
resolve(canvas.toDataURL())
canvas.height = canvas.width = 0
}
img.onerror = reject
img.src = path
return
}
if (typeof plus === 'object') {
plus.io.resolveLocalFileSystemURL(getLocalFilePath(path), function(entry) {
entry.file(function(file) {
var fileReader = new plus.io.FileReader()
fileReader.onload = function(data) {
resolve(data.target.result)
}
fileReader.onerror = function(error) {
reject(error)
}
fileReader.readAsDataURL(file)
}, function(error) {
reject(error)
})
}, function(error) {
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
wx.getFileSystemManager().readFile({
filePath: path,
encoding: 'base64',
success: function(res) {
resolve('data:image/png;base64,' + res.data)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
export function base64ToPath(base64) {
return new Promise(function(resolve, reject) {
if (typeof window === 'object' && 'document' in window) {
base64 = base64.split(',')
var type = base64[0].match(/:(.*?);/)[1]
var str = atob(base64[1])
var n = str.length
var array = new Uint8Array(n)
while (n--) {
array[n] = str.charCodeAt(n)
}
return resolve((window.URL || window.webkitURL).createObjectURL(new Blob([array], { type: type })))
}
var extName = base64.match(/data\:\S+\/(\S+);/)
if (extName) {
extName = extName[1]
} else {
reject(new Error('base64 error'))
}
var fileName = Date.now() + '.' + extName
if (typeof plus === 'object') {
var bitmap = new plus.nativeObj.Bitmap('bitmap' + Date.now())
bitmap.loadBase64Data(base64, function() {
var filePath = '_doc/uniapp_temp/' + fileName
bitmap.save(filePath, {}, function() {
bitmap.clear()
resolve(filePath)
}, function(error) {
bitmap.clear()
reject(error)
})
}, function(error) {
bitmap.clear()
reject(error)
})
return
}
if (typeof wx === 'object' && wx.canIUse('getFileSystemManager')) {
var filePath = wx.env.USER_DATA_PATH + '/' + fileName
wx.getFileSystemManager().writeFile({
filePath: filePath,
data: base64.replace(/^data:\S+\/\S+;base64,/, ''),
encoding: 'base64',
success: function() {
resolve(filePath)
},
fail: function(error) {
reject(error)
}
})
return
}
reject(new Error('not support'))
})
}
完成!!
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。