用上下文映射鼠标坐标

时间:2019-11-29 12:56:07

标签: javascript mapping

我在画布元素上渲染了一个webgl。渲染后,我希望允许用户使用鼠标在其上绘画(例如,rect)。由于getContext第二次不起作用,因此我在webgl画布上添加了另一个透明画布,我想用鼠标在该透明画布上绘制一个矩形。问题在于,mousedown事件中的坐标与上下文相关元素非常不同

我的画布如下

<div id="container">
  <canvas id="webglCanvas" tabindex='1'></canvas>
  <canvas id="transCanvas" tabindex='1'></canvas>
</div>

获取上下文

var $canvas1 = document.getElementById('transCanvas');
var ctx = $canvas1.getContext("2d");
TransCanvas的

鼠标按下事件。请注意,在按下鼠标事件时,我已经对该区域进行了硬编码。稍后,我将通过鼠标移动等操作。在画布上可以正常工作,并且可以在屏幕上看到矩形。但是鼠标坐标,例如e.clientX和e.clientY处于隐藏状态,并离开了屏幕?

function handleCanvasMouseMove(e) {
   ctx.beginPath();
   ctx.fillStyle = '#F30';
   ctx.fillRect(75, 75, 75, 75);
}

2 个答案:

答案 0 :(得分:0)

请记住,您要将每个轴上范围为[-1..1]的NormalizedDeviceCoords转换为屏幕上的位置。您应用的所有转换都将模型空间vert放在一个以原点为中心的大小为2的立方体中。

所以...我想您也想在相同的空间找回鼠标坐标。如果是这样,则只需构造一个矩阵,然后将屏幕空间位置乘以该矩阵即可得出[-1..1]

范围内的x,y。

过去我做过类似的事情时,我使用了以下一系列转换:

function makeClipToPixMat(width,height)
{
    // flip the Y
    let s1 = mat4.scaling(1,-1,1);

    // translate so space is now [0..2]
    let t1 = mat4.translation(1,1,0);

    // scale so space is now [0..width], [0..height]
    let s2 = mat4.scaling(width/2,height/2,1);

    // s1, then t1, then s2 are applied to the input coords
    let result = mat4.matrixByMatrix(s1,t1);
    result = result.multiply(s2);

    return result;
}

但是从名称中您会注意到,这是一个错误方向的映射。我们希望将屏幕坐标映射到NDC,但是此代码却相反。那怎么办简单-颠倒矩阵或确定所需的一系列变换,然后构建一个矩阵即可一次完成所有操作。这是一个足够简单的转换,因此矩阵求逆似乎是执行如此简单操作的一种极其昂贵的方法。

实际上,这是我使用的功能。反转也可以正常工作,并且可以在运行时间上节省代码大小。

function pixelsToClipspace(width,height)
{
    let scaleMat = mat4.scaling( 1/(width/2), -1/(height/2), 1);
    let translateMat = mat4.translation(-width/2, -height/2, 0); //.transpose();
    let mat = mat4.matrixByMatrix(translateMat, scaleMat);
    return mat;
}

由于我目前有一段时间,因此为您整理了一个快速演示。不好意思,vec4和矩阵有380行代码,而演示仅约35行。 :laughs:也就是说,它完美地说明了矩阵.inverse()函数的昂贵和复杂。

最后:请注意,对于所包含但尚未使用的任何代码的准确性,我不做任何声明。这样的锻炼对我们每个人都有益。您获得了一些理解,我得到了更多的调试测试用例。 :)矩阵是大列矩阵(就像所有好的GL矩阵一样)

"use strict";
window.addEventListener('load', onLoaded, false);

let s2Clip = null;

function onLoaded(evt)
{
	let can = document.querySelector('canvas');
	s2Clip = pixelsToClipspace(can.clientWidth, can.clientHeight);	// use clienWidth/clientHeight to avoid CSS scaling problems
	can.addEventListener('mousemove', onMouse, false);
}


function onMouse(evt)
{
	var rawPos = new vec4(evt.offsetX, evt.offsetY, 0, 1);			
	var trPos = s2Clip.timesVector(rawPos);
	document.getElementById('rawMouse').innerText = `${rawPos.x}, ${rawPos.y}`
	document.getElementById('transMouse').innerText = `${trPos.x.toFixed(2)}, ${trPos.y.toFixed(2)}`
}

function pixelsToClipspace(width,height)
{
	let scaleMat = mat4.scaling( 1/(width/2), -1/(height/2), 1);
	let translateMat = mat4.translation(-width/2, -height/2, 0); //.transpose();
	let mat = mat4.matrixByMatrix(translateMat, scaleMat);
	return mat;
}
// </script>



// <script origSrc='vector.js'>
class vec4
{
	// w=0 for dir (cant translate), w=1 for pos (can)
	constructor(x=0,y=0,z=0,w=0){this.values = [x,y,z,w];}
	clone(){ return new vec4(this.x,this.y,this.z,this.w); }
	get x(){return this.values[0];}
	get y(){return this.values[1];}
	get z(){return this.values[2];}
	get w(){return this.values[3];}
	set x(x){this.values[0]=x;}
	set y(y){this.values[1]=y;}
	set z(z){this.values[2]=z;}
	set w(w){this.values[3]=w;}
	get length(){return Math.hypot( ...this.values ); }
	normalize(){ var l = this.length; if (l>1e-6) {this.x/=l;this.y/=l;this.z/=l;this.w/=l;} return this;}
	scaleBy(scalar){this.x*=scalar;this.y*=scalar;this.z*=scalar;this.w*=scalar;return this;}
	divBy(scalar){this.x/=scalar;this.y/=scalar;this.z/=scalar;this.w/=scalar;return this;}
	add(other){return new vec4(this.x+other.x, this.y+other.y, this.z+other.z, this.w+other.w);}
	sub(other){return new vec4(this.x-other.x, this.y-other.y, this.z-other.z, this.w-other.w);}
	get xyz(){return new vec3(this.x,this.y,this.z);}
	
	toStringN(n){return `[${pad(this.x,n)}, ${pad(this.y,n)}, ${pad(this.z,n)}, ${pad(this.w,n)}]`;}
	
	timesMatrix(matrix)
	{
		let m0 = matrix.getCol(0), m1 = matrix.getCol(1), m2 = matrix.getCol(2), m3 = matrix.getCol(3);
		return new vec4(
			(m0.x*this.x) + (m1.x*this.y) + m2.x*this.z + m3.x*this.w,
			(m0.y*this.x) + (m1.y*this.y) + m2.y*this.z + m3.y*this.w,
			(m0.z*this.x) + (m1.z*this.y) + m2.z*this.z + m3.z*this.w,
			(m0.w*this.x) + (m1.w*this.y) + m2.w*this.z + m3.w*this.w		
		);
	}
	
	vecByMatrix(m) /// operator * (matrix, vector)
	{
		let mc0 = m.getCol(0), mc1=m.getCol(1), mc2=m.getCol(2), mc3=m.getCol(3);
		return new vec4(
			(mc0.x * this.x) + (mc1.x * this.y) + (mc2.x * this.z) + (mc3.x * this.w),
			(mc0.y * this.x) + (mc1.y * this.y) + (mc2.y * this.z) + (mc3.y * this.w),
			(mc0.z * this.x) + (mc1.z * this.y) + (mc2.z * this.z) + (mc3.z * this.w),
			(mc0.w * this.x) + (mc1.w * this.y) + (mc2.w * this.z) + (mc3.w * this.w),
		);
	}
	
	matrixByVec(m) /// operator * (vector, matrix)
	{
		let mCol0 = m.getCol(0), mCol1=m.getCol(1), mCol2=m.getCol(2), mCol3=m.getCol(3);
		return new vec4(
			this.x*mCol0.x + this.y*mCol0.y + this.z*mCol0.z + this.w*mCol0.w,
			this.x*mCol1.x + this.y*mCol1.y + this.z*mCol1.z + this.w*mCol1.w,
			this.x*mCol2.x + this.y*mCol2.y + this.z*mCol2.z + this.w*mCol2.w,
			this.x*mCol3.x + this.y*mCol3.y + this.z*mCol3.z + this.w*mCol3.w
		);
	}
}


class mat4
{
	constructor(xVec4=new vec4(1,0,0,0), yVec4=new vec4(0,1,0,0), zVec4=new vec4(0,0,1,0), wVec4=new vec4(0,0,0,1) )
	{
		this.columns = [ 
							xVec4.clone(), 
							yVec4.clone(), 
							zVec4.clone(), 
							wVec4.clone() 
						];
	}
	getCol(colIndex) {return this.columns[colIndex];}
	setCol(colIndex, newVec) {this.columns[colIndex] = newVec.clone();}
	
	setIdentity()
	{
		let x=new vec4(1,0,0,0);
		let y=new vec4(0,1,0,0);
		let z=new vec4(0,0,1,0);
		let w=new vec4(0,0,0,1);
		this.setCol(0,x);
		this.setCol(0,y);
		this.setCol(0,z);
		this.setCol(0,w);
		return this;
	}
	
	static clone(other)
	{
		var result = new mat4( other.columns[0], other.columns[1], other.columns[2], other.columns[3] );
		return result;
	}
	clone()
	{
		return mat4.clone(this);
	}
	
	static scaling(sx=1,sy=1,sz=1)
	{
		let x = new vec4(sx,0,0,);
		let y = new vec4(0,sy,0,);
		let z = new vec4(0,0,sz,);
		let w = new vec4(0,0,0,1);
		return new mat4(x,y,z,w);
	}
	
	static translation(tx=0,ty=0,tz=0)
	{
		let X = new vec4(1,0,0,tx);
		let Y = new vec4(0,1,0,ty);
		let Z = new vec4(0,0,1,tz);
		let W = new vec4(0,0,0,1);
		return new mat4(X,Y,Z,W);
	}
	
	static matrixByMatrix(m1, m2)
	{
		let mCol0 = m2.getCol(0), mCol1=m2.getCol(1), mCol2=m2.getCol(2), mCol3=m2.getCol(3);
		let X = mCol0.vecByMatrix(m1);
		let Y = mCol1.vecByMatrix(m1);
		let Z = mCol2.vecByMatrix(m1);
		let W = mCol3.vecByMatrix(m1);
		return new mat4(X,Y,Z,W);
	}
	
	static matTimeMat(m1,m2)
	{
		let mc0=m2.getCol(0),mc1=m2.getCol(1),mc2=m2.getCol(2),mc3=m2.getCol(3);
		let x = m1.timesVector(mc0);
		let y = m1.timesVector(mc1);
		let z = m1.timesVector(mc2);
		let w = m1.timesVector(mc3);
		return new mat4(x,y,z,w);
	}
	
	multiply(other,shouldPrepend=false)
	{
		var a=this,b=other,c;
		if (shouldPrepend===true){a=other;b=this;}
		c = mat4.matrixByMatrix(a,b);
		this.columns = c.columns.slice();
		return this;
	}
	
	translate(tx=0,ty=0,tz=0)
	{
		return this.multiply( mat4.translation(tx,ty,tz) );
	}
	
	setScale(sx=1,sy=1,sz=1)
	{
		let x = new vec4(sx,0,0,0);
		let y = new vec4(0,sy,0,0);
		let z = new vec4(0,0,sz,0);
		let w = new vec4(0,0,0,1);
		let tmp = new mat4(x,y,z,w);
		this.columns = tmp.columns.slice();
		return this;
	}
	setTrans(tx=0,ty=0,tz=0)
	{
		let x = new vec4( 1, 0, 0, 0);
		let y = new vec4( 0, 1, 0, 0);
		let z = new vec4( 0, 0, 1, 0);
		let w = new vec4( tx, ty, tz, 1);
		var tmp = new mat4(x,y,z,w);
		this.columns = tmp.columns.slice();
		return this;
	}
	setRotX(degrees)
	{
		let cosa = Math.cos(degrees * 3.141/180);
		let sina = Math.sin(degrees * 3.141/180);
		let x = new vec4(1,0,0,0);
		let y = new vec4(0,cosa,sina,0)
		let z = new vec4(0,-sina,cosa,0);
		let w = new vec4(0,0,0,1);
		let tmp = new mat4(x,y,z,w);
		this.columns = tmp.columns.slice();
		return this;
	}
	setRotY(degrees)
	{
		let cosa = Math.cos(degrees * 3.141/180);
		let sina = Math.sin(degrees * 3.141/180);
		let x = new vec4( cosa,	0,-sina,0);
		let y = new vec4(    0,	1,   0,	0)
		let z = new vec4( sina,	0,cosa,	0);
		let w = new vec4(    0,	0,   0,	1);
		let tmp = new mat4(x,y,z,w);
		this.columns = tmp.columns.slice();
		return this;
	}
	setRotZ(degrees)
	{
		let cosa = Math.cos(degrees * 3.141/180);
		let sina = Math.sin(degrees * 3.141/180);
		let x = new vec4(cosa,sina,0,0);
		let y = new vec4(-sina,cosa,0,0)
		let z = new vec4(0,0,1,0);
		let w = new vec4(0,0,0,1);
		let tmp = new mat4(x,y,z,w);
		this.columns = tmp.columns.slice();
		return this;
	}
	
	scaleEach(sX=1,sY=1,sZ=1,shouldPrepend=false)
	{
		let tmp = new mat4();
		let X = tmp.getCol(0);
		X.x = sX;
		tmp.setCol(0,X);
		let Y = tmp.getCol(1);
		Y.y = sY;
		tmp.setCol(1,Y);
		let Z = tmp.getCol(2);
		Z.z = sZ;
		tmp.setCol(2,Z);
		return this.multiply(tmp, shouldPrepend);
		//return this;
	}
	
	scaleAll(sXYZ, shouldPrepend=false)
	{
		return this.scaleEach(sXYZ,sXYZ,sXYZ,shouldPrepend);
		//return this;
	}
	
	/*
	translate(tX=0, tY=0, tZ=0, shouldPrepend=false)
	{
		let tmp = new mat4();
		let W = tmp.getCol(3);
		W.x = tX;
		W.y = tY;
		W.z = tZ;
		tmp.setCol(3,W);
		return this.multiply(tmp, shouldPrepend);
	}
	*/
	
	timesVector(vector)
	{
		let m0=this.getCol(0), m1=this.getCol(1), m2=this.getCol(2), m3=this.getCol(3);
		return new vec4(
			(vector.x*m0.x) + (vector.y*m0.y) + (vector.z*m0.z) + (vector.w*m0.w),
			(vector.x*m1.x) + (vector.y*m1.y) + (vector.z*m1.z) + (vector.w*m1.w),
			(vector.x*m2.x) + (vector.y*m2.y) + (vector.z*m2.z) + (vector.w*m2.w),
			(vector.x*m3.x) + (vector.y*m3.y) + (vector.z*m3.z) + (vector.w*m3.w)
		);
	}
	
	toString()
	{
		let result = '', row=0,col=0;
		result  = `[ ${this.getCol(0).x}, ${this.getCol(1).x}, ${this.getCol(2).x}, ${this.getCol(3).x} ]\n`;
		result += `[ ${this.getCol(0).y}, ${this.getCol(1).y}, ${this.getCol(2).y}, ${this.getCol(3).y} ]\n`;
		result += `[ ${this.getCol(0).z}, ${this.getCol(1).z}, ${this.getCol(2).z}, ${this.getCol(3).z} ]\n`;
		result += `[ ${this.getCol(0).w}, ${this.getCol(1).w}, ${this.getCol(2).w}, ${this.getCol(3).w} ]\n`;
		return result;
	}
	
	toStrN(n)
	{
		return this.toStringN(n);
	}
	
	toStringN(nDigs)
	{
		let result = '';
		let xVec=this.getCol(0).clone(), 
			yVec=this.getCol(1).clone(), 
			zVec=this.getCol(2).clone(), 
			wVec=this.getCol(3).clone();
			
		let vs=[xVec,yVec,zVec,wVec];
		
		for (var i=0,n=vs.length; i<n; i++)
		{
			vs[i].x = pad(vs[i].x, nDigs);
			vs[i].y = pad(vs[i].y, nDigs);
			vs[i].z = pad(vs[i].z, nDigs);
			vs[i].w = pad(vs[i].w, nDigs);
		}
		result  = `[ ${xVec.x}, ${yVec.x}, ${zVec.x}, ${wVec.x} ]\n`;
		result += `[ ${xVec.y}, ${yVec.y}, ${zVec.y}, ${wVec.y} ]\n`;
		result += `[ ${xVec.z}, ${yVec.z}, ${zVec.z}, ${wVec.z} ]\n`;
		result += `[ ${xVec.w}, ${yVec.w}, ${zVec.w}, ${wVec.w} ]\n`;
		return result;
	}
	asRows(nDigs=2)
	{
		let result = '',xVec=this.getCol(0),yVec=this.getCol(1),zVec=this.getCol(2),wVec=this.getCol(3);
		result = `[${xVec.x.toFixed(nDigs)}, ${xVec.y.toFixed(nDigs)}, ${xVec.z.toFixed(nDigs)}, ${xVec.w.toFixed(nDigs)}]\n`;
		result += `[${yVec.x.toFixed(nDigs)}, ${yVec.y.toFixed(nDigs)}, ${yVec.z.toFixed(nDigs)}, ${yVec.w.toFixed(nDigs)}]\n`;
		result += `[${zVec.x.toFixed(nDigs)}, ${zVec.y.toFixed(nDigs)}, ${zVec.z.toFixed(nDigs)}, ${zVec.w.toFixed(nDigs)}]\n`;
		result += `[${wVec.x.toFixed(nDigs)}, ${wVec.y.toFixed(nDigs)}, ${wVec.z.toFixed(nDigs)}, ${wVec.w.toFixed(nDigs)}]\n`;
		return result;
	}

	transpose()
	{
		let X=this.getCol(0), Y=this.getCol(1), Z=this.getCol(2), W=this.getCol(3);
		
		let tmp = new mat4(
							new vec4(X.x,Y.x,Z.x,W.x),
							new vec4(X.y,Y.y,Z.y,W.y),
							new vec4(X.z,Y.z,Z.z,W.z),
							new vec4(X.w,Y.w,Z.w,W.w),
						);
		this.setCol(0,X);
		this.setCol(1,Y);
		this.setCol(2,Z);
		this.setCol(3,W);
		return tmp; //this.copy(tmp);
	}
	
	inverse()
	{
		let X = this.getCol(0), Y = this.getCol(1), Z = this.getCol(2), W = this.getCol(3);
		let m00=X.x, m01=X.y, m02=X.z, m03=X.w,
			m10=Y.x, m11=Y.y, m12=Y.z, m13=Y.w,
			m20=Z.x, m21=Z.y, m22=Z.z, m23=Z.w,
			m30=W.x, m31=W.y, m32=W.z, m33=W.w;
			
		let tmp_0=m22*m33, tmp_1=m32*m23, tmp_2=m12*m33,
			tmp_3=m32*m13, tmp_4=m12*m23, tmp_5=m22*m13,
			tmp_6=m02*m33, tmp_7=m32*m03, tmp_8=m02*m23,
			tmp_9=m22*m03, tmp_10=m02*m13,tmp_11=m12*m03,
			tmp_12=m20*m31,tmp_13=m30*m21,tmp_14=m10*m31,
			tmp_15=m30*m11,tmp_16=m10*m21,tmp_17=m20*m11,
			tmp_18=m00*m31,tmp_19=m30*m01,tmp_20=m00*m21,
			tmp_21=m20*m01,tmp_22=m00*m11,tmp_23=m10*m01;

			var t0 = (tmp_0 * m11 + tmp_3 * m21 + tmp_4 * m31) - (tmp_1 * m11 + tmp_2 * m21 + tmp_5 * m31);
			var t1 = (tmp_1 * m01 + tmp_6 * m21 + tmp_9 * m31) - (tmp_0 * m01 + tmp_7 * m21 + tmp_8 * m31);
			var t2 = (tmp_2 * m01 + tmp_7 * m11 + tmp_10 * m31) - (tmp_3 * m01 + tmp_6 * m11 + tmp_11 * m31);
			var t3 = (tmp_5 * m01 + tmp_8 * m11 + tmp_11 * m21) - (tmp_4 * m01 + tmp_9 * m11 + tmp_10 * m21);

			var d = 1.0 / (m00 * t0 + m10 * t1 + m20 * t2 + m30 * t3);

			let Xo = new vec4(d*t0, d*t1, d*t2, d*t3);
			//      d * t0,
			//      d * t1,
			//      d * t2,
			//      d * t3,

			let Yo = new vec4(
								  d * ((tmp_1 * m10 + tmp_2 * m20 + tmp_5 * m30) - (tmp_0 * m10 + tmp_3 * m20 + tmp_4 * m30)),
								  d * ((tmp_0 * m00 + tmp_7 * m20 + tmp_8 * m30) - (tmp_1 * m00 + tmp_6 * m20 + tmp_9 * m30)),
								  d * ((tmp_3 * m00 + tmp_6 * m10 + tmp_11 * m30) - (tmp_2 * m00 + tmp_7 * m10 + tmp_10 * m30)),
								  d * ((tmp_4 * m00 + tmp_9 * m10 + tmp_10 * m20) - (tmp_5 * m00 + tmp_8 * m10 + tmp_11 * m20))
							);
							
			let Zo = new vec4(
								  d * ((tmp_12 * m13 + tmp_15 * m23 + tmp_16 * m33) - (tmp_13 * m13 + tmp_14 * m23 + tmp_17 * m33)),
								  d * ((tmp_13 * m03 + tmp_18 * m23 + tmp_21 * m33) - (tmp_12 * m03 + tmp_19 * m23 + tmp_20 * m33)),
								  d * ((tmp_14 * m03 + tmp_19 * m13 + tmp_22 * m33) - (tmp_15 * m03 + tmp_18 * m13 + tmp_23 * m33)),
								  d * ((tmp_17 * m03 + tmp_20 * m13 + tmp_23 * m23) - (tmp_16 * m03 + tmp_21 * m13 + tmp_22 * m23))
							);
							
			let Wo = new vec4(
								  d * ((tmp_14 * m22 + tmp_17 * m32 + tmp_13 * m12) - (tmp_16 * m32 + tmp_12 * m12 + tmp_15 * m22)),
								  d * ((tmp_20 * m32 + tmp_12 * m02 + tmp_19 * m22) - (tmp_18 * m22 + tmp_21 * m32 + tmp_13 * m02)),
								  d * ((tmp_18 * m12 + tmp_23 * m32 + tmp_15 * m02) - (tmp_22 * m32 + tmp_14 * m02 + tmp_19 * m12)),
								  d * ((tmp_22 * m22 + tmp_16 * m02 + tmp_21 * m12) - (tmp_20 * m12 + tmp_23 * m22 + tmp_17 * m02))
							);
			this.columns = [Xo,Yo,Zo,Wo];
			return this;
	}
}

function pad(num, n)
{
	let str = num.toFixed(n);
	if (num >= 0)
		str = " " + str;
	return str;
}
canvas
{
	background-color: #333;
	cursor: crosshair;
}
<body>
	<canvas width='300' height='300'></canvas><br>
	<div>Screen Coords of mouse: <span id='rawMouse'></span></div>
	<div>(2d) NDC of mouse: <span id='transMouse'></span></div>
</body>

答案 1 :(得分:0)

我已经在代码中添加了解释作为注释

// Canvas viewport (4:3)
const DRAW_WIDTH = 800;
const DRAW_HEIGHT = 600;

const RECT_SIZE = 10;
const RECT_FILL = 'black';

let canvas, ctx;

function init() {
  canvas = document.querySelector("canvas");
  // setup canvas drawing space
  // this will give an aspect-ratio to the canvas
  canvas.setAttribute('width', DRAW_WIDTH);
  canvas.setAttribute('height', DRAW_HEIGHT);
  ctx = canvas.getContext('2d');
  // attach listener
  canvas.addEventListener("click", onMouseDown);
}

function onMouseDown(e) {
  // get canvas position and size infos:
  const bbox = canvas.getBoundingClientRect();
  const {
    x: canvasX,
    y: canvasY,
    width: canvasW,
    height: canvasH
  } = bbox;
  // mouse click position
  const {
    clientX: mouseX,
    clientY: mouseY
  } = e;

  // compute ratio between drawing size (viewport) and actual size
  const widthRatio = DRAW_WIDTH / canvasW;
  // compute x relative to your canvas
  const relativeX = (mouseX - canvasX);
  // I advise you to use int values when drawing on canvas
  // thus Math.round
  const finalX = Math.round(widthRatio * relativeX);

  // same for Y-axis
  const heightRatio = DRAW_HEIGHT / canvasH;
  const relativeY = (mouseY - canvasY);
  const finalY = Math.round(heightRatio * relativeY);

  // draw something with that:
  ctx.fillStyle = RECT_FILL;
  ctx.rect(finalX - RECT_SIZE / 2, finalY - RECT_SIZE / 2, RECT_SIZE, RECT_SIZE);
  ctx.fill();
  ctx.closePath();
}

init();
/* set canvas width in the document */

canvas {
  width: 80vw;
  margin-left: 5vw;
  background: coral;
  display: block;
}
<canvas></canvas>