纯前端canvas手绘海报

纯前端canvas手绘海报

源码:

<!doctype html>
<html>
<head>
<meta charset="utf-8">
<meta content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no, viewport-fit=cover" name="viewport">
<!-- 解决ios HTML img标签 src为网络地址无法显示图片问题解决(https) -->
<meta name="referrer" content="no-referrer">
<title>分享赚钱</title>
	.poster-wrap img {
		width: 100%;
	}
</style>
</head>
<body>

<div class="" style="background: #ffbb53;">
	<div class="poster-wrap" style="width:100%;">
		<!-- 设置海报背景图 以动态获取海报高度 -->
		<img src="img/img-hb.png" class="hb-img" style="width: 100%;position: absolute;z-index: -1;">
		<!-- 创建canvas -->
		<canvas id="posterWrap" style="width: 100%;height: 675px;"></canvas>
	</div>
	<div class="">
		<div class="option-wrap">
			<a class="item _savePoster" href="javascript:;">
				<span class="icon download"></span>
				<span class="title">点击生成<br>分享海报</span>
			</a>
			<a class="item _shareTimeLine" href="javascript:;">
				<span class="icon moments"></span>
				<span class="title">点击右上角<br>分享到朋友圈</span>
			</a>
		</div>
	</div>
</div>

<script src="../../static/js/jquery.js"></script>
<script src="../../static/js/layer_mobile/layer.js"></script>
<script>
$(function(){
	//接口获取数据
	function getCode(){
	    $.get(_api + '/user/getCode', function(result){
	    
	    	//海报背景
		    var img_big = 'img/img-hb.png';
		    
		    //解决ios 图片不显示问题 链接后追加?x-oss-process=image/format,jpg
			var img_code = result.data.path+'?x-oss-process=image/format,jpg';
			
			//获取海报图片高度
		    var hb_height = $('.hb-img').height()
		    
		    //将获取海报图片高度hb_height,动态赋值给canvas的高度,保证图片完整展示
		    $('#posterWrap').height(hb_height)
		    
		    //开始绘制canvas
		    var poster = document.getElementById("posterWrap");
			var cxt = poster.getContext("2d");
			
			//做移动端不同dpr适配,解决Canvas在移动端绘制模糊的问题
			let dpr = window.devicePixelRatio; 
			// 获取css的宽高
			let { width: cssWidth, height: cssHeight } = poster.getBoundingClientRect();
			// 根据dpr,扩大canvas画布的像素,使1个canvas像素和1个物理像素相等
			poster.width = dpr * cssWidth;
			poster.height = dpr * cssHeight;
			// 由于画布扩大,canvas的坐标系也跟着扩大,如果按照原先的坐标系绘图内容会缩小
			// 所以需要将绘制比例放大
			cxt.scale(dpr,dpr);
			
			//此处宽高为css中的width和height属性 与 canvas的width和height属性 的
			//不同之处参考https://segmentfault.com/a/1190000019007037
			//定义变量方便后面调用
			var w = document.body.clientWidth;//可见区域宽,动态适配不同分辨率
			console.log(w)
			var h = hb_height;//海报高度,此处海报过长使用的是图片本身的高度
			//如果只做一屏展示的海报可以用
			//var h = document.body.clientHeight;
			console.log(h)
			
			// 底部背景色
			cxt.fillStyle="#ffbb53";
			cxt.fillRect(0, 0 ,w ,h );
			// 海报图
			var imgbig = new Image;
		    imgbig.src = img_big;
		    imgbig.onload = function () {
		    	cxt.drawImage(imgbig, 0,0,w ,h );
				// 二维码
				var imgcode = new Image;
		    	imgcode.src = img_code;
		    	imgcode.onload = function () {
		    		
		    		//canvas中 图形水平居中
		    		//参考:https://blog.csdn.net/wzy890312/article/details/8722538
		    		//公式: (外层图形宽度-需要居中图形宽度)/2
		    		cxt.drawImage(imgcode, (w-160)/2 ,h-230 ,160 ,160 );
		    	}
		    }
			
			//点击按钮生成海报
		    $("._savePoster").click(function() {
		    	//添加延时器,避免ios渲染失败问题
		    	setTimeout(function() {
		    		var img = convertCanvasToImage(poster);
			        $('.poster-wrap').html(img);
			        //提示弹框
			        layer.open({content:'海报已生成,长按海报保存到本地', skin:'msg', time: 2});
			        console.log(img);
		    	} ,1000)
		    })
		    //canvas生成base64图片
			function convertCanvasToImage(canvas) {
				var image = new Image();
		        image.src = canvas.toDataURL("image/png");
		        return image;
		    }
	    });
  	};
	getCode();
});
</script>
</body>
</html>

第二张海报,部分功能做一下记录
纯前端canvas手绘海报

<div class="poster-wrap" style="width:100%;height: 100vh;">
	<canvas id="posterWrap" style="width: 100%;height: 100%;"></canvas>
</div>
<script>
	var img_big = 'https://***/img-lzj.jpg';
	var img_code = 'https://***/img-code.png';
    console.log(img_code)
	var poster = document.getElementById("posterWrap");
	var cxt = poster.getContext("2d");
	let dpr = window.devicePixelRatio; // 假设dpr为2
	// 获取css的宽高
	let { width: cssWidth, height: cssHeight } = poster.getBoundingClientRect();
	// 根据dpr,扩大canvas画布的像素,使1个canvas像素和1个物理像素相等
	poster.width = dpr * cssWidth;
	poster.height = dpr * cssHeight;
	// 由于画布扩大,canvas的坐标系也跟着扩大,如果按照原先的坐标系绘图内容会缩小
	// 所以需要将绘制比例放大
	cxt.scale(dpr,dpr);
	// 可见区域宽
	var w = document.body.clientWidth;
	// 可见区域高
	var h = document.body.clientHeight;
	console.log(w)
	console.log(h)
	
	var imgbig = new Image;
    imgbig.src = img_big;
    imgbig.onload = function () {
    	// 海报图
    	cxt.drawImage(imgbig, 0,0,w ,h-80 );
    	// 底部背景色
		cxt.fillStyle="#ab141b";
		cxt.fillRect(0,h-80 ,w ,80 );
		// 二维码
		var imgcode = new Image;
    	imgcode.src = img_code;
    	imgcode.onload = function () {
    		cxt.drawImage(imgcode, 5 ,h-75 ,70 ,70 );
    	}
    	// 长按图标
    	var iconca = new Image;
    	iconca.src = 'img/icon-ca.png';
    	iconca.onload = function () {
    		cxt.drawImage(iconca, 85 ,h-75 ,8 ,10 );
    	}
    	// 长按文字
    	cxt.beginPath()//注意此处
    	cxt.fillStyle = '#fff';   // 文字填充颜色
        cxt.font = '12px Adobe Ming Std';
        cxt.fillText('长按图片购买',98 ,h-65 );
        cxt.closePath()//注意此处
        // 爆品产值限时购
        var imgTips = new Image;
    	imgTips.src = 'img/img-tips.png';
    	imgTips.onload = function () {
    		cxt.drawImage(imgTips, (w-128),h-80 ,128 ,20 );
    	}
    	// 城市文字
    	cxt.beginPath()//注意此处
    	cxt.fillStyle = '#fff';   // 文字填充颜色
        cxt.font = '16px Adobe Ming Std';
        cxt.fillText('济南 |',85 ,h-46 );
    	cxt.closePath()//注意此处
    	// 商品名称
    	cxt.beginPath()//注意此处
    	cxt.fillStyle = '#fff';   // 文字填充颜色
        cxt.font = '16px Adobe Ming Std';
        cxt.fillText('醉美枣庄辣子鸡',135 ,h-46 );
    	cxt.closePath()//注意此处
    	// 地址图标
    	var imgAddr = new Image;
    	imgAddr.src = 'img/icon-dw.png';
    	imgAddr.onload = function () {
    		cxt.drawImage(imgAddr, 85 ,h-37 ,5 ,8);
    	}
		// 地址标题文字
    	cxt.beginPath()//注意此处
    	cxt.fillStyle = '#fff';   // 文字填充颜色
        cxt.font = '10px Adobe Ming Std';
        cxt.fillText('地址:',92 ,h-30 );
    	cxt.closePath()//注意此处
    	// 地址详情文字
    	cxt.beginPath()//注意此处
    	cxt.fillStyle = '#fff';   // 文字填充颜色
        cxt.font = '10px Adobe Ming Std';
        // cxt.fillText('万科中心401室经十路与奥体东路交叉口东北角',120,578);
        cxt.lineWidth=1; 
		var str = "万科中心401室经十路与奥体东路交叉口东北角(浪潮东门,万科麓山南邻)距奥体东路经十路交叉口180m"
		var lineWidth = 0;
		var canvasWidth = w -140//计算canvas的宽度
		var initHeight = h-30;//绘制字体距离canvas顶部初始的高度
		var lastSubStrIndex= 0; //每次开始截取的字符串的索引
		for(let i=0;i<str.length;i++){ 
			lineWidth+=cxt.measureText(str[i]).width; 
			if(lineWidth>canvasWidth){  
				cxt.fillText(str.substring(lastSubStrIndex,i),120,initHeight);//绘制截取部分
				initHeight+=11;//40为字体的高度
				lineWidth=25;
				lastSubStrIndex=i;
			} 
			// if(i==str.length-1){//绘制剩余部分
			// 	cxt.fillText(str.substring(lastSubStrIndex,i+1),120,initHeight);
			// }
		}
    	cxt.closePath()//注意此处
    	// 商家电话文字
    	cxt.beginPath()//注意此处
    	cxt.fillStyle = '#fff';   // 文字填充颜色
        cxt.font = '11px Adobe Ming Std';
        cxt.fillText('商家电话:15555555555',85 ,h-5 );
    	cxt.closePath()//注意此处

    }
	setTimeout(function() {
		var img = convertCanvasToImage(poster);
        $('.poster-wrap').html(img);
        layer.open({content:'海报已生成,长按海报保存到本地', skin:'msg', time: 2});
        console.log(img);
	}, 1000);
        
	function convertCanvasToImage(canvas) {
		var image = new Image();
        image.src = canvas.toDataURL("image/png");
        return image;
    }
</script>

实现方式基本相同,多了一个文字超出换行功能:
主要参考https://www.cnblogs.com/hejun26/p/10239649.html

// 地址详情文字
cxt.beginPath()//注意此处
cxt.fillStyle = '#fff';   // 文字填充颜色
cxt.font = '10px Adobe Ming Std';
// cxt.fillText('万科中心401室经十路与奥体东路交叉口东北角',120,578);
cxt.lineWidth=1; 
var str = "万科中心401室经十路与奥体东路交叉口东北角(浪潮东门,万科麓山南邻)距奥体东路经十路交叉口180m"
var lineWidth = 0;
var canvasWidth = w -140//计算canvas的宽度
var initHeight = h-30;//绘制字体距离canvas顶部初始的高度
var lastSubStrIndex= 0; //每次开始截取的字符串的索引
for(let i=0;i<str.length;i++){ 
	lineWidth+=cxt.measureText(str[i]).width; 
	if(lineWidth>canvasWidth){  
	cxt.fillText(str.substring(lastSubStrIndex,i),120,initHeight);//绘制截取部分
	initHeight+=11;//40为字体的高度
	lineWidth=25;
	lastSubStrIndex=i;
} 
// if(i==str.length-1){//绘制剩余部分
// 	cxt.fillText(str.substring(lastSubStrIndex,i+1),120,initHeight);
// }
}
cxt.closePath()//注意此处

另外 beginPath() 和 closePath()的使用:
主要参考:https://blog.csdn.net/qq_33974741/article/details/84474878
canvas 绘制可以通过 JS 代码来控制(其实应该说 HTML 5 只是提供了个容器,绘制只能在 JS 里完成),而 JS 提供了两个函数,beginPath() 和 closePath() ,这两个函数可以起到类似 div 的作用,用它来把每个圆圈包围,就可以绘制不同颜色的图形了。
另外,在测试中,笔者还发现,当去掉所有的 closePath() 时,只保留 beginPath() ,一样可以达到目的,也就是说,当遇到beginPath() 时,会自动重新开始下一个图形的绘制(保留 closePath() ,去掉 beginPath() 时,效果相当于不添加这两个函数)。但是笔者个人认为,最好两个函数都添加,这样会提高代码可读性,使代码更规范。

官方解释 :
beginPath() 方法开始一条路径,或重置当前的路径。
closePath() 方法创建从当前点到开始点的路径。
个人理解 beginPath()相当于开始的点,closePath()相当于结束的点,搭配使用应该可以形成一个封闭的路径,这样在一个父图形中可以绘制多个不同的子图形。

海报主要绘制参考:
https://www.cnblogs.com/hejun26/p/10239649.html
Canvas在移动端绘制模糊的原因与解决办法参考:
https://segmentfault.com/a/1190000019007037

上一篇:Html5学习系列


下一篇:从零开始开发一款H5小游戏(一) 重温canvas的基础用法