关于canvas:canvas-笔记

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title></head><body> <canvas id="canvas" width="600" height="800" style="background-color: rgb(38, 139, 139); margin:20px"> </canvas> <script> /** @type {HTMLCanvasElement} */ let cDom=document.getElementById('canvas') let context=cDom.getContext('2d')// 第一局部 // 绘制线条 // context.moveTo(10,20) // context.lineTo(70,30) // context.lineWidth=12 // context.strokeStyle="red" // context.stroke() // context.moveTo(50,80) // context.lineTo(50,120) // context.lineWidth=7 // context.strokeStyle='pink' // context.stroke() //第二个图案, // context.beginPath() // context.moveTo(10,120) // context.lineTo(100,120) //新建门路API // context.lineWidth=5 // context.strokeStyle='black' // context.stroke() // 三角形, // context.beginPath() // context.moveTo(10,150) // context.lineTo(120,150) // context.lineTo(60,180) // context.closePath() //线路闭合API // context.fillStyle='green' //填充色彩 // context.fill() //填充 // context.lineWidth=3 // context.strokeStyle='pink' // context.stroke() // 矩形 // context.beginPath() // context.rect(20,180,40,50) // context.lineWidth=1 // context.strokeStyle='red' // context.stroke() // 空心矩形组合API // context.beginPath() // context.lineWidth=2 // context.strokeStyle="pink" // context.strokeRect(20, 240, 40, 50); // 实心矩形组合API // context.beginPath() // context.fillStyle="blue" // context.fillRect(30,200,40,50); // 第二局部弧线 //actTo 弧线原理: 三个控制点,x1,x2,x3,同时内切于 x1-x2 和 x2-x3 两条直线的内切圆 // context.beginPath(); // context.moveTo(200, 10); // context.arcTo(200, 100, 100, 10, 48); // context.lineWidth=3; // context.strokeStyle='pink'; // context.stroke(); // 辅助线 // context.beginPath(); // context.moveTo(200, 10); // context.lineTo(200, 100); // context.lineTo(100, 10); // context.closePath(); // context.lineWidth=1; // context.strokeStyle='red'; // context.stroke(); // arc画弧原理:确定圆心,半径,起始角度,终止角度,方向 // context.beginPath(); // context.arc(100, 200, 30, 0, Math.PI/3, false); // context.stroke(); // 二阶贝塞尔曲线:终点,起点,控制点 // context.beginPath(); // context.moveTo(130, 250); // context.quadraticCurveTo(130, 280, 160, 265); // context.stroke(); //3 绘图款式 // context.beginPath(); // context.moveTo(10, 20); // context.lineTo(200, 300); // context.lineWidth=5; // 默认值 // context.lineCap='butt'; // 圆角 // context.lineCap='round'; // 矩形 // context.lineCap='square'; // 虚线 // context.setLineDash([7,10,15,20,30]); // context.stroke(); // 线性突变 // context.beginPath(); // x1,y1,x2,y2 // let gradient= context.createLinearGradient(50,150,100,290) // gradient.addColorStop(0, 'pink'); // gradient.addColorStop(0.7, 'red'); // gradient.addColorStop(1, 'pink'); // context.fillStyle=gradient; // context.fillRect(25,150,200,150); // 径向突变 // context.beginPath(); // 圆心一,半径一,圆心二,半径二 // let gradient2=context.createRadialGradient(190, 440, 5, 140, 450, 80); // gradient2.addColorStop(0,'green') // gradient2.addColorStop(0.2,'pink') // gradient2.addColorStop(0.4,'green') // gradient2.addColorStop(0.6,'pink') // gradient2.addColorStop(0.8,'green') // gradient2.addColorStop(1,'pink') // context.fillStyle=gradient2; // context.fillRect(40,350,200,200); // 纹理款式 填充图片 // context.beginPath(); // let img1=new Image(); // img1.src='./mao.gif' // img1.onload=function(){ // let pattern=context.createPattern(img1, 'repeat'); // context.fillStyle=pattern; // context.fillRect(30,600,200,120); // } // 绘制文本 // context.beginPath(); // 字体款式 // context.font='200px Times New Roman'; // 暗影右偏移 // context.shadowOffsetX=2; // 暗影下偏移 // context.shadowOffsetY=6; // 暗影含糊度 // context.shadowBlur=6; // 暗影色彩 // context.shadowColor='red'; // 填充文本 // context.fillText(text, x, y, maxWidth); // 轮廓文本 // context.strokeText('hello', 150, 150); // 纹理填充 // let img=new Image(); // img.src='./mao.gif' // img.onload=function(){ // let pattern=context.createPattern(img, 'repeat'); // context.fillStyle=pattern; // context.fillText('hello', 150, 300); // } // 绘制图片 // context.beginPath(); // let img=new Image() // img.src="./tuzi.png" // img.onload=function(){ // 图片,x,y,宽,高, // context.drawImage(img, 10, 20,100,100); // 从图片上扣取某局部放到画布上:图片,图片里的x,图片的y,剪裁的宽度,剪裁的高度,画布的x,画布的y,搁置的宽度,搁置的高度 // context.drawImage(img, 100, 500,100,100,100,500,100,100); // } // 进阶 // 变形 平移:translate,选装:rotate,缩放;scale // 平移 // context.beginPath(); // context.fillStyle='red'; // 状态保留:坐标,色彩 // context.save(); // context.fillRect(0,0,100,100); // context.translate(400, 400); // context.fillRect(0,0,100,100); //复原之前的状态,当然也能够 context.translate(-400 -400); // context.restore(); // context.fillStyle='black'; // context.fillRect(0,0,50,50); // 选装 // context.beginPath(); // context.fillStyle='yellow'; // context.save(); // context.rotate( 30 * Math.PI / 180); // context.fillRect(100,100,40,40); // context.restore(); // context.fillRect(100,100,40,40); // 缩放 // context.beginPath(); // context.fillStyle='blue'; // context.save(); // context.scale(1.5, 2); // context.fillRect(200,200,50,50); // context.restore(); // context.fillRect(200,200,50,50); // transform 线性代数矩阵变换 // a c e // b d f // 0 0 1 // a 程度缩放 1 // b 程度歪斜 0 // c 垂直歪斜 0 // d 垂直缩放 1 // e 程度位移 0 // f 垂直位移 0 // context.beginPath(); // context.save(); // context.transform(1, 0, 0, 1, 100, 200); // context.fillStyle='pink'; // context.fillRect(0,0,100,100); // 合成: 共提供了26中混排模式,用的时候现查就能够了。 // 后绘制的放到上面 destination-over // 后绘制的镂空先绘制的 destination-out // context.globalCompositeOperation='destination-over'; // context.fillStyle='blue'; // context.fillRect(100,100,200,200); // context.fillStyle='red'; // context.fillRect(200,200,200,200 ); // 裁剪 // context.lineWidth='1px'; // context.rect(0, 0, 150, 150); // context.stroke(); // context.clip(); //只在上方的闭合曲线内显示 // context.fillStyle='blue'; // context.font='44px sans-serif'; // context.fillText('hello', 100, 100); </script></body></html>

September 22, 2023 · 3 min · jiezi

关于canvas:Canvas实现以鼠标当前位置为原点缩放及画布拖动矩阵变换

Canvas实现以鼠标以后地位为原点缩放及画布拖动(矩阵变换)前言在之前的Canvas鼠标滚轮缩放以及画布拖动(图文并茂版)一文中我已经介绍过一种实现鼠标滚轮缩放及画布拖动的办法,这种形式利用的是Canvas的api进行缩放和拖动,并且实现原理了解起来也比拟形象。 本文将介绍一种更加便捷、通用的形式来实现鼠标滚轮缩放及画布拖动的形式,这就是矩阵变换。 矩阵变换矩阵变换就是一种坐标系的转换,因而在图形学中,就会应用矩阵变换来进行图形的变动,比方平移、缩放、旋转。接下来我会重点介绍本文所波及到的平移和缩放变换。 平移假如有一个点 P(x, y),平移到点 P'(x1, y1),在程度方向位移 dx,垂直方向的位移 dy,那么就能够失去如下公式: $$x1 = x + dx \\y1 = y + dy$$ 如果将上述变换公式转换为矩阵变换的模式能够失去如下矩阵变换公式: $$\left[\begin{matrix} 1 & 0 & dx \\ 0 & 1 & dy \\ 0 & 0 & 1\end{matrix}\right]\left[\begin{matrix} x \\ y \\ 1\end{matrix}\right]= \left[\begin{matrix} x+dx \\ y+dy \\ 1 \\\end{matrix}\right]$$ 在坐标系中,一个点就相当于一个向量,从 P 点到 P' 点的变换能够通过: $$变换矩阵 * P点 = P'点$$ 的模式来表白。 $$A = \left[\begin{matrix} 1 & 0 & dx \\ 0 & 1 & dy \\ 0 & 0 & 1\end{matrix}\right]$$ ...

March 26, 2023 · 3 min · jiezi

关于canvas:Canvas鼠标滚轮缩放以及画布拖动图文并茂版

Canvas鼠标滚轮缩放以及画布拖动本文会带大家意识Canvas中罕用的坐标变换办法 translate 和 scale,并联合这两个办法,实现鼠标滚轮缩放以及画布拖动性能。 <div align=center> <img src="https://p6-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/cf6d1ae14ebe42d0b31cb9b6542943d4~tplv-k3u1fbpfcp-watermark.image?" alt="" /></div> Canvas的坐标变换Canvas 绘图的缩放以及画布拖动次要通过 CanvasRenderingContext2D 提供的 translate 和 scale 两个办法实现的,先来意识下这两个办法。 translate 办法语法: translate(x, y)translate 的用法记住一句话: translate 办法从新映射画布上的(0, 0)地位。说白了就是把画布的原点挪动到了 translate 办法指定的坐标,之后所有图形的绘制都会以该坐标进行参照。 举个例子: const canvas = document.getElementById('canvas');const ctx = canvas.getContext('2d');canvas.width = 600;canvas.height = 400;ctx.fillStyle = 'red';ctx.fillRect(50, 50, 50, 50);ctx.translate(50, 50);ctx.fillStyle = 'green';ctx.fillRect(50, 50, 50, 50);开始的时候,Canvas 容器原点和绘图原点重合,绘制一个背景色为红色,原点坐标(50, 50),长宽各为 50 的矩形,接着调用 translate 办法将绘图原点沿程度和纵向各偏移50,再绘制一个背景色是绿色,原点坐标(50, 50),长宽各为 50 的矩形,示意图如下,其中灰色的背景为 Canvas 区域。 须要留神的是,如果此时持续调用 translate 办法进行偏移操作,后续的偏移会基于原来偏移的根底上进行的。 ctx.fillStyle = 'red';ctx.fillRect(50, 50, 50, 50);// 第一次坐标系偏移ctx.translate(50, 50);ctx.fillStyle = 'green';ctx.fillRect(50, 50, 50, 50);// 第二次坐标系偏移ctx.translate(50, 50);ctx.fillStyle = 'blue';ctx.fillRect(50, 50, 50, 50); ...

February 15, 2023 · 5 min · jiezi

关于canvas:canvas画板之画笔的多种效果

前言我之前做了一个画板,曾经迭代了两个版本,但既然是画板,如果只有一种画笔就显得太枯燥了,我就收罗了一下网上的各种计划和本人的一些想法,目前做出了5种款式,包含根底的总共6种,当然有了一些思路后,后续会持续减少。我会在本文具体阐明实现思路和具体代码,6种款式包含: 根底单色荧光多色画笔喷雾蜡笔泡泡预览预览地址:https://songlh.top/paint-board/ 源码:https://github.com/LHRUN/paint-board 欢送Star⭐️ 根底单色画笔的根底实现,除了点与点之间的连贯,还须要留神两点 首先是在鼠标挪动时计算以后挪动的速度,而后依据速度计算线宽,这个是为了实现鼠标挪动快,线宽就变窄,挪动慢,线宽就恢复正常这个成果为了防止直线连接点成果不好,我会采纳贝塞尔曲线进行连贯 /** * 鼠标挪动时增加新的坐标 * @param position */addPosition(position: MousePosition) { this.positions.push(position) // 解决当火线宽 if (this.positions.length > 1) { // 计算挪动速度 const mouseSpeed = this._computedSpeed( this.positions[this.positions.length - 2], this.positions[this.positions.length - 1] ) // 计算线宽 const lineWidth = this._computedLineWidth(mouseSpeed) this.lineWidths.push(lineWidth) }}/** * 计算挪动速度 * @param start 终点 * @param end 起点 */_computedSpeed(start: MousePosition, end: MousePosition) { // 获取间隔 const moveDistance = getDistance(start, end) const curTime = Date.now() // 获取挪动间隔时间 lastMoveTime:最初鼠标挪动工夫 const moveTime = curTime - this.lastMoveTime // 计算速度 const mouseSpeed = moveDistance / moveTime // 更新最初挪动工夫 this.lastMoveTime = curTime return mouseSpeed}/** * 计算画笔宽度 * @param speed 鼠标挪动速度 */_computedLineWidth(speed: number) { let lineWidth = 0 const minWidth = this.minWidth const maxWidth = this.maxWidth if (speed >= this.maxSpeed) { lineWidth = minWidth } else if (speed <= this.minSpeed) { lineWidth = maxWidth } else { lineWidth = maxWidth - (speed / this.maxSpeed) * maxWidth } lineWidth = lineWidth * (1 / 3) + this.lastLineWidth * (2 / 3) this.lastLineWidth = lineWidth return lineWidth}渲染时就遍历所有坐标 ...

December 19, 2022 · 5 min · jiezi

关于canvas:你的代码怎么下起了雨

大多数的程序员都会有一个本人的集体网站,咱们想要在本人的网站上面去刻画一个酷炫的背景,咱们可能会应用一些炫酷的图片,或者叠加一个视频背景,亦或是通过css3 来进行手动绘制,这些计划都各有利弊,在呈现canvas之后,咱们呈现了一种新的可能,咱们能够通过canvas绘制一些十分炫酷的背景,有意思的是,咱们还能够通过鼠标或者键盘事件与其交互,这样,咱们就领有了一种绘制动静背景的能力。 什么是canvas其实它是Html5新增的一个标签,翻译过去就是画布的意思,他就是一张画布,须要开发者们手动绘制,咱们如何绘制呢?很显著作为一个标签能力无限,咱们须要应用javascript对其进行加工绘制,所以咱们很好了解了,canvas是纸,而JavaScript是笔,通过两者的单干能力实现绘制工作。 咱们平时用的网页截图、H5游戏、前端动效、可视化图表...,都有canvas 的利用场景,所以其性能是特地弱小的,同时其大量的工作都是在GPU当中进行,一般来说性能是很高的,在咱们去做一些对性能要求更高的场景下,是一种不错的抉择,当然,本次咱们不是为了来解说canvas,这里就不做过多的解说,接下来咱们来进入实战。 实战残缺的代码曾经放在文章开端,能够通过码上掘金间接观看,咱们来细聊一下其实现思路和过程,逐渐拆解进去,看完置信你也能够轻松绘制出这样的一个成果。 1. 根底筹备工作当然最根底的是咱们须要一个canvas标签了,所以第一步须要创立一个标签并且给绑定一个id属性不便JavaScript获取到他有了标签之后,咱们须要干嘛呢,总结下来是这几部,获取canvas节点,获取窗口的宽高,给canvas设置宽高,同时拿到其绘制的上下文对象,咱们要操作他须要调用其中的各种api办法。 /* 1.获取节点 */const canvs = document.getElementById('app')/* 2.设置canvas的宽高 */({ innerWidth: cvs.width, innerHeight: cvs.height } = window);/* 3.获取canvans绘制上下文对象 */const ctx = cvs.getContext('2d');其次咱们是渲染不同的文字,所以咱们定义一下咱们须要渲染哪些文字,同时,每次渲染的时候,随机获取一个,所以,咱们写一个办法,用于每次随机拿到一个文字。 /* 4. 筹备一个获取随机文字办法 */function getRandomChar(){ const str = '码上掘金永远滴神YYDS' return str[Math.floor(Math.random() * str.length)]}咱们心愿每次绘制的文字色彩也是不同的色彩,所以咱们须要筹备一个获取随机色彩的办法,置信这里都是很简略的。 /* 5.筹备一个获取随机色彩的办法 */function getRandomColor() { const colors = [ '#33B5E5', '#0099CC', '#AA66CC', '#9933CC', '#669900', '#FFBB33', '#FF8800', '#FF4444', '#CC0000' ] return colors[Math.floor(Math.random() * colors.length)]}有了这些筹备工作,上面进入外围的绘制过程,咱们持续 2.绘制过程有了后面的步骤,咱们曾经有了根底的一些工具办法筹备了,上面咱们来开始绘制,在此之前咱们须要对其略微思考一下咱们应该怎么做,上面看看这张简略图来了解,咱们如果下实现这样的一个成果,咱们最根底的是须要啥样的。 咱们能想到的是根底场景下,咱们一行可能就须要这么多,要实现下面的成果,只须要铺满屏幕并且让他们的y点的程序不同即可,那么对于根底的这几列,咱们须要哪些货色呢,首先咱们得本人定义一列须要多宽的间隔,其次就能够通过窗口的宽度/一列宽晓得咱们最多能够一行放多少列了,其次咱们须要晓得每一列的(x,y)点的坐标,因为咱们的绘制是整个窗口,坐标从左上角(0,0)开始计算,很显著第一行的状况下,所有的y坐标是怎么计算的呢,第一列的y就是一个字体高度,第二列就是两个以此类推, 然而x的坐标也很简略,就是一列的宽度*你是第几列即可,咱们就轻松算出了第一列的(x,y),接下来咱们就让第一列绘制进去,代码中咱们会有具体的正文。/* 6. 设置一列宽度并计算一行须要多少列 */const columnWidth = 30;const columnCount = Math.floor(window.innerWidth / columnWidth)咱们定义一列为30,同时计算出了一列能够最多columnCoun列,所以咱们开始绘制,只须要循环columnCoun次就能够画出一整排的字了,同时这里咱们确定他的(x,y)坐标,上面开始绘制第一行:/* 7.开始绘制 */function draw(){ /* 定义一下字体大小, 同时y坐标每行的所需高度就是字体大小的高度 */ const fontsize = 16; /* 获取一个随机色彩用于字体绘制 */ ctx.fillStyle = getRandomColor(); /* 设置字体格局和字体大小,这个随便本人设置 */ ctx.font = `${fontsize}px "Microsoft YaHei"`; /* 循环列数次,同时每次计算出xy的坐标,因为是第一行所以y默认就是 1 * 字体高度 */ for (let i = 0; i < columnCount; i++) { const x = i * columnWidth; const y = 1 * fontsize /* 绘制 三个参数别离是 字符 x坐标 y坐标 */ ctx.fillText(getRandomChar(), x, y) }}当咱们实现这个办法之后,咱们只须要调用这个办法就能够绘制出一行文字了,例如下图: ...

October 28, 2022 · 2 min · jiezi

关于canvas:手把手写个Canvas验证码组件并且与Vue解耦

明天下班摸鱼,咱们一起来写一个利用canvas画布纯前端实现验证码性能。开始之前咱们先撸一撸开发的流程,先干什么再干什么,把思路撸分明了,干什么都容易。 当然必须先有一个canvas画布接着再整几个数字+英文下来 咱们先来个空标签,先占个位,当前这里就会有一个canvas。 <span id="verify"></span>并且咱们定义了一个id为verify。接下开始整canavs了,咱们须要把canvas变到span外面。没有canvas,就创立一个。 const node = document.getElementById('verify')const canvas = document.createElement('canvas');node.appendChild(canvas)canvas.width = 120canvas.height = 60并且咱们棘手把它塞进了span外面。到这里咱们的小指标第一步曾经实现了。接下去再整几个数字+英文下来就完事了。要在canvas上整点货色,当然先要拿到canvas。 const ctx = canvas.getContext('2d');而后咱们间接进入小指标的第二步。 ctx.fillText('jsx123', 0, 0);到这里预计有人要喷我了,你TM整的什么玩意儿。验证码怎么是写死的,不应该是随机的吗。是的,没错。上面咱们就来实现小指标中的细节。咱们须要失去一个随机的数字+字母组合,字母大小写也是随机的。先定义一段字符串。 const str = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789'到这里咱们只须要随机选取字符串中的字符即可。咱们封装一个办法,来返回指定长度的随机字符串。 function getString(length) { let str = 'abacdefghjklmnopqrstuvwxyzABCDEFGHJKLMNOPQRSTUVWXYZ0123456789' let string = '' for (let i = 0; i < length; i++) { string += str[Math.floor(Math.random() * str.length)] } return string}控制台跑一下,是咱们想要的后果,改一下咱们的代码。 ctx.fillText(getString(6), 0, 0);这样咱们每次刷新,呈现的验证码都是随机的。然而验证码的色彩不是随机的,而且验证码排列的形式也不是咱们想要的。咱们心愿验证码的每个字符的色彩都是随机的,而且每个字符须要带点旋转,以进步机器辨认的难度。 // 生成随机色 function randomColor() { return `#${((Math.random() * (0xFFFFFF).toString(10)).toString(16)).slice(-6)}` }不晓得怎么随机生成16进制色彩的,能够间接百度。或者应用随机获取0-255整数的办法取得rgb色彩。这里咱们须要遍历验证码的每一个字符。 // 生成指定范畴内的随机数function getRandom(min, max) { return Math.floor(Math.random() * (max - min + 1)) + min}let code = getString(6)for (let i = 0; i < code.length; i++) { ctx.save() ctx.strokeStyle = randomColor() ctx.rotate(getRandom(-30, 30) * Math.PI / 180) ctx.fillText(code[i], 0, 0); ctx.restore()}到这一步咱们一次性实现了验证码字符的色彩随机和角度旋转。为了烦扰机器辨认,咱们须要增加杂线。 ...

August 20, 2022 · 1 min · jiezi

关于canvas:canvas-2-image的使用小心得

在开发中遇到一个设置通明色的需要,大略形容就是一张图,而后再给一个色彩,把这个图片上所有这个色彩的像素点设置为通明色,如下图实现思路就是将图片画到canvas上,而后遍历图片像素信息,将指定的rgb的alpha设置为0,即设置为全透明。 const canvas = document.createElement('canvas'); const context = canvas.getContext('2d'); const img = new Image(); img.crossOrigin = 'Anonymous'; img.src = src; img.onload = () => { const w = img.width; const h = img.height; context.drawImage(img, 0, 0); const imgData = context.getImageData(0, 0, w, h); const pixcount = w * h; for (let i = 0; i < pixcount * 4; i += 4) { const r = imgData.data[i]; const g = imgData.data[i + 1]; const b = imgData.data[i + 2]; if ( Math.abs(r - color.r) <= dis && Math.abs(g - color.g) <= dis && Math.abs(b - color.b) <= dis ) { imgData.data[i + 3] = 0; } } // 转成base64 canvas.toDataURL('image/webp')} 在此性能实现过程中发现几个问题:1、canvas.toDataURL(type, encoderOptions) 图片格式type默认为image/png, encoderOptions 图片品质默认0.92 然而此参数在type为image/png时是不失效的,只在 image/jpeg和image/webp格局下失效2、在应用此形式的过程中发现,原始图片jpeg只有1M,设置通明色后图片格式为png时图片体积增 大到10M, 为了解决此问题,做了以下尝试: 缩放canvas,导出仍旧是原canvas大小,图片还是10M,此计划不行♂️ 设置为jpeg格局,会失落通明像素,通明像素变成了彩色,过后的我太蠢了jpeg怎么会有通明像素呢 设置为image/webp格局,图片体积900K, 完满,然而请留神此type在Safari上是不反对,在safari上canvas.toDataURL('image/webp')会默认变成canvas.toDataURL('image/png') ...

May 13, 2022 · 1 min · jiezi

关于canvas:支付宝小程序-横屏电子版签字-canvas实现

背景形容:业务须要在支付宝小程序横屏签字,然而目前支付宝不反对横屏,所以只能用款式疏导用户实现成果: 思路:应用transform和translate 去旋转页面,这样就能满足款式的需要,看着是没啥问题,然而当在canvas上绘画的时候,就会看到,我写的是一横,展现确实是一竖起因:页面是旋转了,然而坐标零碎没有扭转 解决方案:调整坐标零碎可借由rotate逆向旋转90°,而后由translate平移坐标系。 context.rotate((degree * Math.PI) / 180);switch (degree) { // 页面顺时针旋转90°后,画布左上角的原点地位落到了屏幕的右上角(此时宽高调换),围绕原点逆时针旋转90°后,画布与原地位垂直,居于屏幕右侧,须要向左平移画布以后高度雷同的间隔。 case -90: context.translate(-height, 0); break; // 页面逆时针旋转90°后,画布左上角的原点地位落到了屏幕的左下角(此时宽高调换),围绕原点顺时针旋转90°后,画布与原地位垂直,居于屏幕下侧,须要向上平移画布以后宽度雷同的间隔。 case 90: context.translate(0, -width); break; // 页面顺逆时针旋转180°回到了同一个地位(即页面倒立),画布左上角的原点地位落到了屏幕的右下角(此时宽高不变),围绕原点反方向旋转180°后,画布与原地位平行,居于屏幕右侧的下侧,须要向左平移画布宽度雷同的间隔,向右平移画布高度的间隔。 case -180: case 180: context.translate(-width, -height);}上面是我的代码: <canvas ref="cxt" id="xfCanvas" class="xfcanvas" :style="{ height: canvasw -52+'px' , width: '100%'}" @touchstart="touchStart" @touchmove="touchMove" @touchend="touchEnd" :disable-scroll="true"> </canvas> <view class="btn"> <view @click="resetImg">重签</view> <view @click="goSignDate">下一步签日期</view> </view> mounted() { let that = this; uni.getSystemInfo({ success: function(res) { that.canvasw = res.windowWidth; that.canvash = res.windowHeight; console.log(that.canvasw, that.canvash, '画布尺寸'); that.$nextTick(() => { that.ctx = uni.createCanvasContext('xfCanvas');//========重点在这里 扭转坐标零碎,52的高度是我操作按钮的高度============= that.ctx.rotate((-90 * Math.PI) / 180); that.ctx.translate(-that.canvash + that.canvasw + 52, 0) that.ctx.setStrokeStyle('#000'); // 色彩 that.ctx.setLineWidth(8); // 粗细 that.ctx.setLineCap('round'); // 线头形态 that.ctx.setLineJoin('round'); // 交叉处形态 }); }, }); },methods:{ touchStart(e) { console.log('+++++++++++刚开始触摸'); this.startX = e.changedTouches[0].x; this.startY = e.changedTouches[0].y; this.ctx.beginPath(); this.ctx.moveTo(this.startX, this.startY); // 找到终点 }, touchMove(e) { console.log('+++++++++++触摸进行中'); this.isSign = true let moveX = e.changedTouches[0].x; let moveY = e.changedTouches[0].y; this.ctx.lineTo(moveX, moveY); // 找到起点 this.ctx.stroke(); // 描述门路 this.ctx.draw(true, function(ret) {}); }, touchEnd() { this.isSign = true console.log('+++++完结啦', this.ctx); },}最初实现成果 ...

April 13, 2022 · 1 min · jiezi

关于canvas:canvas画布和其他元素有缝隙解决

如图:canvas和按钮之间有缝隙 解决: 给canvas画布增加 display: block 即可

April 13, 2022 · 1 min · jiezi

关于canvas:如何用手机模拟激光笔

引言你是否留神到,这些年咱们越来越少在演讲中看到演讲人用激光笔给观众批示所讲的内容。先说激光笔,激光笔的工作原理是射出一束激光,照射到幕布上并反射到观众的眼睛里,于是大家能够看到一个很亮的红色光点。但当初因为大尺寸屏幕越来越便宜,咱们越来越少应用幕布这种传统投影显示设施了,毕竟屏幕的显示成果要更好。而屏幕为了保障良好的显示成果,往往都会在外表的玻璃上应用大量抗反射技术。这些抗反射技术的使用大大削弱了激光的反射,所以最终用户看到的红色光点就不那么显眼了,激光笔的成果大打折扣。 那么有没有可能用大家出门惟一违心携带的手机来代替激光笔呢?我尝试了上面两种计划。 3D 模仿计划简略来说就是用手机内置的姿势传感器和加速度传感器来构建出手机和显示屏在三维空间中的方位和姿势,再以此模拟计算如果从手机收回一束激光,会照射到屏幕上的哪个地位,并在屏幕上的相应地位绘制一个红点,这样来实现手机模仿激光笔的成果。 如上图所示,假如一块屏幕的两条邻边别离平行与 X 轴和 Z 轴。当手机从 A 点沿着屏幕的一条边挪动到 B 点,再从 B 点沿着屏幕的另一条边挪动到 C 点,咱们就能够计算出屏幕在以 A 点为原点的三维空间中的具体位置。 计算方法其实很简略,比方当咱们计算从 A 到 B 的过程时,只须要用手机上的加速度传感器取出时时刻刻手机在 x 轴方向上的加速度,再乘以工夫,就能够失去手机时时刻刻在 x 轴方向的速度,速度再乘以工夫就能够得出手机在 x 轴方向挪动了多少间隔。简略说就是对手机在 x 轴方向的加速度做了两次工夫维度上的积分。 可能通过把手机从 A 点挪动到 B 点和 C 点来计算出屏幕的地位,天然也能够在接下来计算出任意时刻手机绝对屏幕的空间地位和姿势方向,并模拟计算从手机射出一束激光会照射到屏幕的哪个地位。 可是,试验过后我发现这个计划尽管在实践上是可行的,但因为累积误差的存在,理论并不可行。 咱们拿手机从 A 点挪动到 B 点这个最根底的场景来举例。当一个人拿着手机从 A 挪动到 B 的时候,手机的加速度、速度和挪动间隔能够用上面三幅图来形容。 横轴 t 示意工夫,纵轴 a、v、d 别离示意手机在 x 方向的加速度、速度和挪动间隔。 第一幅图中,加速度前半程是正的,后半程是负的,所以手机在 x 方向上先减速后加速,速度从 0 增长到最大,后又缓缓减为 0,而挪动间隔一开始因为速度比较慢,所以增长慢,两头速度达到最大值,挪动间隔也增长得最快,最初速度归 0,挪动间隔也不在增长。 可问题就出在加速度上。原本加速度正的局部的积分和负的局部的积分,也就是蓝色区域的面积和黄色区域的面积,是完全一致的,这样当静止过程完结时手机的速度就会复原为 0,但理论状况并非如此。 ...

March 21, 2022 · 1 min · jiezi

关于canvas:关于Canvas-API的学习

Canvas是什么Canvas英文画布,是HTML5出的一个能够在下面绘制一系列图像的元素。 Canvas的应用场景能够用于动画、游戏画面、数据可视化、图片编辑以及实时视频解决等方面。 根本应用办法在HTML文件中 <canvas id="canvasBox" width="" height=""></canvas><!--注解: canvas必须是闭合标签</canvas>不可省,如果省略文档的其余内容将不会显示只有两个属性width和height如果没用设置宽高默认宽300高150-->在JS文件中 const canvasBox=document.querySelector("#canvasbox");//获取画布元素const ctx=canvasBox.getContext(contextType);/*获取渲染上下文(具备了绘制和解决展现内容的能力)contextType参数有2d:绘制2d图像(创立一个CanvasRenderingContext2D对象作为2d渲染的上下文)webgl(experimental-webgl)、webgl2:绘制3d图像(实验性)bitmaprenderer:把位图绘制在canvas上下文上(实验性)*/canvas绘制图形的形式:第一通过矩形例如绘制矩形: fillRect(x,y,width,height)//矩形的终点坐标(x,y)矩形的宽高(width,height)ctx.fillRect(0,0,300,150)//绘制了一个终点坐标为(0,0)宽高别离为300px,150px的矩形strokeRect(x,y,width,height)//绘制一个矩形边框(x,y)终点坐标矩形长宽(width,height)ctx.strokeRect(0,0,300,150)//绘制了一个终点坐标为(0,0)长宽别离为300,150clearRect(x,y,width,heihgt)//革除指定矩形区域,让革除局部齐全通明。ctx.clearRect(x,y,width,heihgt) //革除一个矩形边框(x,y)终点坐标革除矩形长宽(width,height)第二通过门路绘制直线 ctx.beginPath();//开始绘制新的门路ctx.moveTo(x,y)//门路起始坐标ctx.lineTo(x,y);//绘制直线到指定坐标点...ctx.closePath()//闭合门路ctx.stroke();//理论绘制门路绘制曲线 ctx.moveTo(x, y);//起始点ctx.bezierCurveTo(cp1x, cp1y, cp2x, cp2y, x, y);//别离是第一个控制点的横纵坐标第二个控制点的横纵坐标和完结点的横纵坐标ctx.stroke();//理论绘制门路绘制二次贝塞尔曲线 ctx.moveTo(x, y);//起始点ctx.quadraticCurveTo(cpx, cpy, x, y);//别离是第一个控制点的横纵坐标和完结点的横纵坐标ctx.stroke();//理论绘制门路绘制圆弧 context.beginPath();//开始绘制新的门路ctx.arc(x, y, radius, startAngle, endAngle [, anticlockwise])//圆弧圆心横纵坐标半径圆弧开始的角度圆弧完结的角度context.stroke();//理论绘制门路绘制矩形 ctx.rect(x,y,width,height)//矩形起始点的横纵坐标和宽高context.stroke();//理论绘制门路椭圆绘制 ctx.ellipse(x, y, radiusX, radiusY, rotation, startAngle, endAngle, anticlockwise)// 椭圆弧对应的圆心横纵坐标椭圆弧的长短轴半径大小椭圆弧的旋转角度圆弧开始和完结的角度顺逆时针context.stroke()//理论绘制门路对于款式的相干设置线条款式的设置 ctx.lineWidth=number//设置线条的宽度ctx.lineCap=//线头的款式别离buzz(默认),round(圆弧)和square(方头)ctx.lineJoin=//miter:尖角 round:圆角 bevel:平角ctx.miterLimit = value;//0-10//设置尖角长度和lineJoin属性值是miter配合应用ctx.getLineDash()//获取以后线条的虚线数值一个偶数个数的数组ctx.setLineDash()//线条为虚线参数是个数组如果是[]实线context.lineDashOffset=value//虚线绘制的偏移间隔默认0是浮点数填充描边ctx.fillStyle=//填充色彩color gradient patternctx.strokeStyle=//边框色彩color gradient patternctx.stroke()//绘制门路图像和像素(重点)//用法:/*参数阐明image:图片资源在画布上布局一片区dx:规划区的横坐标dy:规划区的纵坐标dWidth:规划区的宽dHeight:规划区的高图片元素绘制在Canvas画布sx:起始横坐标sy:起始纵坐标sWidth:图片元素从坐标点开始算,多大的宽度内容sHeight:图片元素从坐标点开始算,多大的高度内容*/context.drawImage(image, dx, dy);context.drawImage(image, dx, dy, dWidth, dHeight);context.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);//例子let image= new Image(); //创立一个空元素 image.src = Url; // 门路 image.onload = function(){ // ctx.drawImage(image,0,0) ctx.drawImage(image,41,74,64,82,0,0,128,164) //参数顺次是要绘制图片,图片从那个坐标点开始绘制,绘制的面积的大小将绘制的图片,绘制在画布中地位以及绘制图片在画布中大小 } 文本/*参数阐明:text文本内容x,y文本在画布中的终点坐标地位maxWidth文本占据的最大宽度(强制压缩不换行)*/ctx.fillText(text, x, y [, maxWidth]);//绘制文本ctx.strokeText(text, x, y [, maxWidth]);//绘制文本边框ctx.measureText(text)//获取TextMetrics对象测量文本的宽//对于文本的款式ctx.font=""//设置文本字体大小/*vulue文本对齐形式left:左对齐right:右对齐center:居中对齐start:起始方位对齐end:完结方位对齐*/ctx.textAlign=value状态ctx.save()//存储context.restore();//弹出存储状态突变/*线性突变x0,y0突变起始点横纵坐标x1,y1突变完结点横纵坐标*/ctx.createLinearGradient(x0, y0, x1, y1);/*镜像突变x0,y0起始圆得圆心坐标r0起始圆半径x1,y1完结圆得圆心坐标r1完结圆半径*/ctx.createRadialGradient(x0, y0, r0, x1, y1, r1);变形ctx.rotate(angle)//旋转单位是弧度ctx.scale(x,y)//缩放ctx.translate(x,y)//位移ctx.transform(a,b,c,d,e,f)//变形别离程度缩放程度斜切垂直斜切垂直缩放程度位移垂直位移ctx.setTransform()//同上区别执行会齐全重置已有的变换暗影ctx.shadowBlur = value;//暗影大小ctx.shadowColor = color;//暗影色彩ctx.shadowOffsetX = offset;//暗影程度偏移ctx.shadowOffsetY = offset;//暗影垂直偏移透明度和层级ctx.globalAlpha = value;//透明度0-1/*type参数阐明:source-over:间接笼罩在原有图层下面互相叠加(纯视觉笼罩)source-in:只显示互相叠加的区域(新内容为显示层,原内容是遮罩层)source-out:和source-in相同(重叠的地位是通明的)source-atop:重叠内容进行相似遮罩解决未重叠的失常显示****destination-*和source-*显示主体绝对destination以原图层为显示主体sourc以新图层为显示主体****destination-overdestination-indestination-outdestination-atoplighter:混合模式copy:只显示新内容xor:相互重叠的区域是通明的multiply:正片迭代screen:滤色overlay:叠加darken:变暗lighten:变亮color-dodge:色彩减淡color-burn:色彩加深hard-light:强光soft-light:柔光difference:差别exclusion:排除hue:色调saturation:饱和度color:色值luminosity:亮度*/ctx.globalCompositeOperation = type;图案相干/*imag:平铺的CanvasImageSource图像repetition:repeat程度垂直平铺 no-repeat不平铺 repeat-x程度平铺 repeat-y垂直平铺*/ctx.createPattern(image, repetition);地位检测/*参数阐明x,y检测的点的横纵坐标 fillRule参数填充规定nonzero:非零规定,此乃默认规定。evenodd:奇偶规定。*///检测点是否在指定门路内ctx.isPointInPath(x, y);//返回值true和falsectx.isPointInPath(x, y, fillRule);//返回值true和false/*x,y检测的点的横纵坐标path指Path2D对象*///检测点是否在门路上context.isPointInStroke(x, y);//返回值true和faslecontext.isPointInStroke(path, x, y);//同上以上就是canvas罕用的api,更详尽的学习请查看CanvasAPI相干文档 ...

March 1, 2022 · 1 min · jiezi

关于canvas:Day-27100-Canvas-复制图片的功能

1、需要想实现魔法棒性能;须要实现图片的Canvas像素级别的复制性能; 2、实现代码<!-- * @Author: ArdenZhao * @Date: 2022-01-18 14:09:54 * @LastEditors: Do not edit * @LastEditTime: 2022-02-16 18:55:42 * @FilePath: /magic_wand/demo/8、canvasCopy.html--><!DOCTYPE html><html><head> <meta charset="utf-8"> <title>Canvas Image</title></head><body> <h2>Canvas Image</h2> <div> <div class="inputoutput"> <canvas id="canvasOutput" width="1600" height="660"></canvas> <div class="caption">canvasOutput</div> </div> </div> <script type="text/javascript"> //获取canvas标签 let canvasElement = document.getElementById('canvasOutput'); let ctx = canvasElement.getContext('2d');; //2. 获取2D上下文 //3. 新建一个Image对象 var img = new Image(); //4. 设置Image的src img.src = 'https://t7.baidu.com/it/u=2397542458,3133539061&fm=193&f=GIF'; img.crossOrigin = "Anonymous"; let imageWidth = 0 let imageHeight = 0 let imageData = {} // 原始图像数据 img.onload = () => { imageWidth = canvasElement.width / 2 imageHeight = canvasElement.height / 2 ctx.drawImage(img, 0, 0, imageWidth, imageHeight);// 将解决的过的数据显示在新的地位 imageData = ctx.getImageData(0, 0, imageWidth, imageHeight); //复制到Canvas的像素信息 } // 4、获取到Canvas点击的坐标 canvasElement.addEventListener('click', (e) => { let clone = new ImageData(new Uint8ClampedArray(imageData.data), imageWidth, imageHeight) ctx.putImageData(clone, 0, imageHeight + 20); // 将解决的过的数据显示在原来的地位 }, false); </script></body></html>3、实现成果 ...

February 16, 2022 · 1 min · jiezi

关于canvas:1常见图形绘制演示环形图

环形图假如咱们当初有一组数据: var data = [ {type: "苹果",number: 124}, {type: "香蕉",number: 56}, {type: "栗子",number: 310}];咱们心愿采纳环形图来显示,最终的成果如下: 繁多的状况咱们先不思考边缘的提醒文字和折线,只思考两头的环形如何绘制。 首先,咱们须要求解出数值的和,这样能力会绘制的时候晓得每个环的占比: var sum = 0;for (var i = 0; i < data.length; i++) sum += data[i].number;当初,就能够通过循环的形式一个个绘制圆环了: // beginDeg示意以后绘制的环的开始弧度,咱们从-0.5PI的中央开始绘制var beginDeg = -Math.PI * 0.5, deg; for (var i = 0; i < data.length; i++) { // 计算占比和和2PI相乘得出以后环的弧度 deg = data[i].number / sum * Math.PI * 2; // 以(200, 200)为圆心,内外半径别离是100和200,从弧度beginDeg开始,绘制弧度deg的圆弧 painter.fillArc(200, 200, 100, 200, beginDeg, deg); beginDeg += deg; // 计算下一个环的开始弧度}具体代码请看评论区的【代码一】带提醒折线文字能够发现,折线和文字的绘制关键点在于每个圆弧对应的折线的那三个点的地位,对于地位的计算,咱们借助rotate来计算。语法如下: ...

December 4, 2021 · 1 min · jiezi

关于canvas:0常见图形绘制演示写在前面

写在后面接下来,咱们将向大家阐明一些常见的图形如何绘制,外围的内容是编程思路的分享,不波及具体的绘图库。 在演示的时候和阐明的时候,尽管咱们抉择基于image2D.js来作为依赖库,但因为其奢侈的语法简直和原生canvas或者说和普通人的认知是统一的,因而咱们认为这不是一个蹩脚的抉择。 舒适提醒:如果你有更好的倡议,欢送给咱们留言~上面,咱们将简略的把须要提前理解的常识在上面进行阐明。 引入并获取画笔为了能够应用image2D,你能够抉择npm或者CDN的形式引入,这里为了简略,咱们间接应用CDN引入: <script src="https://cdn.jsdelivr.net/npm/image2d@1"></script>接着,你须要筹备一个canvas: <canvas>十分道歉,您的浏览器不反对canvas!</canvas>而后,应用上面的语句即可获取画笔: var painter=$$('canvas').painter();绘制办法残缺的绘制办法请查看画笔上的绘图办法中的阐明,这里,咱们抉择画矩形的办法给大家演示一下,十分的简略: painter.fillRect(50,50,120,60);下面的语句就会在(50,50)的地位绘制一个宽120高60的矩形。 残缺代码状况评论区【代码一】计算方法说的简略的点击,就是封装了一些绘图中可能会用到的计算方法,确定的输入输出。 具体的计算方法请查看辅助计算中的阐明,咱们这里就不再阐明了。

December 4, 2021 · 1 min · jiezi

关于canvas:如何绘制完美的鼠标轨迹

动机在公司的某次周会上,我吐槽了某产品中一个显示鼠标轨迹的成果实现得比拟形象: 能够看到它的实现形式是将 mousemove 事件触发时的坐标,用长宽不一的矩形连接起来,所以连接处呈现了显著的“断裂”,整个轨迹也不平滑,而且其宽度和透明度的“突变”也比拟僵硬,有显著断层。 而我现实中的鼠标轨迹应该是长这样的: 整个轨迹是一条绝对平滑的曲线,两头不应该有僵硬的“断裂”,而且轨迹的宽度和透明度都平均变动。 过后我感觉这么简略一个成果齐全应该做得欠缺一点,也花不了多少工夫。 然而,一个周末的中午,我正在洗碗,忽然脑子里灵光一闪,我意识到,在 web canvas 上要实现一个「完满」的鼠标轨迹成果仿佛并没有设想的那么简略。于是我决定本人尝试一下,就有了这个我的项目。 问题所谓「并没有设想的那么简略」次要是要解决这几个问题: 通过 mousemove 事件获取的鼠标轨迹是离散的坐标点,而不是实在的轨迹曲线,如何通过离散坐标绘制平滑曲线?鼠标轨迹的透明度应该是突变的,web canvas 上并没有提供在一个 path 上做线性突变的接口,这个成果如何实现?鼠标轨迹的粗细也应该是突变的,web canvas 上的繁多 path 也没有提供画笔粗细突变的接口,这个成果又如何实现?计划如何通过离散坐标绘制平滑曲线?如果你用过 Photoshop 中的钢笔工具,答案其实就很简略,用贝塞尔曲线。Photoshop 中的钢笔工具其实就是一个贝塞尔曲线编辑器,通过终点、起点以及两个控制点,就能够在终点和起点间建设一条曲线。 而如果一个两头点上的两个控制点满足肯定的法则,就能够实现曲线的间断,也就是视觉效果上的平滑。感兴趣的话能够浏览「用钢笔工具绘图」中的内容。 那么两头点上的两个控制点满足什么样的法则就能够实现曲线的间断呢?其实也很简略,就是两头点和两个控制点在同一直线上即可。 如下图,鼠标通过 A、B、C 三点,此时 B 点和他的两个控制点 C1 和 C2 在同一直线上,整个曲线在 B 点处就是平滑的。其数学逻辑也很简略,三点处于同一直线就意味着 B 点在 C1 方向和 C2 方向上的斜率都雷同,这样曲线就平滑了。 那么,在已知 A、B、C 三点坐标的状况下如何计算出每个点的控制点呢?一个简略的方法如下如所示: 计算角 p1-pt-p2 的角平分线,以及此角平分线通过点 pt 的垂线 c1-pt-c2取 p1、p2 在 c1-pt-c2 上的投影点中距离 pt 点较近的点 c2在 c1-pt-c2 上取与 c2 点绝对 pt 对称的点 c1此时用计算出的 c1、c2 点作为 pt 点的控制点,就能生成一个成果不错的平滑曲线了,同时 c1、c2 到 pt 点的间隔还能够用一个 tension 参数进行调节,从而达到调节曲线平滑水平的作用。 ...

November 3, 2021 · 1 min · jiezi

关于canvas:vue3写一个生成国庆头像的网站

国庆来了,最近风行小程序生成国庆主题头像。我用vue3写了一个,基于canvas,能够手动调节头像成果,性能比较简单,几个小时就搞定了。欢送大家体验:国庆头像生成器,源码在github。

September 30, 2021 · 1 min · jiezi

关于canvas:微信小程序Canvas绘制证件照底色小程序Canvas绘图

小程序提供了Canvas绘图的API,咱们很轻松就能够应用Canvas绘制一张图片并保留下来。本次案例应用绘制证件照的形式演示Canvas的示例。 筹备去掉背景的证件照(宽160px,高230px) 代码index.wxml <!-- Canvas 2D组件 --><canvas canvas-id="firstCanvas" class="firstCanvas"></canvas><!-- 保留按钮 --><button bindtap="saveimg" class="saveimg">保留到相册</button>index.wxss .firstCanvas{ width: 160px; height: 230px; margin:30px auto 0;}.saveimg{ margin-top: 30px;}index.js Page({ canvasIdErrorCallback: function (e) { console.error(e.detail.errMsg) }, onReady: function (e) { // 应用 wx.createContext 获取绘图上下文 context var context = wx.createCanvasContext('firstCanvas') // 设置边框色彩 context.setStrokeStyle("#fff") // 设置边框粗细 context.setLineWidth(0) // 设置背景色彩 context.setFillStyle("#f00") context.fillRect(0, 0, 160, 230) // 将人像绘制下来 context.drawImage('../images/1.png',0,0,160,230) // 创立一个矩形 context.rect(0, 0, 160, 230) context.stroke() context.draw() }, // 保留图片到相册 saveimg(){ var that = this; // 先将Cnavas绘制成临时文件 wx.canvasToTempFilePath({ x: 0, y: 0, width: 160, height: 230, destWidth: 160, destHeight: 230, canvasId: 'firstCanvas', success(res) { console.log(res.tempFilePath) // 再保留到相册 wx.saveImageToPhotosAlbum({ filePath:res.tempFilePath, success(res) { wx.showToast({ title: '已保留', icon: 'success', duration: 2000 }) } }) } }) }})演示 ...

September 17, 2021 · 1 min · jiezi

关于canvas:利用canvas绘制时钟模拟时钟效果

<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> <script> window.onload = function () { var canvas = document.querySelector('canvas') var context = canvas.getContext('2d') function clock() { //绘制圆盘 context.beginPath() context.arc(300, 300, 200, 0, Math.PI * 2) context.fillStyle = 'yellow' context.fill() //完结门路 context.closePath() //绘制时刻度 for (i = 1; i <= 12; i++) { // save():将以后的绘画状态进行保留并存入状态栈 //简而言之就是保留画刻度之前的状态 和restore()一起用 context.save() context.lineWidth = 4 context.beginPath() //将原点平移 圆心地位 context.translate(300, 300) //旋转30度 小时的刻度 // angle:旋转角度,旋转的中心点就是坐标轴的原点 context.rotate(i * (Math.PI / 6)) //绘制时刻度 从内向里画(反过来则要正数) context.moveTo(0, 200) context.lineTo(0, 180) context.stroke() context.fillStyle = 'black' // 调整数字的大小 context.font = '20px bold' //数半径 context.fillText(i, -10, -150) context.closePath() // restore():该办法每次调用只会回滚到上一次save的状态 //回退到画刻度之前的save()状态,不影响前面的时针,分针等等的绘制 context.restore() } //绘制分刻度 分针要转60次 for (i = 1; i <= 60; i++) { context.save() //平移原点 新的原点就是圆心 context.translate(300, 300) // 分针要转60次的6度 Math.PI相当于180度 context.rotate(i * (Math.PI / 30)) context.beginPath() // 绘制分刻度 从里往外画 context.moveTo(0, -190) context.lineTo(0, -200) context.stroke() context.closePath() //反复分刻度 context.restore() } //利用内置函数Date() 获取以后工夫 var date = new Date() //打印看一下 console.log(date); //获取以后的小时 var hours = date.getHours() //获取以后分钟 var minutes = date.getMinutes() //获取以后秒数 var seconds = date.getSeconds() //打印看一下 console.log(hours, minutes, seconds); //以后的小时数 hours = hours + minutes / 60; //绘制时针 仍旧要保留绘画时针前的状态而后画完后回退 context.save() //平移原点 context.translate(300, 300) //小时乘以30度 // rotate(angle):旋转角度,旋转的中心点就是坐标轴的原点 context.rotate(hours * (Math.PI / 6)) context.beginPath() context.lineWidth = 5 context.moveTo(0, 10) context.lineTo(0, -90) context.stroke() context.closePath() context.restore() //绘制分针 context.save() //平移原点 新原点就是圆心 context.translate(300, 300) //先依据以后分钟旋转到那个地位 找到坐标 context.rotate(minutes * (Math.PI / 30)) //再绘制分针 context.beginPath() //调整时针线宽 context.lineWidth = 3 //开始绘制 moveTo() 是挪动到分针的起始地位 context.moveTo(0, 10) //lineTo() 是画直线 (0, -120)是完结坐标点 这个坐标点是绝对圆心来说的,因为后面translate(300, 300)曾经把原点平移到圆心的地位了 context.lineTo(0, -120) context.strokeStyle = 'blue' context.stroke() context.closePath() context.restore() //绘制秒针 context.save() context.translate(300, 300) //小时乘以30度 // angle:旋转角度,旋转的中心点就是坐标轴的原点 context.rotate(seconds * (Math.PI / 30)) context.beginPath() //调整线宽 也就是秒针的宽度 context.lineWidth = 2 //绘制秒针 context.moveTo(0, 10) context.lineTo(0, -170) //扭转秒针款式 context.strokeStyle = 'red' //画的是轮廓图形 context.stroke() //完结门路 context.closePath() //回退状态 context.restore() //绘制交叉处 // save():将以后的绘画状态进行保留并存入状态栈 context.save() context.translate(300, 300) // 开始门路 context.beginPath() //绘制时钟两头的小圆 xy轴开始地位 小圆半径 开始弧度 完结弧度360度 context.arc(0, 0, 5, 0, Math.PI * 2) //填充圆设置为红色 context.fillStyle = 'white' //轮廓圆设置红色 context.strokeStyle = 'red' //绘制填充圆 context.fill() //绘制轮廓圆 context.stroke() //完结门路 context.closePath() context.restore() //超时模仿间歇 防止载入时 时钟先隐没一小会儿再呈现 setTimeout(clock, 1000) } clock() } </script></head><body> <canvas width="600px" height="600px" style="background-color: rgb(154, 233, 243);"></canvas></body></html>

September 15, 2021 · 2 min · jiezi

关于canvas:教你实现一个朴实的Canvas时钟效果

摘要:明天教大家写一个canvas的时钟案例,成果可能看起来比较简单,没有那些花里胡哨的。本文分享自华为云社区《如何实现一个朴实无华的Canvas时钟成果》,作者: 北极光之夜。。 一.先看成果:明天写一个canvas的时钟案例。成果可能看起来比较简单,没有那些花里胡哨的,不过,它波及的canvas知识点是比拟多的,初学canvas那是必然要会的。上面手把手带你疾速实现~ 二.实现步骤(源码在最初):1. 设置根本的标签与款式: <div class="clock"> <canvas width="300" height="300" id="canvas"></canvas> </div> * { margin: 0; padding: 0; box-sizing: border-box; } body { height: 100vh; display: flex; justify-content: center; align-items: center; background-color: rgb(204, 204, 204); } .clock { width: 300px; height: 300px; background-color: rgb(15, 15, 15); border-radius: 50px; }2. 开始js代码实现,上面为了易于了解,所以一个性能封装一个函数:var canvas = document.getElementById("canvas"); var ctx = canvas.getContext("2d");3. 先绘制钟的整体红色底盘:同时为了前期将旋转点为.clock的核心的,所以将translate偏移一半的间隔。 function drawPanel() { ctx.translate(150, 150); // 开始绘制 ctx.beginPath(); // 画一个圆 ctx.arc(0, 0, 130, 0, 2 * Math.PI); ctx.fillStyle = "white"; ctx.fill(); }4.绘制时钟的12位数字:可知,一个圆上它的x坐标为:R cos(它的角度), y坐标为:R sin(它的角度)。同时,因为Math.cos()与Math.sin()里是计算弧度的,所以要转换。公式:弧度 = 角度 * / 180 ...

September 14, 2021 · 2 min · jiezi

关于canvas:canvas-字体自动换行-线条粗细解决1px线条模糊问题

ctx.fillText 主动更换行<!DOCTYPE HTML><html><head> <meta charset="UTF-8"> <title>fillText Auto-wrap</title></head><body> <canvas id="mycanvas">您的浏览器不反对canvas。</canvas> <br> <textarea id="input" row="6" col="60" style="width:300px;height: 100px;">文本主动换行auto-wrap文本主动换行auto-wrap文本主动换行auto-wrap文本主动换行auto-wrap文本主动换行auto-wrap</textarea></body></html>(function() { function writeTextOnCanvas(cns, lh, rw, text) { var cns = document.getElementById(cns); var ctx = cns.getContext("2d"); var lineheight = lh; var text = text; ctx.width = cns.width; ctx.height = cns.height; ctx.clearRect(0, 0, ctx.width, ctx.height); ctx.font = "16px 微软雅黑"; ctx.fillStyle = "#f00"; function getTrueLength(str) { //获取字符串的实在长度(字节长度) var len = str.length, truelen = 0; for (var x = 0; x < len; x++) { if (str.charCodeAt(x) > 128) { truelen += 2; } else { truelen += 1; } } return truelen; } function cutString(str, leng) { //按字节长度截取字符串,返回substr截取地位 var len = str.length, tlen = len, nlen = 0; for (var x = 0; x < len; x++) { if (str.charCodeAt(x) > 128) { if (nlen + 2 < leng) { nlen += 2; } else { tlen = x; break; } } else { if (nlen + 1 < leng) { nlen += 1; } else { tlen = x; break; } } } return tlen; } for (var i = 1; getTrueLength(text) > 0; i++) { var tl = cutString(text, rw); ctx.fillText(text.substr(0, tl).replace(/^\s+|\s+$/, ""), 10, i * lineheight + 50); text = text.substr(tl); } } writeTextOnCanvas("mycanvas", 22, 40, document.getElementById("input").value); document.getElementById("input").onkeyup = function() { writeTextOnCanvas("mycanvas", 22, 40, this.value); }})();效果图: ...

August 29, 2021 · 2 min · jiezi

关于canvas:图片画到canvas-上的三种方法

第一种: /*3参数*/ /*图片对象*/ /*绘制在画布上的坐标 x y*/ //ctx.drawImage(image,100,100);<script type="text/javascript"> window.onload = function(){ var mycanvas = document.getElementById('mycanvas') var ctx = mycanvas.getContext('2d') // 内存中先加载,而后当内存加载结束时,再把内存中的数据填充到咱们的 dom元素中,这样可能疾速的去 // 反馈,比方网易的图片 var img = new Image(); img.onload = function(){ alert('加载结束') // 将图片画到canvas下面下来! ctx.drawImage(img,100,100); } img.src = "https://ss0.bdstatic.com/94oJfD_bAAcT8t7mm9GUKT-xh_/timg?image&quality=100&size=b4000_4000&sec=1537549551&di=3f8d4d76679adcae225387f7d6b199aa&src=http://gss0.baidu.com/-4o3dSag_xI4khGko9WTAnF6hhy/lvpics/h=800/sign=b49dc48f8718367ab28972dd1e728b68/9922720e0cf3d7ca7f0736d0f31fbe096a63a9a6.jpg"; } </script> <canvas id='mycanvas' width="500" height="500"></canvas>效果图: 第二种: /*5个参数*/ /*图片对象*/ /*绘制在画布上的坐标 x y*/ /*是图片的大小 不是裁剪 是缩放*/ // 从100,100 开始画,而后缩放到200,20 //ctx.drawImage(image,100,100,200,200);效果图: 第三种:// 从100,100 开始画,而后缩放到200,200 ctx.drawImage(img,593,327,500,500,100,100,300,300);从图片的593,327 坐标开始截图,截取 500,500 这么大而后将截取的图片,从canvas 100,100开始画, 缩放 300,300 这么大! /*9个参数*/ /*图片对象*/ /*图片上定位的坐标 x y */ /*在图片上截取多大的区域 w h*/ /*绘制在画布上的坐标 x y*/ /*是图片的大小 不是裁剪 是缩放*/ ctx.drawImage(image,400,400,400,400,200,200,100,100);效果图:原图: ...

August 29, 2021 · 1 min · jiezi

关于canvas:html2canvas将dom转换为画布

以下是依赖于html2canvas生成的海报成果,亲测无效 以一张背景图+二维码的布局为例 html局部: <div class="container"> <div class="share-img"> <img style="width: 300px; height: 300px" :src="imgUrl" alt="分享图" /> </div> <div class="creat-img" ref="box"> <img src="https://img0.baidu.com/it/u=3998012246,2453684564&fm=26&fmt=auto&gp=0.jpg" alt="分享背景图" /> <div id="qrcode" class="qrcode"></div> </div> </div>大抵是share-img这个盒子用来寄存最终生成的canvas海报creat-img盒子是寄存原始dom背景图和二维码的构造布局,下边通过html2canvas将其转换为画布海报 js局部: <script>import { qrcanvas } from "qrcanvas";import html2canvas from "html2canvas";export default { data() { return { imgUrl: "", }; }, watch: { imgUrl(val, oldval) { //监听到imgUrl有变动当前 阐明新图片曾经生成 暗藏DOM this.$refs.box.style.display = "none"; }, }, mounted() { let that = this; that.$nextTick(function () { //生成二维码 var canvas1 = qrcanvas({ data: decodeURIComponent("https://www.baidu.com/"),//你的二维码条跳转地址 size: 128, }); document.getElementById("qrcode").innerHTML = ""; document.getElementById("qrcode").appendChild(canvas1); //合成分享图 html2canvas(that.$refs.box).then( function (canvas) { that.imgUrl = URL.createObjectURL( that.base64ToBlob(canvas.toDataURL()) ); // ios的话会存在还未加载完就进行绘制,若为ios则放到定时器里执行 // setTimeout(() => {}, 2000); } ); }); }, methods: { //base64转blob base64ToBlob(code) { let parts = code.split(";base64,"); let contentType = parts[0].split(":")[1]; let raw = window.atob(parts[1]); let rawLength = raw.length; let uInt8Array = new Uint8Array(rawLength); for (let i = 0; i < rawLength; ++i) { uInt8Array[i] = raw.charCodeAt(i); } return new Blob([uInt8Array], { type: contentType }); }, },};</script>css局部 ...

August 28, 2021 · 2 min · jiezi

关于canvas:canvas-绘制海报or价签下载单张图片下载多张图片zip压缩包

需要:批量下载价签,点击批量下载,if抉择一条数据则绘制一张canvas,并且间接下载。else多条数据则循环绘制多张canvas,并且下载zip,zip名字设置“标签”+以后日期(zip名字可自行设置)此性能我用的vue实现的,然而canvas绘制海报的原理都是一样的。知识点: 革除画布,画矩形、画边框、截取图片画图、文字超出换行并显示省略号、字体大小色彩加粗、1px线条、循环生成canvas并且下载、下载zip留神的点: 图片加载的时候会提早绘图的过程 Canvas期待所有图片加载实现才开始绘图,此处用的是promise.all()解决,留神同步异步得问题。(https://www.cnblogs.com/jannr...<template> <div> <el-button type="text" @click="handleLabelDownload"">批量下载</el-button> <canvas ref="canvas" width="400" height="916" style="display:none"></canvas> <a ref='aImg' href=""></a> </div></template><script> import JSZip from "jszip"; import FileSaver from 'file-saver' const imgName = require('@/../static/img/图片名字.png') export default { methods:{ // 点击批量下载按钮 事件events async handleLabelDownload(){ if(this.items.length !== 0){ let zip = new JSZip() if(this.items.length === 1){ await this.drawCanvas(0) }else{ for(let u in this.items){ await this.drawCanvas(u,zip) } } if(this.items.length > 1){ zip.generateAsync({ type: 'blob' }).then(function (content) { let date = new Date(),month = 0; if(date.getMonth() + 1 < 10){ month = '0'+(date.getMonth()+1) }else{ month = date.getMonth() +1 } FileSaver.saveAs(content, '标签-'+month+'-'+date.getDate()+'.zip'); }); } } }, // 加载图片 预加载图片,最初返回一个promise对象 loadImage(url) { return new Promise((resolve)=>{ const img = new Image(); img.onload = ()=>resolve(img); img.src = url; }) }, getTrueLength(str) { let len = 0, trueLen = 0; if(str){ len = str.length for (let x = 0; x < len; x++) { if (str.charCodeAt(x) > 128) { trueLen += 2; } else { trueLen += 1; } } } return trueLen; }, cutString(str, leng) { let len = str.length, tlen = len, nlen = 0; for (let x = 0; x < len; x++) { if (str.charCodeAt(x) > 128) { if (nlen + 2 < leng) { nlen += 2; } else { tlen = x; break; } } else { if (nlen + 1 < leng) { nlen += 1; } else { tlen = x; break; } } } return tlen; }, // 绘制 价签 async drawCanvas(u,zip){ const {price,itemId,itemName,itemBn,distributor_id,label_name,label_bn,label_spec,label_model,label_texture} = this.items[u] // 掉接口 去获取二维码url const rest = await goodsCode({ itemId, itemName,itemBn,distributor_id}) const iconUrl = rest.data.data.qrcode_url || '' await Promise.all([ this.loadImage(this.imgName), this.loadImage(iconUrl) ]).then((imgs) => { // 获取画布画笔 const context = this.$refs.canvas.getContext('2d') // 清理矩形办法:clearRect(x,y,w,h) context.clearRect(0,0,400,916) // 背景 框 context.fillStyle = '#ffffff' context.fillRect(0,0,400,916); // 描边矩形办法:strokeRect(x,y,w,h) context.strokeStyle='#32323e'; context.lineWidth=2; context.strokeRect(0, 0, 400, 916); // 填充矩形办法:fillRect(x,y,w,h) context.fillStyle='#32323e'; context.fillRect(1,1, 400,140); // title字 context.font = '42px 微软雅黑'; context.textAlign ='center'; context.fillStyle ='#fff'; context.fillText(title名字,224,84); // logo context.drawImage(imgs[0],0,1000,1500,1500,90,49,50,50); // 商品名称 let text = label_name if(text){ context.beginPath() //开始绘画的申明 context.font = '40px 微软雅黑'; context.fillStyle ='#373737'; if(this.getTrueLength(text) <= 18){ context.textAlign ='center'; context.fillText(text,200,240); context.fillText(text,201,240); }else{ for (let i = 1; this.getTrueLength(text) > 0; i++) { let tl = this.cutString(text, 18); context.textAlign ='left'; if(i <= 2){ let t = 0,s=''; if(i == 2){ if(this.getTrueLength(text.substr(0, tl)) >= 16){ t = 8 s='...' }else{ t = tl } }else{ t = tl } context.fillText(text.substr(0, t).replace(/^\s+|\s+$/, "")+s, 20, i * 45 + 190); context.fillText(text.substr(0, t).replace(/^\s+|\s+$/, "")+s, 21, i * 45 + 190); } text = text.substr(tl); } } context.closePath() } // 货号 if(label_bn){ context.font = '24px 微软雅黑'; context.fillStyle ='#373737'; context.textAlign ='center'; context.fillText(label_bn,200,323,360); } // 线条 context.moveTo(20.5,370.5); // 定义终点,能够了解为将画笔挪动到一个地位 context.lineTo(20.5,370.5) // 定义一个线条一端的终点 context.lineTo(380.5,370.5) // 定义一个线条一端的起点 context.lineWidth = 1 // 定义线条宽度 context.strokeStyle='#d0d0d0'; // 定义线条色彩 // context.lineCap='round' // 定义线帽(含圆角、尖角、斜角) context.stroke() // 给线条上色,即进行绘制 context.font = '22px 微软雅黑'; context.fillStyle ='#373737';// 文字色彩 context.textAlign ='center'; context.fillText('规格:',53,412); // 文本程度对齐形式 context.fillText('规格:',54,412); // 文字加粗 // 规格值 if(label_spec){ context.font = '18px 微软雅黑'; context.fillStyle ='#373737'; context.textAlign ='left'; context.fillText(label_spec,82,413,300); } context.moveTo(20.5,434.5); context.lineTo(20.5,434.5); context.lineTo(380.5,434.5); context.lineWidth = 1 context.strokeStyle='#d0d0d0'; context.stroke(); context.font = '22px 微软雅黑'; context.fillStyle ='#373737'; context.textAlign ='center'; context.fillText('型号:',53,476); context.fillText('型号:',54,476); if(label_model){ context.font = '18px 微软雅黑'; context.fillStyle ='#373737'; context.textAlign ='left'; context.fillText(label_model,82,477,300); } context.moveTo(20.5,498.5); context.lineTo(20.5,498.5); context.lineTo(380.5,498.5); context.lineWidth = 1 context.strokeStyle='#d0d0d0'; context.stroke(); context.font = '22px 微软雅黑'; context.fillStyle ='#373737'; context.textAlign ='center'; context.fillText('材质:',53,540); context.fillText('材质:',54,540); if(label_texture){ context.font = '18px 微软雅黑'; context.fillStyle ='#373737'; context.textAlign ='left'; context.fillText(label_texture,82,541,300); } context.moveTo(20.5,562.5); context.lineTo(20.5,562.5); context.lineTo(380.5,562.5); context.lineWidth = 1 context.strokeStyle='#d0d0d0'; context.stroke(); // 价格 context.font = '30px 微软雅黑'; context.fillStyle ='#373737'; context.textAlign ='center'; const prices = '¥' + price context.fillText(prices,200,662); context.fillText(prices,201,662); context.moveTo(20,692); context.lineTo(20,692); context.lineTo(380,692); context.lineWidth = 1 context.strokeStyle='#a3a3a3'; context.stroke(); context.moveTo(20,697); context.lineTo(20,697); context.lineTo(380,697); context.lineWidth = 1 context.strokeStyle='#a3a3a3'; context.stroke(); context.drawImage(imgs[1],140,730,130,130); context.font = '14px 微软雅黑'; context.textAlign ='center'; context.fillStyle ='#373737'; context.fillText(' 扫 码 了 解 更 多',200,880); if(!zip){ // 如果是一张图片的话,就间接主动触发a标签的click事件,主动下载文件 let dataURL = this.$refs.canvas.toDataURL(); let oA = this.$refs.aImg; oA.href = dataURL; oA.download = name + '.png'; // 下载的文件名能够此处批改 oA.click() } }) if(zip){ await this.addToZip(this.$refs.canvas, zip, name+'.png'); } }, addToZip(canvas, zip, name){ return new Promise((resolve, reject) => { canvas.toBlob(function (blob) { zip.file(name, blob); resolve(); }); }) }, } }</script>实际效果: ...

August 11, 2021 · 3 min · jiezi

关于canvas:Canvas-入门指南二-如何使用-canvas-画一个滑稽

前情提要:Canvas 入门指南 工具合集地址 最初效果图: 教程获取 canvas 对象获取 context 对象加一点点细节功败垂成剖析一下上图,根本都是圆弧。设置几个同心圆以及拼接几段圆弧即可本画。 大体轮廓首先设置填充色彩,通过取色能够理解到填充色彩为 rgb(241, 201, 96)画一个残缺的圆形context.fillStyle = 'rgb(241, 201, 96)';context.arc(100, 100, 50, 0, 2 * Math.PI, true);context.fill();嘴巴画一个半圆,特点是与轮廓成一个同心圆context.lineWidth = 2;context.arc(100, 100, 40, 0, Math.PI, false);context.stroke();眼睛轮廓眼睛轮廓会比拟麻烦,分为左眼以及右眼 两个眼睛主题局部都是由两个同心圆组成 左眼 // 左眼context.beginPath();context.arc(75, 90, 20, Math.PI * 1.1, Math.PI * 1.9, false);context.stroke();context.beginPath();context.arc(75, 90, 10, Math.PI * 1.1, Math.PI * 1.9, false); context.stroke();context.beginPath();context.arc(60, 85, 5, Math.PI, Math.PI * 2, true); // 左眼左连接处context.stroke();context.beginPath();context.arc(90, 85, 5, Math.PI, Math.PI * 2, true); // 左眼 右连接处context.stroke();右眼: ...

July 14, 2021 · 1 min · jiezi

关于canvas:canvas添加水印

// 动静尺寸const dynamicDateSize = function (size) { //return (document.body.offsetWidth / 375) * size; return (getClientW() / 375) * size}export function addWaterBack(text) { const width = window.screen.width || document.body.offsetWidth; const height = window.screen.height || document.body.clientHeight; let selector = document.querySelector("body"); let section = document.createElement("section"); const styleStr = ` position: fixed; width: 100%; height: 100%; left: 0; top: 0; pointer-events: none; z-index: 199999999;`; section.setAttribute('style', styleStr); section.style.background = `url(${_canvasToimg(width, height, text)})`; //section.classList.add("warter-back"); selector.appendChild(section); //selector.style.background = `url(${_canvasToimg(width, height, text)})`;}function _canvasToimg(width, height, text) { const width2 = width / 2; const height3 = height / 5; // 单个水印 let sCanvas = document.createElement("canvas"); // 创立canvas标签 sCanvas.width = width2; // 设置画布大小 sCanvas.height = height3; let ctx = sCanvas.getContext("2d"); ctx.fillStyle = "rgba(35,24,21,0.1)"; const fontSize = Math.min(Number(CommonJs.dynamicDateSize(12)).toFixed(0) || 12, 24); ctx.font = `${fontSize}px Arial`; ctx.rotate((-25 * Math.PI) / 180); ctx.fillText(text, 0, height3 / 1.5); ctx.rotate((25 * Math.PI) / 180); // 大的canvas let bCanvas = document.createElement("canvas"); bCanvas.width = width; bCanvas.height = height; let ctx1 = bCanvas.getContext("2d"); ctx1.clearRect(0, 0, width, height); let pat = ctx1.createPattern(sCanvas, "repeat"); //在指定的方向上反复指定的元素 ctx1.fillStyle = pat; ctx1.fillRect(0, 0, width, width); return sCanvas.toDataURL("image/png");}

June 28, 2021 · 1 min · jiezi

关于canvas:面试官问我会canvas-我可以绘制一个烟花动画

前言在咱们日常开发中贝塞尔曲线无处不在: svg 中的曲线(反对 2阶、 3阶)canvas 中绘制贝塞尔曲线简直所有前端2D或3D图形图表库(echarts,d3,three.js)都会应用到贝塞尔曲线所以把握贝塞尔曲线势在必得。 这篇文章次要是实战篇,不会介绍和贝塞尔相干的常识, 如果有同学对贝塞尔曲线不是很分明的话:能够查看我这篇文章——深刻了解SVG 绘制贝塞尔曲线第一步咱们先创立ctx, 用ctx 画一个二阶贝塞尔曲线看下。二阶贝塞尔曲线有1个控制点,一个终点,一个起点。 const canvas = document.getElementById( 'canvas' );const ctx = canvas.getContext( '2d' );ctx.beginPath();ctx.lineWidth = 2;ctx.strokeStyle = '#000';ctx.moveTo(100,100)ctx.quadraticCurveTo(180,50, 200,200)ctx.stroke(); 这样咱们就画好了一个贝塞尔曲线了。 绘制贝塞尔曲线动画画一条线谁不会哇?接下来文章的主体内容。 首先试想一下动画咱们必定一步步画出曲线? 然而这个ctx给咱们全副画进去了是不是有点问题。咱们从新看下二阶贝塞尔曲线的实现过程动画,看看是否有思路。 从图中能够剖析得出贝塞尔上的曲线是和t有关系的, t的区间是在0-1之间,咱们是不是能够通过二阶贝塞尔的曲线方程去算出每一个点呢,这个专业术语叫离散化,然而这样的得进去的点的信息是不太准的,咱们先这样实现。 先看下方程: 咱们模仿写出代码如如下: //这个就是二阶贝塞尔曲线方程function twoBezizer(p0, p1, p2, t) { const k = 1 - t return k * k * p0 + 2 * (1 - t) * t * p1 + t * t * p2}//离散function drawWithDiscrete(ctx, start, control, end,percent) { for ( let t = 0; t <= percent / 100; t += 0.01 ) { const x = twoBezizer(start[0], control[0], end[0], t) const y = twoBezizer(start[1], control[1], end[1], t) ctx.lineTo(x, y) }}咱们看下成果: ...

June 26, 2021 · 2 min · jiezi

关于canvas:Canvas-入门指南

mdn 文档 canvas 能够用于动画、游戏画面、数据可视化、图片编辑以及实时视频解决等内容。 canvas api 次要聚焦于 2D 图形。 WebGL api 次要聚焦于硬件加速的 2D 和 3D 图形。 先来看一个根底事例: <canvas id="canvas"> Your browser not support canvas, please update browser.</canvas><script> function draw() { // 获取 canvas 元素 const canvas = document.getElementById('canvas'); // 创立画板 const context = canvas.getContext('2d'); // 填充形态 context.fillRect(10, 10, 55, 50); // 填充色彩 context.fillStyle = 'black'; } draw();</script>这时,浏览器会呈现一个彩色的方块: 绘制图形首先学习如何绘制矩形。 绘制矩形canvas 提供了 3 种办法来绘制矩形: fillRect(x, y, width, height): 绘制了一个填充的矩形;strokeRect(x, y, width, height): 绘制了一个矩形的边框;clearRect(x, y, width, height): 分明指定矩形区域,让分明局部齐全通明;rect(x, y, width, height): 矩形门路应用以上三个办法构建一个空心矩形: ...

May 17, 2021 · 3 min · jiezi

关于canvas:将canvas作为图片下载

将canvas作为图片下载思路是将canvas转化为base64链接,通过模仿a标签的download属性进行下载 // https://stackoverflow.com/questions/18480474/how-to-save-an-image-from-canvasfunction download(canvas, filename) { var lnk = document.createElement('a'), e lnk.download = filename lnk.href = canvas.toDataURL('image/png;base64') if(document.createEvent) { e = document.createEvent('MouseEvents') e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ) lnk.dispatchEvent(e) } else if(lnk.fireEvent) { lnk.fireEvent('onclick') }}放一个html的Demo <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Download Cavans</title> <style> *{ margin: 0; padding: 0; box-sizing: border-box; } html,body{ height: 100%; width: 100%; } .wrapper{ width: 100%; height: 100%; padding: 100px; display: flex; flex-direction: column; justify-content: space-around; align-items: center; } .button{ width: 100px; height: 40px; display: flex; justify-content: center; align-items: center; border: 1px solid #ddd; box-shadow: 2px 2px 14px #333; cursor: pointer; user-select: none; transition: all .3s; } .button:hover{ transform: skew(-15deg); } </style></head><body> <div class="wrapper"> <div class="button">Download</div> </div> <script> var wrapper = document.querySelector('.wrapper') var canvas = document.createElement('canvas') canvas.width = 500 canvas.height = 500 var ctx = canvas.getContext('2d') ctx.fillStyle = 'rgba(0, 0, 225, .6)' ctx.fillRect(0, 0, 500, 500) ctx.fillStyle = 'rgba(225, 0, 0, .6)' ctx.font = '600 40px Arial' ctx.fillText('Canvas', 100, 100) wrapper.appendChild(canvas) function download(canvas, filename) { var lnk = document.createElement('a'), e lnk.download = filename lnk.href = canvas.toDataURL('image/png;base64') if(document.createEvent) { e = document.createEvent('MouseEvents') e.initMouseEvent('click', true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null ) lnk.dispatchEvent(e) } else if(lnk.fireEvent) { lnk.fireEvent('onclick') } } document.querySelector('.button').onclick = () => { download(canvas, 'image.png') } </script></body></html>

May 12, 2021 · 2 min · jiezi

关于canvas:canvas绘制折线路径动画

最近有读者加我微信征询这个问题: 其中的成果是一个折线门路动画成果,如下图所示: 要实现以上门路动画,个别能够应用svg的动画性能。或者应用canvas绘制,联合门路数学计算来实现。 如果用canvas来绘制,其中的难点在于: 须要计算子门路,这块计算比较复杂。(当然是能够实现的)突变的计算, 从图中能够看出,动画的子门路是有突变成果的,如果要分段计算突变也很简单。本文介绍一种思路,应用clip办法,动静挪动clip的区域,来达到近似的成果。具体怎么做。 绘制灰色门路绘制门路的代码比较简单,此处就不具体阐明,上面代码就模仿了了一个折线门路的绘制: ctx.beginPath(); ctx.moveTo(100,100); ctx.lineTo(200,100); ctx.lineTo(230,200); ctx.lineTo(250,50); ctx.lineTo(270,180); ctx.lineTo(300,60); ctx.lineTo(330,160); ctx.lineTo(350,60); ctx.lineTo(380,100); ctx.lineTo(480,100); ctx.strokeStyle = "gray"; ctx.lineJoin = "round"; ctx.stroke(); 成果如下: 绘制亮色门路绘制亮色门路的代码和绘制灰色门路的代码一样,只是款式是一个亮的色彩: ctx.save(); ctx.beginPath(); ctx.moveTo(100,100); ctx.lineTo(200,100); ctx.lineTo(230,200); ctx.lineTo(250,50); ctx.lineTo(270,180); ctx.lineTo(300,60); ctx.lineTo(330,160); ctx.lineTo(350,60); ctx.lineTo(380,100); ctx.lineTo(480,100); ctx.strokeStyle = "gray"; ctx.lineJoin = "round"; ctx.stroke(); 成果如下: clip管制亮色门路的绘制区域canvas的clip办法能够管制绘制的区域,通过该办法,能够管制智绘制门路的一部分: ctx.beginPath(); ctx.rect(offset,0,100,500); // offset 等于0 ctx.clip(); ... ctx.stroke(); clip之后,亮色门路就只会绘制一部分,如下图: 动画成果通过一直变动offset的值,就能够小道亮色门路挪动的成果,代码如下: offset += 2; if(offset > 600){ offset = 100; }requestAnimationFrame(animate);最终成果如下: ...

May 9, 2021 · 2 min · jiezi

关于canvas:假如只剩下canvas标签

公众号“执鸢者”,回复“canvas”获取对应源码,还有业余交换群等你一起来洒脱。一、背景如果只剩下canvas标签,该如何去绘制页面中的内容呢?这兴许是一个伪命题,然而用canvas确事可能帮忙实现很多事。明天就用canvas+AST语法树构建一个信息流款式。二、绘制流程将整个绘制流程分为三局部:根本元素、AST语法树、主函数类。根本元素指的是图片、文字、矩形、圆等;AST语法树在本处值得就是蕴含一些属性的js对象;主函数类指对外裸露的接口,通过调用实现最终绘制。2.1 根本元素不论如许简单的事物必定都是由一系列简略的元素组成,例如汽车必定是通过一些简略的机械零配件组成;电脑也是通过电阻、电容等零配件组成。网页也不例外,也是通过文字、图片、矩形等组成。2.1.1 加载图片图片是一个页面中的灵魂元素,在页面中占据绝大部分空间。class DrawImage { constructor(ctx, imageObj) { this.ctx = ctx; this.imageObj = imageObj; } draw() { const {centerX, centerY, src, sx = 1, sy = 1} = this.imageObj; const img = new Image(); img.onload = () => { const imgWidth = img.width; const imgHeight = img.height; this.ctx.save(); this.ctx.scale(sx, sy); this.ctx.drawImage(img, centerX - imgWidth * sx / 2, centerY - imgHeight * sy / 2); this.ctx.restore(); }; img.src = src; }}2.1.2 绘制文字文字可能进步页面的可读性,让察看该页面的每一个人都可能疾速理解该页面的思维。class DrawText { constructor(ctx, textObj) { this.ctx = ctx; this.textObj = textObj; } draw() { const {x, y, font, content, lineHeight = 20, width, fillStyle = '#000000', textAlign = 'start', textBaseline = 'middle'} = this.textObj; const branchsContent = this.getBranchsContent(content, width); this.ctx.save(); this.ctx.fillStyle = fillStyle; this.ctx.textAlign = textAlign; this.ctx.textBaseline = textBaseline; this.ctx.font = font; branchsContent.forEach((branchContent, index) => { this.ctx.fillText(branchContent, x, y + index * lineHeight); }); this.ctx.restore(); } getBranchsContent(content, width) { if (!width) { return [content]; } const charArr = content.split(''); const branchsContent = []; let tempContent = ''; charArr.forEach(char => { if (this.ctx.measureText(tempContent).width < width && this.ctx.measureText(tempContent + char).width <= width) { tempContent += char; } else { branchsContent.push(tempContent); tempContent = ''; } }); branchsContent.push(tempContent); return branchsContent; }}2.1.3 绘制矩形通过矩形元素可能与文字等元素配合达到意想不到的成果。class DrawRect { constructor(ctx, rectObj) { this.ctx = ctx; this.rectObj = rectObj; } draw() { const {x, y, width, height, fillStyle, lineWidth = 1} = this.rectObj; this.ctx.save(); this.ctx.fillStyle = fillStyle; this.ctx.lineWidth = lineWidth; this.ctx.fillRect(x, y, width, height); this.ctx.restore(); }}2.1.4 绘制圆圆与矩形承当的角色统一,也是在页面中比拟重要的角色。class DrawCircle { constructor(ctx, circleObj) { this.ctx = ctx; this.circleObj = circleObj; } draw() { const {x, y, R, startAngle = 0, endAngle = Math.PI * 2, lineWidth = 1, fillStyle} = this.circleObj; this.ctx.save(); this.ctx.lineWidth = lineWidth; this.ctx.fillStyle = fillStyle; this.ctx.beginPath(); this.ctx.arc(x, y, R, startAngle, endAngle); this.ctx.closePath(); this.ctx.fill(); this.ctx.restore(); }}2.2 AST树AST形象语法树是源代码语法结构的一种形象示意。它以树状的模式体现编程语言的语法结构,树上的每个节点都示意源代码中的一种构造。例如,在Vue中,将模板语法转换为AST形象语法树,而后再将形象语法树转换为HTML构造,咱们在利用canvas绘制页面时也利用AST形象语法树来示意页面中的内容,实现的类型有rect(矩形)、img(图片)、text(文字)、circle(圆)。 ...

May 5, 2021 · 4 min · jiezi

关于WPF:WPF-使用RenderTargetBitmap将Canvas保存为图片

在WPF中对控件进行截图是十分不便的,应用RenderTargetBitmap即可实现。然而如果是对Canvas这种类型的容器控件进行截图,截图的范畴可能不准。此时能够应用如下办法对Canvas进行截图,办法来自Clemens。 public void WriteToPng(UIElement element, string filename){ var rect = new Rect(element.RenderSize); var visual = new DrawingVisual(); using (var dc = visual.RenderOpen()) { dc.DrawRectangle(new VisualBrush(element), null, rect); } var bitmap = new RenderTargetBitmap( (int)rect.Width, (int)rect.Height, 96, 96, PixelFormats.Default); bitmap.Render(visual); var encoder = new PngBitmapEncoder(); encoder.Frames.Add(BitmapFrame.Create(bitmap)); using (var file = File.OpenWrite(filename)) { encoder.Save(file); }}

April 20, 2021 · 1 min · jiezi

关于canvas:canvas绘制图像轮廓效果

在2d图形可视化开发中,常常要绘制对象的选中成果。 一般来说,表白对象选中能够应用边框,轮廓或者发光的成果。  发光的成果,能够应用canvas的暗影性能,比拟容易实现,此处不在赘述。 绘制边框绘制边框是最容易实现的成果,比方上面的图片 要绘制边框,只须要应用strokeRect的形式即可。成果如下图所示: 这个代码也很简略,如下所示: ctx1.strokeStyle = "red"; ctx1.lineWidth = 2; ctx1.drawImage(img, 1, 1,img.width ,img.height) ctx1.strokeRect(1,1,img.width,img.height);绘制轮廓问题是,简略粗犷的加一个边框,并不能满足需要。很多时候,人们须要的是轮廓的成果,也就是图片的有像素和无像素的边缘处。如下图的成果所示: 要实现上述成果,最容易想到的思路就是通过像素的计算来判断边缘,并对边缘进行特定色彩的像素填充。然而像素的计算算法并不容易,简略的算法又很难达到预期的成果,而且因为逐像素操作,效率不高。 思考到在三维webgl中,计算轮廓的算法思路是这样的: 先绘制三维模型本身,并在绘制的时候启动模板测试,把三维图像保留到模板缓冲中。把模型适当放大,用纯属绘制模型,并在绘制的时候启用模板测试,和之前的模板缓冲区中的像素进行比拟,如果对应的坐标处在之前模板缓冲区中有像素,就不绘制纯色。根据上述的原理,就能够绘制处三维对象的轮廓了。上面是一个示例成果,(参考https://stemkoski.github.io/Three.js/Outline.html) 在2d canvas外面有相似的原理能够实现轮廓成果,就是应用globalCompositeOperation了。 大体思路是这样的: 首先绘制放大一些的图片。而后开启globalCompositeOperation = 'source-in', 并用纯色填充整个canvas区域,因为source-in的成果,纯色会填充放大图片有像素的区域。应用默认的globalCompositeOperation(source-over),用原始尺寸绘制图片。绘制放大一些的图片通过drawImage的参数能够管制绘制图片的大小,如下所示,drawImage有几个模式: 1 void ctx.drawImage(image, dx, dy);2 void ctx.drawImage(image, dx, dy, dWidth, dHeight);3 void ctx.drawImage(image, sx, sy, sWidth, sHeight, dx, dy, dWidth, dHeight);其中dx,dy 代表绘制的起始地位,个别绘制的时候应用第一个办法,代表绘制的大小就是本来图片的大小。而应用第二个办法,咱们能够指定绘制的尺寸,咱们能够应用第二个办法绘制放大的图片,代码如所示: ctx.drawImage(img, p - s, p - s, w + 2 * s, h+ 2 * s);其中p代表图片自身的绘制地位,s代表向左,向上的偏移量,同时图片的宽和高都减少 2 * s 用纯色填充放大图片的区域在上一步绘制的根底上,开启globalCompositeOperation = 'source-in', 并用纯色填充整个canvas区域。 代码如下所示: ...

March 31, 2021 · 2 min · jiezi

关于canvas:JS奇巧淫技之Canvas中绘制特殊字符

在canvas中绘制特殊字符,最简略也最实用的形式就是应用 字符 ,以 fillText 的形式进行绘制。当然如果对特殊符号的款式有特殊要求,应用图片也是不错的抉择。这里只介绍字符绘制形式。 计划一:代码中间接应用特殊字符以win10零碎为例,在代码输出过程中(切换为中文)能够点击候选词前面的笑脸图标,关上特殊字符抉择面板: 在弹出面板中抉择你须要的特殊字符即可主动键入到代码中: 上面以“无穷符号”为例: 代码 <!DOCTYPE html><html><head> <meta charset="utf-8"> <title>HTML5 canvas 绘制特殊字符</title> <style type="text/css"> canvas { border: 1px solid #ccc; } </style></head><body> <canvas id="myCanvas" width="500" height="500"></canvas> <script type="text/javascript"> // 取得 canvas.context var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); // 文字款式 context.fillStyle = '#1ABFFF'; context.font = '120px "微软雅黑"'; // 绘制文字 context.fillText('∞', 200, 200); </script></body></html>计划二:应用Unicode码查问所需特殊字符的Unicode代码,如“无穷符号”的Unicode码为\u221e,在代码中定义为 '\\u221e' ,而后应用 eval 函数进行解析,留神解析时,Unicode码必须在内部加一层引号,demo如下: 代码 <!DOCTYPE html><html><head> <meta charset="utf-8"> <title>HTML5 canvas 绘制特殊字符</title> <style type="text/css"> canvas { border: 1px solid #ccc; } </style></head><body> <canvas id="myCanvas" width="500" height="500"></canvas> <script type="text/javascript"> // 取得 canvas.context var canvas = document.getElementById("myCanvas"); var context = canvas.getContext("2d"); // 文字款式 context.fillStyle = '#1ABFFF'; context.font = '120px "微软雅黑"'; // 绘制文字 var infinCode = "\\u221e"; context.fillText(eval('"' + infinCode + '"'), 200, 200); </script></body></html>最终成果 ...

February 19, 2021 · 1 min · jiezi

关于SegmentFault:风格化shader热成像

曾经过来的2020是一个不怎么顺遂的一年,出入公共场所都须要体温监测,而人流量密集的商场,个别会采纳热成像技术来疾速测量体温。那么明天咱们就来说说如何让一张一般图片变成具备热成像的成果。 如果你对shader相干的技术感兴趣也能够浏览以下文章: 风格化shader:马赛克 本期代码应用javascript编写,波及一些webgl,glsl相干常识。从本文中你能够理解到: 如何colorRamp实现gameboy成果如何对图片进行含糊解决如何实现简略的高亮成果如何联合以上技术实现热成像成果colorRamp这一大节咱们介绍colorRamp的原理,并仅仅应用colorRamp实现将图片的GameBoy格调。 最终的成品: 什么ColorRamp简略来说colorRamp就一个色彩条,他能够是突变的,也能够就是固定的几个色彩。相似ps填充色彩时应用的那个。 咱们能够应用代码生成这种图片,但通常也会应用固定的图片素材。这里咱们以图片素材为例生成一个colorRamp: 这样就能够在片段着色器中应用这张图片了: color = texture2D(u_img, uv);但须要留神的时如何依照这样的代码渲染colorRamp会失去上面的后果 固定色彩的图片,变成突变的 这是因为咱们在调用gl.texParameteri函数是第三个参数传入gl.LINEAR,如果传入gl.NEAREST,则不会进行插值。 var val ;if(condition){ val = gl.LINEAR;}else{ val = gl.NEAREST}gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, val); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, val);colorRamp只是咱们的配色计划,不会间接将它绘制在canvas上。有了配色计划后咱们还须要通知shader什么地位应用何种配色。例如在场景一个类Minecraft的游戏中咱们须要一些"土块". 这时咱们的shader能够是这样的 blender中图形化的shader 它能够依据z轴的方向来决定是涂上绿色还是涂上土色。更进一步如果咱们将colorRamp作为参数,在不扭转shader的逻辑的状况下,就能够做出多种材质,实现shader的复用。 对于更简单的几何体咱们还能够联合法线,mix函数制作。这里就不深刻开展。 Gameboy格调要如何实现上述的gameBoy格调shader?代码如下 uniform float u_colorRampLuminosity;uniform sampler2D u_img0;uniform sampler2D u_img1;float saturate(float x){ return clamp(x,0.,1.); }void main() { vec4 color = texture2D(u_img0, uv); float luminance = 0.; luminance = dot(vec3(.3,.6,.1) , color.rgb); luminance = saturate(luminance + u_colorRampLuminosity); color.rgb = texture2D(u_img1, vec2(luminance, .0)).rgb; gl_FragColor = color;}这里咱们咱们向shader中传入三个变量别离是咱们的原图纹理u_img0,colorRamp纹理u_img1,以及一个控制参数u_colorRampLuminosity。 ...

January 19, 2021 · 3 min · jiezi

关于canvas:canvas可视化效果之内阴影效果

canvas可视化成果之内暗影成果楔子在之前的一个轨道交通可视化我的项目中,使用到了很多绘制技巧。 能够参考 之前的一篇文章 《利用canvas暗影性能与双线技巧绘制轨道交通大屏我的项目成果》 效果图中的轨道,就同时存在外发光和内发光成果的成果。 外发光成果咱们晓得外发光成果是很容易实现的,间接通过设置暗影成果即可达到。比方咱们轻易绘制一条线段,加上暗影成果,看起来就是外发光的成果: ctx.clearRect(0,0,canvas.width,canvas.height); ctx.shadowBlur= 20; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.shadowColor="red"; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.lineWidth = 10; ctx.strokeStyle = "blue"; ctx.beginPath(); ctx.moveTo(300,300); ctx.lineTo(750,300); ctx.quadraticCurveTo(800,300,800,350); ctx.lineTo(800,450); ctx.quadraticCurveTo(800,500,750,500); ctx.lineTo(300,500); ctx.stroke();效果图如下: 如果绘制圆形成果如下: 下面的代码都容易了解,就是通过shadowBlur产生突变暗影的成果。 默认的暗影,咱们称之为外暗影,意思都是图像向往开展的暗影成果。 内暗影接下来的问题可能就变得有点难度。如果咱们须要如下的一个内暗影的成果呢? 有人说,简略,一个突变就搞定了。 那再看看上面这个图像呢? 还是没问题,还是能够通过突变来搞定,只是突变的stop设置要麻烦一点罢了。 如果在简单一些的图形呢,比方上面的线段成果: 对于下面的线段的内暗影成果,就很难应用简略的突变来实现了。 如何绘制内暗影成果要实现下面的内暗影成果,首先还是应用shadowBlur参数,而后把ctx的globalCompositeOperation参数设置为“source-out” 即可。 试试如下代码: ctx.globalCompositeOperation = 'source-out'; ctx.beginPath(); ctx.beginPath(); ctx.moveTo(300,300); ctx.lineTo(750,300); ctx.quadraticCurveTo(800,300,800,350); ctx.lineTo(800,450); ctx.quadraticCurveTo(800,500,750,500); ctx.lineTo(300,500); ctx.lineCap = "round"; ctx.shadowBlur =15; ctx.lineWidth = 20; ctx.shadowColor="blue"; ctx.fillStyle = 'red'; ctx.strokeStyle = 'red'; ctx.stroke();最终绘制的成果就是下面的线段图的成果: ...

December 29, 2020 · 2 min · jiezi

关于canvas:利用canvas阴影功能与双线技巧绘制轨道交通大屏项目效果

利用canvas暗影性能与双线技巧绘制轨道交通大屏我的项目成果前言近日公司接到一个轨道零碎的需要,须要将地铁线路及列车实时地位展现在大屏上。既然是大屏我的项目,那视觉效果当然是第一重点,咱们能够先来看看我的项目实现后的效果图。能够看到两头线路里轨道的成果是十分炫酷的,那么本文的次要内容就是解说如何在canvas上绘制出这种成果。 剖析设计稿先看看设计稿中的轨道成果程序员解决问题时常常喜爱用到的办法是把一个大问题拆解为若干个小问题而后逐个解决,也就是分而治之,所以我在思考这个轨道成果的实现时,也是先思考到将它拆解。依据设计稿咱们能够看到这个线路实际上是由 外层的空心线+发光成果+内层的斑马线+倒影 组成的,所以咱们要做的就是如何解决这几个小问题。 实现成果绘制空心线与发光成果绘制空心线时咱们须要利用到[CanvasRenderingContext2D.globalCompositeOperation](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/globalCompositeOperation)这个属性,具体原理能够查看canvas 绘制双线技巧,本文不再做赘述。理解实现原理之后入手就很容易了,简述思路就是:通过ctx.globalCompositeOperation = "destination-out"绘制空心线,再利用canvas的暗影配置来模仿发光的成果。间接上代码: // 获取页面里的画布元素和其上下文对象var canvas = document.getElementById("canvas");var ctx = canvas.getContext("2d");// 因为ctx.globalCompositeOperation = "destination-out"会影响到画布上已有的图像// 所以须要先创立一个离屏canvas,把空心线绘制到离屏canvas上,再将离屏canvas绘制到页面的画布中var tempCanvas = document.createElement("canvas");tempCanvas.width = 800;tempCanvas.height = 800;var tempCtx = tempCanvas.getContext("2d");// 创立坐标点用来连线var points = [createPoint(50, 50), createPoint(500, 50), createPoint(500, 500)];// 配置参数var options = { color: "#03a4fe", // 轨道色彩 lineWidth: 26, // 总宽度 borderWidth: 8, // 边框宽度 shadowBlur: 20, // 暗影含糊半径};paint(ctx, points, options);// 绘制function paint(ctx, points, options) { paintHollow(tempCtx, points, options); // 将离屏canvas绘制到页面上 ctx.drawImage(tempCanvas, 0, 0);}/** * 绘制空心线 * @param {*} ctx 画布上下文 * @param {*} points 坐标点的汇合 * @param {*} options 配置 */function paintHollow( ctx, points, { color, lineWidth, borderWidth, shadowBlur }) { // 连线 paintLine(ctx, points); // 增加配置参数 ctx.lineWidth = lineWidth; ctx.strokeStyle = color; ctx.lineCap = "round"; ctx.lineJoin = "round"; // 利用暗影 ctx.shadowColor = color; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.shadowBlur = shadowBlur; ctx.stroke(); ctx.globalCompositeOperation = "destination-out"; ctx.lineWidth -= borderWidth; ctx.strokeStyle = color; ctx.stroke(); ctx.globalCompositeOperation = "source-over";}/** * 依据点位绘制连线 * @param {*} ctx 画布上下文 * @param {Array} points 坐标点的汇合 */function paintLine(ctx, points) { var pointIndex = 0, p0, value, pointCount = points.length; p0 = points[0]; ctx.beginPath(); ctx.moveTo(p0.x, p0.y); for (pointIndex = 1; pointIndex < pointCount; pointIndex++) { value = points[pointIndex]; ctx.lineTo(value.x, value.y); }}效果图 ...

December 29, 2020 · 3 min · jiezi

关于canvas:canvas可视化效果之内阴影效果

canvas可视化成果之内暗影成果楔子在之前的一个轨道交通可视化我的项目中,使用到了很多绘制技巧。 能够参考 之前的一篇文章 《利用canvas暗影性能与双线技巧绘制轨道交通大屏我的项目成果》 效果图中的轨道,就同时存在外发光和内发光成果的成果。 外发光成果咱们晓得外发光成果是很容易实现的,间接通过设置暗影成果即可达到。比方咱们轻易绘制一条线段,加上暗影成果,看起来就是外发光的成果: ctx.clearRect(0,0,canvas.width,canvas.height); ctx.shadowBlur= 20; ctx.shadowOffsetX = 0; ctx.shadowOffsetY = 0; ctx.shadowColor="red"; ctx.lineCap = "round"; ctx.lineJoin = "round"; ctx.lineWidth = 10; ctx.strokeStyle = "blue"; ctx.beginPath(); ctx.moveTo(300,300); ctx.lineTo(750,300); ctx.quadraticCurveTo(800,300,800,350); ctx.lineTo(800,450); ctx.quadraticCurveTo(800,500,750,500); ctx.lineTo(300,500); ctx.stroke();效果图如下: 如果绘制圆形成果如下: 下面的代码都容易了解,就是通过shadowBlur产生突变暗影的成果。 默认的暗影,咱们称之为外暗影,意思都是图像向往开展的暗影成果。 内暗影接下来的问题可能就变得有点难度。如果咱们须要如下的一个内暗影的成果呢? 有人说,简略,一个突变就搞定了。 那再看看上面这个图像呢? 还是没问题,还是能够通过突变来搞定,只是突变的stop设置要麻烦一点罢了。 如果在简单一些的图形呢,比方上面的线段成果: 对于下面的线段的内暗影成果,就很难应用简略的突变来实现了。 如何绘制内暗影成果要实现下面的内暗影成果,首先还是应用shadowBlur参数,而后把ctx的globalCompositeOperation参数设置为“source-out” 即可。 试试如下代码: ctx.globalCompositeOperation = 'source-out'; ctx.beginPath(); ctx.beginPath(); ctx.moveTo(300,300); ctx.lineTo(750,300); ctx.quadraticCurveTo(800,300,800,350); ctx.lineTo(800,450); ctx.quadraticCurveTo(800,500,750,500); ctx.lineTo(300,500); ctx.lineCap = "round"; ctx.shadowBlur =15; ctx.lineWidth = 20; ctx.shadowColor="blue"; ctx.fillStyle = 'red'; ctx.strokeStyle = 'red'; ctx.stroke();最终绘制的成果就是下面的线段图的成果: ...

December 29, 2020 · 2 min · jiezi

关于canvas:从零开始手把手教你使用javascriptcanvas开发一个塔防游戏01地图创建

我的项目演示 我的项目演示地址:体验一下 我的项目源码:我的项目源码 代码构造 本节做完成果 游戏主页面index.html <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"><html xmlns="http://www.w3.org/1999/xhtml"><head><meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /><meta http-equiv="X-UA-Compatible" /><title>塔防</title></head><body><img src="img/enemy.png" id="enemy_img" style="display:none;" /><img src="img/tower.png" id="tower_img" style="display:none;" /><img src="img/bullet.png" id="bullet_img" style="display:none;" /><img src="img/btn.png" id="btn_img" style="display:none;" /><div id="game" style="position:relative;float:left;width:600px;height:500px;"> <canvas id="map" width="500" height="500" style="background:url(img/bg.png) repeat;position:absolute;left:0;top:0;z-index:99;"></canvas> <canvas id="main" width="500" height="500" style="position:absolute;left:0;top:0;z-index:100;"></canvas> <canvas id="tower" width="500" height="500" style="position:absolute;left:0;top:0;z-index:100;"></canvas> <canvas id="select" width="500" height="500" style="position:absolute;left:0;top:0;z-index:101;"></canvas> <canvas id="info" width="100" height="500" style="background-color:black;position:absolute;left:500px;top:0;z-index:102;"></canvas></div><div style="float:left;width:500px;margin-left:50px;"> <p> 阐明:每种塔都能够降级到3级,降级的价格与自身建造雷同,卖掉的话就是总额的一半。<br/><br/> 第一种塔:降级到3级,有小偷性能,每攻打一次会偷取1块钱。<br/> 第二种塔:加速攻打,3级时能够同时加速两个。<br/> 第三种塔:多重攻打。攻打多个指标。<br/> 第四种塔:穿刺攻打,攻打一条线上,攻击力最高。<br/> 第五种塔:秒杀攻打,有肯定几率把敌人秒杀。<br/><br /> 抉择地图<select id="select_map"><option value="1">地图一</option><option value="2">地图二</option></select>,而后按<input type="button" value="开始" onclick="startGame()" />开始游戏! </p> </div><script type="text/javascript" src="js/tools.js"></script><script type="text/javascript" src="js/MapData.js"></script><script type="text/javascript" src="js/Map.js"></script><script type="text/javascript" src="js/Info.js"></script><script type="text/javascript" src="js/Enemy.js"></script><script type="text/javascript" src="js/Tower.js"></script><script type="text/javascript" src="js/Bullet.js"></script><script type="text/javascript" src="js/Game.js"></script><script type="text/javascript"> var isStart = false; function startGame(){ if(!isStart)Game.start(); else Game.restart(); isStart = true; } </script></body></html>游戏主模块game.js //游戏数据管制类var Game = { //图片列表信息 imgList : {}, //画布列表信息 canvasList : {}, //初始化 init : function(){ this.initImg(); this.initCanvas(); }, //初始化图片 initImg : function(){ this.imgList = { enemy_img : document.getElementById("enemy_img"), tower_img : document.getElementById("tower_img"), bullet_img : document.getElementById("bullet_img"), btn_img : document.getElementById("btn_img") } }, //初始化画布 initCanvas : function(){ this.canvasList = { map : document.getElementById("map").getContext("2d"), main : document.getElementById("main").getContext("2d"), info : document.getElementById("info").getContext("2d"), select : document.getElementById("select").getContext("2d"), tower : document.getElementById("tower").getContext("2d") } }, //开始 start : function(){ switch(document.getElementById("select_map").value){ case "1": MapData = MapOne; break; case "2": MapData = MapTwo; break; default: MapData = MapOne; break; } Map.draw(this.canvasList.map); this.timer = setInterval(Game.loop,20); }, //循环体 loop : function(){ Canvas.clear(Game.canvasList.main,500,500); }}Game.init();代码不言自明。简略阐明一下:Game.init()加载图片和Canvas对象,本游戏有5个Canvas对象,具体用处咱们在前面图层章节进行阐明。点击网页上的开始按钮,调用start办法。start办法加载地图,并画到canvas上。 ...

December 24, 2020 · 2 min · jiezi

关于canvas:canvas合成图片跨域相关问题

原文地址:https://segmentfault.com/a/1190000038457321作者:Fw恶龙本文首发于:思否一、前言前端在应用 canvas 合成图片时,如果波及到跨域的资源会导致绘制失败,首先须要该资源所在的服务器配置容许跨域拜访,其次在前端开发过程中还须要留神图片的应用形式,以下记录目前合成图片用到的几种形式在开发中须要留神的中央。 二、相干技术流程 *注:增加工夫戳的起因是动态资源可能波及到CDN,可能资源服务器容许跨域拜访,然而该资源所走的CDN不反对跨域拜访,增加工夫戳以保障加载源站资源。 三、代码*注:以下代码只提供相干细节,无奈间接复制应用,需依据应用的框架自行编写相干代码 1. img 标签外链应用形式<img id="bg" src="" crossorigin="anonymous">document.getElementById('bg').src = yourImgSrc + '?time=' + new Date().valueOf()2. js 动态创建 Image 对象var bg = new Image()bg.crossOrigin = 'anonymous'bg.onload = function() { // onload to do something}bg.src = yourImgSrc + '?time=' + new Date().valueOf()四、相干文档html2canvas官网API文档html2canvas 解决跨域图片的解决方案

December 12, 2020 · 1 min · jiezi

关于canvas:通过游戏学javascript系列第一节Canvas游戏开发基础

本节教程通过一个简略的游戏小例子,解说Canvas的基础知识。 最终成果:点击挪动的方块,方块上的分数会减少,方块的前进方向会扭转,并且方块的速度会减少。 在线演示 源码 HTML5引入了canvas元素。canvas元素为咱们提供了一块空白画布。咱们能够应用此画布来绘制和绘制咱们想要的任何货色。JavaScript为咱们提供了动静制作动画并绘制到画布上所需的工具。它不仅提供绘图和动画零碎,还能够解决用户交互。在本教程中,咱们将应用纯JavaScript制作根本的HTML5 Canvas框架,该框架可用于制作实在的游戏。在本教程的结尾创立了一个非常简单的游戏,以演示HTML5 Canvas与JavaScript联合的劣势。 HTML5 Canvas根本游戏框架让咱们围绕canvas元素创立一个根本的游戏框架。咱们须要一个HTML5文件和一个JavaScript文件。HTML5文件应蕴含canvas元素和对JavaScript文件的援用。JavaScript文件蕴含将代码绘制到canvas元素的代码。 这是HTML5文件index.html: <head><meta charset="UTF-8"><title>Canvas Example</title><script type="text/javascript" src="framework.js"></script></head><body><canvas id="viewport" width="640" height="480"></canvas></body></html> 如您所见,JavaScript文件game.js蕴含在html文件的头部。画布元素以名称“ viewport”定义,其宽度为640像素,高度为480像素。在咱们的framework.js中,咱们须要应用其名称查找canvas元素,以便能够在其上进行绘制。咱们正在创立的框架应反对渲染循环以及玩家与鼠标的交互。对于渲染循环,咱们将应用Window.requestAnimationFrame()。通过增加鼠标事件侦听器来启用鼠标交互。 这是JavaScript文件framework.js: // The function gets called when the window is fully loadedwindow.onload = function() { // Get the canvas and context var canvas = document.getElementById("viewport"); var context = canvas.getContext("2d"); // Timing and frames per second var lastframe = 0; var fpstime = 0; var framecount = 0; var fps = 0; // Initialize the game function init() { // Add mouse events canvas.addEventListener("mousemove", onMouseMove); canvas.addEventListener("mousedown", onMouseDown); canvas.addEventListener("mouseup", onMouseUp); canvas.addEventListener("mouseout", onMouseOut); // Enter main loop main(0); } // Main loop function main(tframe) { // Request animation frames window.requestAnimationFrame(main); // Update and render the game update(tframe); render(); } // Update the game state function update(tframe) { var dt = (tframe - lastframe) / 1000; lastframe = tframe; // Update the fps counter updateFps(dt); } function updateFps(dt) { if (fpstime > 0.25) { // Calculate fps fps = Math.round(framecount / fpstime); // Reset time and framecount fpstime = 0; framecount = 0; } // Increase time and framecount fpstime += dt; framecount++; } // Render the game function render() { // Draw the frame drawFrame(); } // Draw a frame with a border function drawFrame() { // Draw background and a border context.fillStyle = "#d0d0d0"; context.fillRect(0, 0, canvas.width, canvas.height); context.fillStyle = "#e8eaec"; context.fillRect(1, 1, canvas.width-2, canvas.height-2); // Draw header context.fillStyle = "#303030"; context.fillRect(0, 0, canvas.width, 65); // Draw title context.fillStyle = "#ffffff"; context.font = "24px Verdana"; context.fillText("HTML5 Canvas Basic Framework ", 10, 30); // Display fps context.fillStyle = "#ffffff"; context.font = "12px Verdana"; context.fillText("Fps: " + fps, 13, 50); } // Mouse event handlers function onMouseMove(e) {} function onMouseDown(e) {} function onMouseUp(e) {} function onMouseOut(e) {} // Get the mouse position function getMousePos(canvas, e) { var rect = canvas.getBoundingClientRect(); return { x: Math.round((e.clientX - rect.left)/(rect.right - rect.left)*canvas.width), y: Math.round((e.clientY - rect.top)/(rect.bottom - rect.top)*canvas.height) }; } // Call init to start the game init();};下面的代码绘制了一个带有边框,题目和每秒帧数的简略框架。这是代码生成的内容 ...

December 11, 2020 · 4 min · jiezi

关于canvas:canvas笔记

canvas代码 根本应用<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <title>Title</title> <style> #canvas { background: #000; } </style></head><body><canvas id="canvas" width="400" height="400"></canvas><script> var canvas = document.getElementById("canvas"); var context = canvas.getContext("2d"); context.beginPath(); context.arc(100, 100, 50, 0, Math.PI * 2, true); context.closePath(); context.fillStyle = 'rgb(255,255,255)'; context.fill();</script></body></html>也可应用js设置canvas的宽高,以及所代表的的意思<template> <canvas id="canvas" ref="canvas"></canvas></template><script> export default { methods: { init() { let canvas = this.$refs.canvas let context = canvas.getContext('2d')//获取到 Canvas 的上下文环境 代表一个二维渲染上下文 let cx = canvas.width = 400 let cy = canvas.height = 400 context.beginPath() // 起始一条门路,或重置以后门路 context.arc(100,100,50,0,Math.PI*2,true)// 创立弧/曲线 context.closePath()// 创立从以后点回到起始点的门路 context.fillStyle = 'rgb(255,255,255)' // 设置或返回用于填充绘画的色彩、突变或模式 context.fill()// 填充以后绘图(门路) } }, mounted () { this.init() }, }</script><style lang="scss" scoped>#canvas{ background-color: #000;}</style>留神*不要应用 CSS 设置。因为默认创立一个 300 150 的画布,如果应用 CSS 来设置宽高的话,画布就会依照 300 150 的比例进行缩放,也就是将 300 150 的页面显示在 400 400 的容器中* ...

November 16, 2020 · 3 min · jiezi

关于canvas:JavaScript-Uncaught-SyntaxError-Illegal-return-statement

在应用 canvas 时,为了判断 canvas 中的 getContext 办法是否存在,就应用了罕用的 if 判断条件来阻止代码持续往下执行: let canvas = document.querySelector('#canvas')if (!canvas.getContext) { return;}然而代码仿佛并没有失常的运行,并且浏览器控制台抛出一个谬误: Uncaught SyntaxError: Illegal return statement(未捕捉的语法错误:非法的返回语句)stackoverflow下面提了一个雷同的问题。 并答复给出了起因:return 仅在函数外部有意义。 也就是说 return 只能在函数外部应用才能够: function myFun() { return '...';}既然 return 只能在函数外部应用,那么我想当 getContext 不存在时,用其余办法终止代码执行就能够了。 那么什么时候能够终止代码继续执行呢?当程序抛出一个谬误的时候仿佛就能够终止代码继续执行了: throw new Error();最终,我把开始的 return 改成了 throw new Error,并且抛出了相应的提示信息: let canvas = document.querySelector('#canvas')if (!canvas.getContext) { throw new Error('没有找到 canvas 中 getContext 办法!');}

November 7, 2020 · 1 min · jiezi

关于canvas:canvas绘图模糊处理高分屏下的canvas绘制

场景在挪动端通过 H5 的 canvas 标签绘制图表的时候,不通过任何解决的图表相比拟其余元素看起来会有些含糊。 序先看上面一组高分屏下的圆,下面是一般 div 元素“绘制”的圆,上面是通过 canvas 绘制的圆。能够看出,上面的圆相比拟是含糊的。 为什么会这样?一台一般屏幕上的像素(逻辑像素),能够当做是失常的像素(css中设置的像素),当画一个100px的元素,他就是一个100px的元素。 然而,在呈现了一些高分辨率的屏幕之后,就产生了一些变动。随之也呈现了一个属性 devicePixelRatio ,它容许咱们去查问设施屏幕的像素比。高分屏下(假如devicePixelRatio为3),在css设置的100px(逻辑像素),理论渲染的是300px的物理像素。 而不论是否为高分屏, canvas 中的单位 1 (逻辑像素),就是 1 物理像素,所以在高分屏下,canvas 绘制的图片看起来就含糊了(能够设想成,一张清晰度失常的一般图片为了布满整个背景被强行放大 n 倍,所以看起来含糊了)。 如何解决?这个波及到 canvas 标签的一些个性。即 canvas 元素的大小(宽高)会影响 canvas 画布的内容。 举个栗子: 画一个半径为 50 的圆,画布默认宽高为 300 * 150 ,不设置 canvas 元素宽高的状况下是一个圆;如果设置 canvas 元素的宽高都为 150, 那么画布 x 轴方向的内容会被压缩,为原来的 1/2 倍。 那么,利用这个个性。 思路: 高分屏下(假如devicePixelRatio为 3,逻辑像素为 100px),将 canvas 画布大小 [乘以 3] 转换成理论像素(物理像素)大小,而后通过css设置 canvas 元素的大小 为逻辑像素大小 [100px]。缩放后,画布内容就是高清的了。然而这个时候画布内容是放大的,这个时候能够应用 canvas 的 scale() 去进行放大 3 倍就能够了。 ...

November 3, 2020 · 1 min · jiezi

关于canvas:乘风破浪的canvas

本篇文章次要波及的内容有 canvas根本理解什么是canvas?canvas是HTML5新增的元素,可用于通过javascript中的脚本来绘制图形;例如它能够用于绘制图形,创立动画。canvas最早由Apple引入webkit; 咱们能够应用canvas标签来定义一个canvas元素,应用canvas标签时,倡议成对呈现,不要应用闭合的模式。canvas默认具备宽高,width: 300px;height: 150px; canvas中的替换内容canvas标签很容易定义一些替换内容,因为某些较老的浏览器(尤其是IE9之前的IE浏览器),不反对HTML元素canvas,然而在这些浏览器上你应该给用户展现一些替换内容。这非常简单,咱们只须要在canvas标签中提供替换文本就能够了,反对canvas的浏览器将会疏忽容器中蕴含的内容,并且只是失常渲染canvas,不反对canvas的浏览器会显示替换内容。 canvas标签中的两个属性canvas看起来和img标签看起来很像,惟一不同的是它并没有src和alt属性。实际上canvas标签只有两个属性,width和height;这些都是可选的,当没有设置高宽时,canvas会初始化宽度为300px和高度150px。 画布高宽html属性设置width height 时只影响画布自身不影响画布内容css属性设置width height时岂但会影响画布自身的高宽,还会使画布中的内容等比例缩放(缩放参照于画布默认的尺寸) !!!所以不要通过css设置画布的尺寸 渲染上下文canvas元素只是创立了一个固定大小的画布,要想在它下面绘制内容,咱们须要找到它的渲染上下文; canvas元素中有一个getContext()办法,这个办法是用来取得渲染上下文和它的绘画性能。getContext只有一个参数,上下文格局 上下文格局类型: "2d", 建设一个 CanvasRenderingContext2D 二维渲染上下文。 "webgl" (或"experimental-webgl") 这将创立一个 WebGLRenderingContext 三维渲染上下文对象。只在实现WebGL 版本1(OpenGL ES 2.0)的浏览器上可用。 "webgl2" (或 "experimental-webgl2") 这将创立一个 WebGL2RenderingContext 三维渲染上下文对象。只在实现 WebGL 版本2 (OpenGL ES 3.0)的浏览器上可用。 "bitmaprenderer" 这将创立一个只提供将canvas内容替换为指定ImageBitmap性能的ImageBitmapRenderingContext 。const canvas = document.querySelector('canvas');if (canvas.getContext) { const ctx = canvas.getContext('2d');}绘制矩形HTML中的元素canvas只反对一种原生(调用一个api)的图形绘制:矩形;所有其余的图形的绘制都至多须要生成一条门路。 绘制矩形canvas提供三种办法绘制矩形: 1. 绘制一个填充矩形 ctx.fillRect(x, y, width, height); 2. 绘制一个矩形的边框 ctx.strokeRect(x, y, width, height); 3. 革除指定矩形的区域,让革除局部齐全通明 ctx.clearRect(x, y, width, height); x,y指定了在canvas画布上所绘制的左上角(绝对于原点)的坐标。width和height设置绘制矩形的尺寸(存在边框的话,边框会在width上占据一个边框的宽度,高度同理)strokeRect时,边框像素渲染问题按理来说渲染进去的边框应该是1px,然而canvas在渲染矩形边框时,边框的宽度是平均分在偏移地位的两侧。 ...

October 12, 2020 · 11 min · jiezi

关于canvas:前端面试每日-31-第542天

明天的知识点 (2020.10.09) —— 第542天 (我也要出题)[html] canvas生成图片有没有跨域问题?如果有如何解决?[css] 说说你对Bootstrap网格零碎的工作原理的了解[js] 写一个办法获取指定两个日期之间相隔的所有日期[软技能] 说说你对http的报文字段upgrade的了解,它有什么作用?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

October 9, 2020 · 1 min · jiezi

关于canvas:使用canvas图片压缩尺寸调整

记录一下 第一步:应用input, capture="camera"调用原生相机 <input onchange="photoChange(event)" type="file" accept="image/*" capture="camera" id="upload_file" />第二步:获取到图片信息应用FileReader读取到图片的信息 function photoChange(el) { const _this = this; var file = el.target.files[0];// name: "dangqi1.png" || type: "image/png" _this.fileName = file.name; var type = file.type.split('/')[0]; if (type === 'image') { var reader = new FileReader(); // FileReader reader.readAsDataURL(file); reader.onloadend = function () { var dataURL = reader.result; // 压缩图片 _this.compressImg(dataURL, { quality: 0.92, // 为了保留图片品质,压缩值设置为0.92 width: 1000 // 把图片尺寸调整为宽为1000的,高度自适应 }, file); }; } else { alert('上传了非图片'); } }第三步:通过canvas进行图片解决 ...

September 4, 2020 · 1 min · jiezi

关于canvas:重磅音视频与白板完美同步即构自研白板SDK上线

身处“教育科技”时代,用于晋升教学质量的“黑科技”越来越多。但无论是线下教室,还是线上课堂,有一样货色始终存在——黑板/白板。 线下教室里的黑板,是老师进行板书、书写示范的外围区域,而线上课堂的白板,除了展现老师的课件外,还是屏幕两端的老师和学生异地互动的承载物。 白板好不好用,关系着老师的上课品质,更间接影响学生的上课体验。一个优良的线上互动白板,除了要反对画笔、文本、橡皮擦、激光笔、直线等多样化教学工具外,还要具备这三个方面的能力: 1.功能齐全:反对“矢量放大文件高清不含糊、PPT动静展现高度还原动画”等能力,满足客户对白板的多样化需要。 2.互动高效:保障“音画与白板的实时同步、多人操作有序合作”的体验,让线上课堂的互动合作更高效。 3.可用性强:解决“白板应用对带宽/内存的占用高、跨国跨网提早大**”等问题,进步互动白板在低端机、跨国教学等场景中的适用性。 但目前,市面上大部分的互动白板厂商提供的服务仍存在能力不全、音视频与白板不同步、无奈多人合作等问题,深深困扰着很多线上教育平台。 因而,在接管到多家教育客户的白板痛点需要,并深刻行业内调研了多家白板/文件共享厂商后,即构决定自研互动白板及文件共享零碎,让用户取得更优质的多人白板互动协同体验。 目前即构互动白板和文件共享SDK已上线,点击这里即可下载体验APP 在提供全面的互动白板和文件共享能力,满足教育客户的教学互动需要外,即构还针对性解决了机构在应用白板利用过程中的痛点,帮忙平台取得品质更好的线上课堂,无效升高工单率、退课率。 一、白板利用痛点一:白板能力不全线上课堂中,老师要共享课件作为教学内容反对。但如果白板在能力上不够全面,就会让老师辛苦筹备了10分的课件,只展现进去3分,教学效果大打折扣。 反对的文件类型太少 幼教、K12教育重视动静PPT,学科类课件则波及到excel、word、pdf等多种类型文件,如果只能反对多数文件类型,就会呈现老师局部课件无奈分享、授课模式受限等问题; 课件分享太含糊 在教学过程中,学生应用手机/平板等小屏幕设施上课时,会放大查看老师分享的课件,但局部文件共享只是将文件转化为固定尺寸图片之后进行共享,会导致课件放大后变的十分含糊,间接影响到学生的上课体验。 局部白板文件放大后含糊 应用即构互动白板 SDK,能够无效避开以上问题: 反对动静PPT展现,反对10+文件格式 即构互动白板反对将PPTX格局动静转码转成HTML5,高度还原原始PPT上的动画内容;反对包含PPT、DOC、XLS、PDF、JPG、BMP、TXT等在内的10多种支流文件格式;同时还提供多表单的残缺显示,兼容每页大小不同的异形PDF文件等能力。 矢量放大,高清出现共享文件 即构互动白板中的文件共享采纳矢量放大,可原样出现文件内容。通过矢量渲染文件内容,即使放大也出现高清晰度的文件和白板内容,保障学生失常上课。即构白板文件放大后清晰的成果 二.白板利用痛点二:高效互动难除了可能完满的共享课件外,作为老师和学生之间的高频互动载体,白板还须要有弱小的互动能力。 音视频与白板/文件实时同步难 因为音视频与白板分属不同的厂商,目前市场上大部分的白板都很难做到与音视频实时同步。这间接导致了上课时音画不同步,例如老师说“来看下一页”,并进行翻页,学生端会先看到下一页,再听到老师的声音。 多人实时操作难 在上课过程中,老师和学生多人同时操作时,容易因为同时对同个图元操作而产生抵触,导致老师学生单方观看的内容不同步。例如当老师和学生同时选中同一个元素,老师往左拖动,学生往右拖动,最初老师看到的在右边,学生看到的在左边。 基于齐全自研的技术劣势,即构将音视频与白板紧密结合,可实现音视频与白板的实时同步,多人合作有序进行。 音视频与白板/文件实时同步 即构通过推流时在音视频流和白板文件信令上带上工夫戳,拉流时通过对齐两者的工夫戳,做到音视频流与白板绘制的对齐同步。同时针对网络异样导致的白板文件多端不同步问题,即构SDK通过实时监控网络状态,利用信令SEQ实时同步服务端最新状态,确保多端同步。 多人实时操作,高效互动 在多端同时操作的场景下,即构白板/文件共享SDK通过自研的智能多人实时抵触断定算法,保障互动的有序进行,可实现百人同时高效互动合作,无论是1V1还是多人互动小班,都能满足课堂上的课件合作需要。 即构白板多人操作的成果 三.白板利用痛点三:卡顿掉线体验差每个在线教育平台,都对课堂品质有着超高要求。一旦呈现卡顿、掉线、听不清等问题,很容易引发家长投诉,工单率回升,重大时甚至导致学生退课。 白板带宽内存占用过高导致卡顿 白板信息传输需占用网络带宽和内存,数据量太大时会抢占信令和音视频等的带宽资源,造成音视频卡顿等问题。当带宽资源和内存占用过多时,对设施性能要求也同步进步。 互动白板在跨国教学中的品质难保障 跨国教学已成为常态,平台的老师大都来自北美、欧洲或东南亚。但因为网络部署、服务节点的限度,局部平台的白板服务在跨国教学时容易呈现卡顿、掉线问题。 即构基于自研技术,从底层优化信令服务,极致压缩白板传输数据量;基于寰球500+服务节点笼罩,实现国内外教学无差别体验。 优化信令服务,升高白板对带宽和内存的占用 即构通过优化信令服务,精简信令协定,升高白板文件操作时同步和缓存过程中的数据量,升高对带宽和内存的占用。在文件渲染和白板笔画绘制等不同场景下,基于canvas和svg抉择不同的渲染计划,在保障渲染高流畅性的根底上,升高渲染过程中对内存和cpu的开销。 寰球部署,跨国教学体验好 即构音视频服务已笼罩寰球200个国家和地区,咱们会依据客户平台用户的散布特点,提供国内和海内服务部署,全站减速,凭借分布式技术保障各数据中心的一致性,真正做到就近接入、就近读取,对于远距离的跨国传输,实现超低延时拜访。 技术的提高,让教育插上腾飞的翅膀。基于优良的自研能力,即构为教育客户提供能力更全面、互动更高效、品质更优质的互动白板和文件共享能力,让每一次的课堂教学都更活泼牢靠。

August 18, 2020 · 1 min · jiezi

关于canvas:前端面试每日-31-第478天

明天的知识点 (2020.08.06) —— 第478天 (我也要出题)[html] 请应用canvas画一个椭圆[css] 请举例说明width:fit-conten有什么应用场景[js] 随机生成一个指定长度的验证码[软技能] 咱们会常常用到ping命令,你晓得它的作用和原理吗?《论语》,曾子曰:“吾日三省吾身”(我每天屡次检查本人)。前端面试每日3+1题,以面试题来驱动学习,每天提高一点!让致力成为一种习惯,让奋斗成为一种享受!置信 保持 的力量!!!欢送在 Issues 和敌人们一起探讨学习! 我的项目地址:前端面试每日3+1【举荐】欢送跟 jsliang 一起折腾前端,零碎整顿前端常识,目前正在折腾 LeetCode,打算买通算法与数据结构的任督二脉。GitHub 地址 微信公众号欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个Star, 同时欢送微信扫码关注 前端剑解 公众号,并退出 “前端学习每日3+1” 微信群互相交换(点击公众号的菜单:交换)。 学习不打烊,充电加油只为遇到更好的本人,365天无节假日,每天早上5点纯手工公布面试题(死磕本人,愉悦大家)。心愿大家在这虚夸的前端圈里,放弃沉着,保持每天花20分钟来学习与思考。在这变幻无穷,类库层出不穷的前端,倡议大家不要等到找工作时,才狂刷题,提倡每日学习!(不忘初心,html、css、javascript才是基石!)欢送大家到Issues交换,激励PR,感激Star,大家有啥好的倡议能够加我微信一起交换探讨!心愿大家每日去学习与思考,这才达到来这里的目标!!!(不要为了谁而来,要为本人而来!)交换探讨欢送大家前来探讨,如果感觉对你的学习有肯定的帮忙,欢送点个[Star]

August 6, 2020 · 1 min · jiezi

通过canvas计算任意两个颜色的插值

通过canvas能够帮助咱们做很多色彩计算的辅助,比方色彩转换,突变色彩计算。 对于色彩转换,之前写过一篇文章:《通过canvas转换色彩为RGBA格局及性能问题》, 读者能够查阅该篇文章来理解。 本文着重解说突变计算色彩的插值计算。 计算任意两个色彩的插值理论利用中通常要计算两个色彩的之间插值后果,比方计算“red”和“green” 之间的插值。 比拟通用的办法就是首先通过《通过canvas转换色彩为RGBA格局及性能问题》中提到的办法把色彩转换成RGBA格局,因为RGBA格局是都是数字的模式,能够间接进行插值运算。大抵的代码如下: let rgba1 = getRgba('red'), rgba2 = getRgba('green');let result = interpolate(rgba1,rgba2,0.5);除此之外,还能够通过canvas的线性突变来计算两个色彩之间的插值。 首先,咱们晓得canvas中反对一种突变叫LinearGradient。 创立线性突变的函数是: context.createLinearGradient(xStart,yStart,xEnd,yEnd)其中参数(xStart,yStart)示意突变的起始点,(xEnd,yEnd)的示意突变的终止点。该函数会返回一个线性突变对象。如下: var grd = ctx.createLinearGradient(100,100,500,100)突变对象下面有一个能够增加色彩点的函数: grd.addColorStop(stop,color);这里的stop传递的是 0 ~ 1 的浮点数,代表点到(xStart,yStart)的间隔占整个突变长度是比例。 无关线性突变的更多常识,能够参考:https://xiaozhuanlan.com/topic/5473801692我能够得出,实现突变色彩插值计算得思路大抵如下: 首先创立一个canvas,指定canvas得宽度为100(依据渐变得精密度能够调整),高度为1而后创立一个线性突变对象,线性渐得尺寸和canvas尺寸保持一致,通过addColorStop增加色彩点,stop为0的时候增加第一种色彩,stop为1的时候增加第二种色彩。插值计算出插值色彩所在的地位,通过canvas的getImageData办法获取。大抵代码如下: var canvas = document.createElement("canvas"); canvas.width = 100; canvas.height = 1; var ctx = canvas.getContext('2d'); var grd = ctx.createLinearGradient(0,1,100,1)function getInterpolateColor(color1,color2,r) { grd.addColorStop(0,color1); grd.addColorStop(1,color2); ctx.fillStyle = grd; ctx.fillRect(0, 0, 100, 1); var x = parseInt(r * 100); var colorData = ctx.getImageData(x, 0, 1, 1).data; return { r: colorData[0], g: colorData[1], b: colorData[2], a: colorData[3] }; }下面代码应用了getImageData办法,无关getImageData的阐明,能够参考:https://xiaozhuanlan.com/topic/5473801692 ...

July 13, 2020 · 1 min · jiezi

Canvas绘制圆点线段

最近一个小伙遇到一个需求,客户需要绘制圆点样式的线条。 大致效果是这样的: 思路一:计算并使用arc填充他自己实现了一种思路,然后咨询我有没有更好的思路。 先看看他的思路是如何实现的,大致代码如下: // 绘制圆点线,通过计算在线条上进行插值运算,计算出需要绘制圆点的一系列点位// 然后调用drawDot方法绘制圆点 function DrawDottedLine(x1,y1,x2,y2,dotRadius,dotCount,dotColor){ var dx=x2-x1; var dy=y2-y1; var spaceX=dx/(dotCount-1); var spaceY=dy/(dotCount-1); var newX=x1; var newY=y1; for (var i=0;i<dotCount;i++){ drawDot(newX,newY,dotRadius,dotColor); newX+=spaceX; newY+=spaceY; } drawDot(x1,y1,3,"red"); drawDot(x2,y2,3,"red"); }// 绘制圆点 function drawDot(x,y,dotRadius,dotColor){ ctx.beginPath(); ctx.arc(x,y, dotRadius, 0, 2 * Math.PI, false); ctx.fillStyle = dotColor; ctx.fill(); }通过上面的简单的示意代码可以看出,绘制逻辑是通过计算直线之间的点位,然后再相应的点上面绘制圆形。该方法最终可以达到效果,可是有如下问题: 存在性能问题如果是贝塞尔曲线曲线,可能会涉及到复杂的运行。 贝塞尔曲线的相关知识,可以参考下文进行了解:深入理解贝塞尔曲线当然此思路在绘制一些更加复杂的效果的时候,可能会有用。比如沿线绘制五角星,其他任意形状或者图片等等。或者要绘制的是圆圈而不是填充的圆形的效果,也需要使用此方法。 但是如果只是绘制圆点线,我们可以使用更加简便的方法,主要思路就是使用setLineDash方法+lineCap设置 思路二 setLineDash方法+lineCap设置lineCap介绍CanvasRenderingContext2D.lineCap 是 Canvas 2D API 指定如何绘制每一条线段末端的属性。有3个可能的值,分别是:butt, round and square。默认值是 butt。 使用时,直接赋值即可: ctx.lineCap = "butt";ctx.lineCap = "round";ctx.lineCap = "square";下面从左到右分别是butt, round ,square的效果: ...

June 9, 2020 · 1 min · jiezi

Canvas学习整理

填充纯色var cvs = document.getElementById("myCanvas"); var ctx = cvs.getContext("2d"); ctx.fillStyle = "#FF0000"; ctx.fillRect(0,0,150,75); 渐变填充径向渐变var cvs = document.getElementById('myCanvasBgGradientColor')var ctx = cvs.getContext("2d")// 创建渐变1 - 45度径向渐变var grd1 = ctx.createLinearGradient(0, 0, 100, 100);grd1.addColorStop(0, 'red')grd1.addColorStop(1, 'white')// 创建渐变2 - 横向径向渐变var grd2 = ctx.createLinearGradient(100, 100, 200, 100);grd2.addColorStop(0, 'red')grd2.addColorStop(1, 'white')// 填充渐变1ctx.fillStyle = grd1ctx.fillRect(0, 0, 100, 100)// 填充渐变2ctx.fillStyle = grd2ctx.fillRect(100, 100, 200, 200) 球型/放射渐变var cvs = document.getElementById('myCanvasBgRadialGradientColor')var ctx = cvs.getContext("2d")// 创建渐变var grd = ctx.createRadialGradient(100, 100, 0, 100, 100, 90);grd.addColorStop(0, 'red')grd.addColorStop(1, 'green')// 填充渐变ctx.fillStyle = grdctx.fillRect(0, 0, 200, 200) ...

June 6, 2020 · 1 min · jiezi

图形选择多边形网格化

在说多边形网格化之前,我们先画个复杂点的多边形出来。准备一组用于逆时针绘图的顶点: const points=[ new Vector2(0,0), new Vector2(0,600), new Vector2(600,600), new Vector2(600,200), new Vector2(200,200), new Vector2(200,400), new Vector2(300,400), new Vector2(300,300), new Vector2(500,300), new Vector2(500,500), new Vector2(100,500), new Vector2(100,100), new Vector2(600,100), new Vector2(600,0)];基于这组顶点绘制一个多边形: const poly=new Poly({ position:new Vector2(50,50), stroke:true, close:true, vertices:[...points]});poly.draw(ctx); 思路解析接下来,我们就说一下网格化思路:1.先判断points 中的顶点数量,若等于3,那就直接将这三个点存储起来。否则,下一步。2.把多边形里的第一个点作为起点 3.从起点连接下下个点,构成一个三角形,如▲012 4.对上面的三角形做两个判断: 三角形是否包含了其它顶点,如上图▲012三角形是否为凹三角形,如下图▲678 若出现上面任何一种情况,就把起点的下一个点做为起点,执行第1步。若上面的情况都不存在,如下图▲456,那就把构成这个三角形的顶点存储起来,把下一个点从points 中删除,把下下个点做为起点,执行第1步。 到这里,网格化的思路就说完了,这是一个递归方法,最终效果如下: 接下来,咱们就说一下这整个逻辑中涉及的两个知识点:1.三角形是否包含了其它顶点。这个方法我们上一章说过,遍历其它顶点,逐一判断即可,在此便不再赘述。2.判断三角形的凹凸。首先我们在定点的时候要遵守一个规则,这里是逆时针定点。这样我们在用叉乘的方法求三角形的面积的时候,面积为正,那就是凹三角形;面积为负,那就是凸三角形。求三角形面积的方法: function triangleArea(p0,p1,p2){ const [bx,by,cx,cy]=[ p1.x-p0.x, p1.y-p0.y, p2.x-p0.x, p2.y-p0.y, ]; return (bx*cy-cx*by)/2;}这个求面积方法用到的是一个叉乘算法。利用这个算法还可以计算任意边数的多边形面积,详情我写过的另一篇文章:多边形面积 整体代码的实现步骤1.绘制多边形 const points=[ new Vector2(0,0), new Vector2(0,600), new Vector2(600,600), new Vector2(600,200), new Vector2(200,200), new Vector2(200,400), new Vector2(300,400), new Vector2(300,300), new Vector2(500,300), new Vector2(500,500), new Vector2(100,500), new Vector2(100,100), new Vector2(600,100), new Vector2(600,0)];const poly=new Poly({ position:new Vector2(50,50), stroke:true, close:true, lineWidth:2, vertices:[...points]});poly.draw(ctx);2.声明用于存储三角形的数组triangles[] 和起点 ...

May 26, 2020 · 2 min · jiezi

图形选择网格选择

网格选择,顾名思义,就是把多边形变成网格后选择(此方法只适用于多边形,若是曲线,我们就得将其分段)。 这样,网格选择就分成了两步:1.将多边形分解为多个三角形。2.判断鼠标点是否在三角形中。 我们先从最基础的判断鼠标点是否在三角形中开始说,我们可以用鼠标点和三角形其它顶点的夹角之和来判断。 点D 在▲ABC 中:∠ADB+∠BDC+∠CDA=360°点D 不在▲ABC 中:∠ADB+∠BDC+∠CDA<360°接下来我们先说一下一下如何基于三个点计算夹角,如∠mon 先根据三个点画一个角: const [m,o,n]=[ new Vector2(300,50), new Vector2(300,200), new Vector2(500,200),];const poly=new Poly({ stroke:true, vertices:[m,o,n]});poly.draw(ctx); 1.把∠mon 的顶点o 归零: m.x-=o.x;m.y-=o.y;n.x-=o.x;n.y-=o.y;2.根据余弦定理,可以基于点m乘以点n 的点积,om的长度乘以on 的长度,计算∠mon 的余弦值 const dot=(m.x*n.x+m.y*n.y);const len=Math.sqrt(m.x*m.x+m.y*m.y)*Math.sqrt(n.x*n.x+n.y*n.y);const cosTheta=dot/len;3.根据反余弦方法acos() 求∠mon,也就是下面的theta const theta=Math.acos(cosTheta);Math.acos() 可以自动把根据余弦取得的弧度限制在[0,Math.PI]之内。如果我们使用Math.atan2(y,x),会得到基于x 轴正方向的弧度,而且y 值为负的时候,atan2(y,x) 的值也是负数,这是不适合夹角求和的。至于这里面涉及的点积公式,这是个纯数学的知识,大家先知道其用法即可,我们后面得为它再起一章:图形选择-点积公式。 我们知道了一个夹角的求法之后,那就可以去求∠ADB+∠BDC+∠CDA 的夹角和了。其和若小于360°,那就在三角之外,否则在三角之中。我把这样的方法封装在了Vector2d 类里: inTriangle(p0,p1,p2){ const [a0,a1,a2]=[ p0.includedAngleTo(this,p1), p1.includedAngleTo(this,p2), p2.includedAngleTo(this,p0), ]; const sum=a0+a1+a2; return Math.PI*2-sum<=0.01;}注:0.01 是一个用于容差的数。电脑对浮点数的运算不是绝对精确的,所以我没有直接用Math.PI*2===sum来做判断,而且是让鼠标点只要靠近了三角形一定范围,就算选择上了。 p1.includedAngleTo(p2,p3) 是求∠p1p2p3 的方法: includedAngleTo(v1,v2){ const [s1,s2]=[ this.clone().sub(v1), v2.clone().sub(v1), ]; return s1.includedAngle(s2);}p1.includedAngle(p2) 求的是角的顶点归零后夹角 ...

May 26, 2020 · 1 min · jiezi

图形选择物质不易

这一章,咱们来说鼠标如何选择变换后的图形。首先给大家举个栗子:在2029年末世之战的时候,终结者想干掉人类领袖大壮,可是大壮太强,而且其实力需要复杂运算才能知晓。所以终结者就想回到1997年,在大壮实力弱小、且已知的情况下将其干掉。这样根据物质不易法则,2029年末世之战中的人类领袖大壮也就不会存在。 接下来给大家解密终结者穿梭时间的方法终结者需要知道数据:1997年大壮的初始属性,比如构成大壮轮廓的顶点集合;大壮从1997到2029的变换信息,比如其大壮移动了多少、旋转了多少、长大了多少。根据物质不易法则:物质不变,空间不变;空间不变,时间不变。将物质不易法则逆推,依旧成立:物质改变,空间改变;空间改变,时间改变。所以终结者想要回到1997 年,只要根据大壮的变换规则逆向变换自己的位置就可以回到1997年。 比如:从1997年到2029年,大壮沿x 轴移动了100,沿y 轴移动了200,旋转了90度,变大了2倍。终结者(鼠标点位)就要沿x 轴移动了-100,沿y 轴移动了-200,旋转-90度,点位到圆心点的距离缩小2倍。注意:终结者的变换顺序要和大壮的变换顺序一致;终结者改变的只是点位,点没有尺寸,其点位变换本质是在目标对象所在的canvas画布的坐标系的位移。只有如此,当终结者穿梭到1997年的时候,才可以精准定位大壮。图示: 接下来我们就在代码里走一下这个原理:先画了一颗爱心,其所在的canvas 画布坐标系在x、y方向分别位移了(300,400) const poly=new Poly({ position:new Vector2(300,400), stroke:true, close:true, crtPath:function(ctx){ ctx.beginPath(); ctx.moveTo(0,0); ctx.bezierCurveTo(-200,-50,-180,-300,0,-200); ctx.bezierCurveTo(180,-300,200,-50,0,0); }});poly.draw(ctx); 若我的鼠标想要选择这颗爱心,那它的位置就要基于爱心的变换信息反向变换:x、y方向分别位移(-300,-400)。代码如下: const mousePos=getMousePos(event);poly.crtPath(ctx);const [nx,ny]=[ mousePos.x-poly.position.x, mousePos.y-poly.position.y];const bool=ctx.isPointInPath(nx,ny);图形变换中的位移说完了,那它的旋转、缩放也是同样道理,就是让鼠标位置基于图形的变换信息反向变换。下面我直接将所有变换的方法封装到了获取鼠标点位的方法里,即getMousePos(event,poly) ,event是事件,poly 是图形。代码如下: const getMousePos=function(event,obj=null){ //获取鼠标位置 const {clientX,clientY}=event; //获取canvas 边界位置 const {top,left}=canvas.getBoundingClientRect(); //计算鼠标在canvas 中的位置 const x=clientX-left; const y=clientY-top; const mousePos=new Vector2(x,y); if(obj){ const {position,scale,rotation}=obj; mousePos.sub(position); mousePos.rotate(-rotation); mousePos.divide(scale); } return mousePos;};mousePos 是一个Vector2 对象,其中封装了关于向量的常用方法。如: export default class Vector2{ constructor(x=0,y=0){ this.x=x; this.y=y; } //减法 sub(v){ this.x -= v.x; this.y -= v.y; return this; } //基于原点旋转 rotate(angle){ const c = Math.cos( angle ), s = Math.sin( angle ); const {x,y}=this; this.x = x * c - y * s; this.y = x * s + y * c; return this; } //向量除法 divide ( v ) { this.x /= v.x; this.y /= v.y; return this; } //...}好啦,关于变换后的图形选择我们就说到这。其实图形图形选择的方法是有很多的,下一章我再跟大家说一个图形选择的方法:图形选择-网格选择 ...

May 26, 2020 · 1 min · jiezi

图形选择图形模块化

说图形模块化之前,先回顾下我们之前画的图形,那是一个多边形,虽然没有闭合,但这不重要。 接下来,咱们就将这个图形封装为一个类对象 PolyPoly 对象是对路径的封装,那我们就可以从两方面来考虑:图形、样式。Poly 对象是对路径的封装,我们可以从两方面来考虑: 图形:路径可以绘制的所有图形,可以是一个图形,也可以是多个图形,只要都在一个路径集合里就行;样式:路径该有的所有样式了。接下来我们看一下Poly 对象的默认属性: const defAttr={ crtPath:crtLinePath, vertices:[], close:false, fill:false, stroke:false, shadow:false, fillStyle:'#000', strokeStyle:'#000', lineWidth:1, lineDash:[], lineDashOffset:0, lineCap:'butt', lineJoin:'miter', miterLimit:10, shadowColor:'rgba(0,0,0,0)', shadowBlur:0, shadowOffsetX:0, shadowOffsetY:0, position:new Vector2(0,0), rotation:0, scale:new Vector2(1,1),};详细解释一下这些属性:crtPath 是建立路径的方法,默认是给了一个绘制多边形的方法,此方法也可以被覆盖。 /*绘制多边形*/function crtLinePath(ctx){ const {vertices}=this; /*连点成线*/ ctx.beginPath(); ctx.moveTo(vertices[0].x,vertices[0].y); const len=vertices.length; for(let i=1;i<len;i++){ ctx.lineTo(vertices[i].x,vertices[i].y); }}vertices 是多边形的顶点集合。对于其它图形的相关属性,我没有写,以后需要了可以再去扩展,比如arc 的圆心位、半径、起始弧度、结束弧度等等。 绘图方法相关的相关的属性: close:否闭合路径fill:否以填充方式绘制图形strtoke:否以描边方式绘制图形shadow:否给图像添加投影样式相关的属性:fillStyle 填充样式、strokeStyle 描边样式…… 这些样式名称和canvas 里的样式是一样的,我就不消多说了。 变换相关属性:和canvas 里的变换是一样的,分别是位移、旋转、缩放。 Poly 的方法: draw(ctx) :绘图方法checkPointInPath(ctx,{x,y}):检测点位是否在路径中整体代码: import Vector2 from "./Vector2.js";/*多边形默认属性*/const defAttr={ crtPath:crtLinePath, vertices:[], close:false, fill:false, stroke:false, shadow:false, fillStyle:'#000', strokeStyle:'#000', lineWidth:1, lineDash:[], lineDashOffset:0, lineCap:'butt', lineJoin:'miter', miterLimit:10, shadowColor:'rgba(0,0,0,0)', shadowBlur:0, shadowOffsetX:0, shadowOffsetY:0, scale:new Vector2(1,1), position:new Vector2(0,0), rotation:0,};/*绘制多边形*/function crtLinePath(ctx){ const {vertices}=this; /*连点成线*/ ctx.beginPath(); ctx.moveTo(vertices[0].x,vertices[0].y); const len=vertices.length; for(let i=1;i<len;i++){ ctx.lineTo(vertices[i].x,vertices[i].y); }}/*Poly 多边形*/export default class Poly{ constructor(param={}){ Object.assign(this,defAttr,param); } draw(ctx){ const { shadow, shadowColor, shadowBlur, shadowOffsetX, shadowOffsetY, stroke, close, strokeStyle, lineWidth, lineCap, lineJoin, miterLimit,lineDash,lineDashOffset, fill, fillStyle, scale,position,rotation }=this; ctx.save(); /*投影*/ if(shadow){ ctx.shadowColor=shadowColor; ctx.shadowBlur=shadowBlur; ctx.shadowOffsetX=shadowOffsetX; ctx.shadowOffsetY=shadowOffsetY; } /*变换*/ ctx.translate(position.x,position.y); ctx.rotate(rotation); ctx.scale(scale.x,scale.y); /*建立路径*/ this.crtPath(ctx); /*描边*/ if(stroke){ ctx.strokeStyle=strokeStyle; ctx.lineWidth=lineWidth; ctx.lineCap=lineCap; ctx.lineJoin=lineJoin; ctx.miterLimit=miterLimit; ctx.lineDashOffset=lineDashOffset; ctx.setLineDash(lineDash); close&&ctx.closePath(); ctx.stroke(); } /*填充*/ if(fill){ ctx.fillStyle=fillStyle; ctx.fill(); } ctx.restore(); } checkPointInPath(ctx,{x,y}){ this.crtPath(ctx); const bool=ctx.isPointInPath(x,y); }}注意:Poly 对象中,我对其点位的定义使用了一个Vector2 对象。Vector2 是一个二维向量对象,它存储了基本的x、y 位置信息,封装了点位的运算方法,比如加、减、乘、除等。这里我先不对Vector2 对象做详细解释,我们先知道它表示一个{x,y} 点位即可,后面我们用到了它的哪个功能,再解释哪个功能。 ...

May 26, 2020 · 2 min · jiezi

图形选择svg

图形选择,是可视化交互中必然会遇到的,它在可视化方面的面试中出现概率是最高的。我在这里会从两个方向来说,分别是svg和canvas。至于普通DOM 的选择,我就不消多说了。因为svg 的选择是最简单的,所以咱们先说svg。svg 的选择方式和普通DOM 的选择方式是一样的。比如画一个三角形,然后为其正常添加鼠标划入划出事件: <svg version="1.1" baseProfile="full" width="700" height="700" xmlns="http://www.w3.org/2000/svg"> <polygon points="50 50, 450 50, 250 200"/></svg><script> const poly=document.querySelector('polygon'); poly.addEventListener('mouseover',function(){ console.log('over'); poly.setAttribute('fill','green'); }); poly.addEventListener('mouseout',function(){ console.log('out'); poly.setAttribute('fill','black'); })</script>在实际的工作中,我们可能会对复杂图形做出交互选择。就比如图片里有一座酷似大象的山,我们把鼠标划到山上的时候,要做出一些相应的提示。 这个时候,我们可以用Illustrator 来绘制山体轮廓。 在绘制完成后,ctrl+shift+S 另存为svg 文件即可。在另存为的时候,还会弹出svg 配置窗口: 在上面的窗口里面,我们可以点击“SVG 代码”按钮,查看相应的SVG代码: <?xml version="1.0" encoding="utf-8"?><!-- Generator: Adobe Illustrator 18.1.1, SVG Export Plug-In . SVG Version: 6.00 Build 0) --><svg version="1.1" id="scenery" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px" viewBox="0 0 503 395" enable-background="new 0 0 503 395" xml:space="preserve"><polygon id="mount" fill-rule="evenodd" clip-rule="evenodd" fill="none" stroke="#080102" stroke-miterlimit="10" points=" 211.7,260.8 234.6,236.6 241.2,190.3 245.6,165.2 255.7,145.4 309.5,95.2 358.4,74.9 381.7,115.9 388.8,130.4 385.7,137.9 398,174.5 406.4,176.2 433.3,205.3 443.8,236.6 468.9,263 288.8,264.8 294.5,239.2 276,243.6 265.9,262.6 "/></svg>从SVG代码中可以看出,图层的名称就是SVG 元素的id。有了SVG 文件之后,我们就需要将其导入HTML 页面里。因为这个文件是用Adobe 的Illustrator 软件生成的,所咱们就用Adobe 官方推荐的方式导入svg 文件: ...

May 26, 2020 · 1 min · jiezi