关于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

开发-HTML5-小游戏

Html5小游戏 在介绍小游戏之前,先看一个框架 Phaser。Phaser 框架是一个 快速、免费且开源的 HTML5 游戏开发框架,它提供了 Canvas 和 WebGL 两种渲染方式,兼容 PC 端与移动端浏览器。 一、Phaser 版本在启动 Phaser 游戏前,需要定义一个 Phaser.Game 对象实例,并同时将配置信息传至该实例:var game = Phaser.Game(config)。在 Phaser2 版本中,定义的是一个全局变量,并作为几乎全部的系统或者场景的入口。但升级至 Phaser3 版本之后,不再使用全局变量来存储游戏实例了。 Phaser2 版本之前// Phaser.Game(// width,// height,// renderer,// parent,// state,// transparent,// antialias,// physicsConfig// );Phaser.Game(800, 600, 'Phaser.AUTO', 'game');Phaser3 版本之后const config = {};Phaser.Game(config);二、游戏配置 configconst config = { type: 'Phaser.AUTO', title: "Starfall", width: 800, height: 600, parent: "game", backgroundColor: "#18216D", scene: [WelcomeScene, PrizeScene, GameScene, ScoreScene], transparent: false, antialias: true, loader: { baseURL: 'https://raw.githubusercontent.com/wqjiao/phaser-prize/master/', // 资源基本地址 crossOrigin: 'anonymous' } physics: { default: "arcade", arcade: { debug: false } },}1.type 游戏使用的渲染环境可选值: Phaser.AUTO、Phaser.WEBGL、Phaser.CANVAS推荐值: Phaser.AUTO 自动尝试使用 WebGL,如果浏览器或设备不支持,它将回退为 Canvas父节点: Phaser 生成的画布元素 canvas 将径直添加到文档中调用脚本的那个节点上,也可以在游戏配置中指定一个父容器 parent。2.title 游戏界面标题3.width、height Phaser 生成的画布尺寸,即游戏界面的分辨率默认:width -- 800、height -- 6004.parent 自定义 Phaser 生成画布(游戏界面)的父容器5.backgroundColor 游戏界面的背景颜色,Phaser3 版本配置项6.scene 游戏场景 ...

October 17, 2019 · 3 min · jiezi

刮刮卡效果实现

公司国庆搞了个集卡、抽奖小活动。抽奖需要刮刮卡的效果,感觉 css 是实现不了。看我使用 canvas 如何实现刮刮卡效果。废话不多说,线上效果 jsrun-测试地址 、 lilnong.top-测试地址 实现方案都有什么clearRect 这是我第一个找到的 API,作用是清除一个矩形区域内的内容。缺点是矩形globalCompositeOperation 基于上面的缺点,我发现了他 destination-out 是我需要的,作用是改变canvas图形的混合模式我们期望有什么功能底部内容自定义(我们将 canvas 浮在内容上即可)自定义蒙版颜色(canvas 绘制画布设置一下颜色即可 context.fillStyle = color;)自定义蒙版图片(使用drawImage来绘制画布)自定义笔触 clearRect为矩形arc配合globalCompositeOperation来实现圆形drawImage配合globalCompositeOperation来自定义笔触自动刮开&自动刮开轨道生成器刮开面积占比底部内容自定义两个方案的差距不大,根据个人喜好选择即可。 方案一 DOM 层级(拥有更多的能力)效果查看-传送门 <style type="text/css"> #app1{width:300px;height:100px;position: relative;border: 1px solid #f00;} #app1Content{width:300px;height:100px;position: absolute;left:0;top:0;z-index: 1} #app1Mark{width:300px;height:100px;position: absolute;left:0;top:0;z-index: 2} </style> <div id="app1"> <div id="app1Content" style="background:url(https://www.lilnong.top/static/img/ml-btn6.png) left center no-repeat">我是自定义内容</div> <canvas id="app1Mark"></canvas> </div>方案二 canvas 背景图(使用简单)效果查看-传送门 <style type="text/css"> #app2{width:300px;height:100px;position: relative;border: 1px solid #f00;} #app2Mark{width:300px;height:100px;position: absolute;left:0;top:0;z-index: 2}</style><div id="app2"> <canvas id="app2Mark" style="background:url(https://www.lilnong.top/static/img/ml-btn6.png) left center no-repeat"></canvas></div>蒙版颜色ctx.fillStyle = '#0cc';主要通过这个来设置填充样式。效果查看-传送门感兴趣可以去看看,我这里没用到就不细说了。传送门 - Canvas API中文网 色值填充 ...

October 9, 2019 · 2 min · jiezi

30行代码实现任意文字转粒子

w和h分别指窗口的宽和高 componentDidMount(){ var c=document.getElementById("canvas"); var ctx=c.getContext("2d"); ctx.fillStyle = "#fff"; ctx.fillRect(0, 0, 100,50); ctx.font="50px Arial "; ctx.textBaseline = "top"; ctx.fillStyle = "red"; ctx.fillText("HELLO",0,0); var imgData=ctx.getImageData(0,0,300,50).data; let point=[] , x=[] for(let i=0;i<imgData.length;i++){ for(let j=i*1200;j<(i+1)*1200;j+=4){ if(imgData[j]==255 && imgData[j+1]==0 && imgData[j+2]==0 ){ x.push(j%1200) } } if(x.length){ point.push(x) x=[] } } c.width=w c.height=h ctx.fillStyle='red' for(let i=0;i<point.length;i++){ for(let j=0;j<point[i].length;j++){ ctx.beginPath(); ctx.arc(point[i][j],i*10+100,2,0,2*Math.PI); ctx.fill() } } }

October 4, 2019 · 1 min · jiezi

Threejs入门之上海外滩

THREE.js入门之上海外滩最近入了three.js的坑,想用three.js做一些demo以便巩固自己最近所掌握的知识点,而且正好赶上国庆放假,随,有了这篇~预览地址: Three.js之上海外滩 欢迎start❤️~本篇虽是关于Three.js入门的文章, 但是太过入门的就不讲了。没意义,网上很多知识,本篇主要是把自己在写demo时候遇到的坑点给记录下来, 有什么不懂的直接去查阅文档或者网上搜,这里要提一下:Three.js的官方文档和例子对于开发者也是挺好的(有中文版!) 废话不多, 先看下效果吧: 代码比较多,就不一一讲解了,本篇主要分为以下几个部分: 初始化代码,创建场景,相机,渲染器,灯光,搭建不规则的地面几何体等搭建东方明珠搭建上海中心大厦搭建环球金融中心搭建金茂大厦随机算法搭建其他建筑物给所有建筑物进行贴图优化搭建黄浦江搭建360全景空间

October 4, 2019 · 1 min · jiezi

开源易扩展方便集成的在线绘图微服务架构图网络拓扑图流程图工具

一个基于typescript + canvas 实现的开源在线绘图的引擎Topology。采用引擎 + 图形库中间件的思路能够方便、快速的扩展、集成到前端项目。目前暂时实现了基本图形、流程图图形库,能够满足微服务架构图、网络拓扑图和流程图的绘制。后面计划陆续实现活动图/时序图/类图等UML图。 在线免费使用(因为操作方便问题,暂时没有适配移动端) 为什么重复造轮子笔者工作中遇到比较多的微服务架构、云资源运维、部署与运维可视化方面的需求开源、满足自己需求的不多typescript + 纯粹canvas架构的不多以中间件方式可定制满足不同场景的不多最重要的是,兴趣 + 不难特点开源可定制化简单易用,方便集成较好的性能,非常流畅方便的数据导入导出图片保存/预览typescript + canvas使用场景微服务架构图运维时部署结构拓扑图流程图后续会推出的: 活动图时序图类图等 架构设计主要由:层、节点、连线和箭头等组成。 层:这里的层,主要是为了提升性能的逻辑层;与ps里面的用户图层无关。 离屏层:包含所有绘图数据,是最稳定的图层。选中层:用户选中部分或全部节点/连线的高亮图层,并设置相关属性、缩放、和旋转等。动画层:主要用于演示动画。活动层:主要用于箭头鼠标交互事件,比如锚点和连线过程。 节点:是画布的主要组成部分,节点内部还可以包含图标或文字。连线和箭头:连线和箭头是关联在一起的。连线两端可以选择设置或不设置箭头。节点可以通过控制点进行整体缩放、旋转。 绘画与属性节点和连线各种有自身的绘画属性,同时还可以设置一个附加的自定义数据 快速集成使用es6使用示例:https://github.com/le5le-com/... typescript使用示例:https://github.com/le5le-com/... 安装# 安装绘图引擎npm install topology-core# 安装图形库 - 流程图npm install topology-flow-diagram# ...其他图形库创建基础画布// 1. 导入绘画引擎import { Topology } from 'topology-core';// 2. 创建画布// 其中,第一个参数'topo-canvas'表示canvas的dom元素id;// 第二个参数{}表示画布选项,这里表示全部使用默认值。具体选项请参考后面的api文档。var canvas = new Topology('topo-canvas', {});// 3. 渲染图形// 其中,第一个参数{}表示图形数据// 第二个参数true,表示打开一个新文件;否则在当前文件打开,覆盖已存在的图形数据canvas.render({}, true);常用画布方法// 获取画布数据const data = this.canvas.data();// 保存为图片blob// toImage函数参数:type, quality, callbackthis.canvas.toImage(null, null, blob => { // Do sth.});// 下载为图片// saveAsImage函数参数:filename, type, qualitythis.canvas.saveAsImage('canvas.png');// 编辑相关操作this.canvas.cut();this.canvas.copy();this.canvas.parse();this.canvas.undo();this.canvas.redo();引用第三方图形库// 使用第三方图形库// 1. 先导入注册函数import { registerNode } from 'topology-core/middles';// 2. 导入图形库图形及其相关元素import { flowData, flowDataAnchors, flowDataIconRect, flowDataTextRect, flowSubprocess, flowSubprocessIconRect, flowSubprocessTextRect, flowDb, flowDbIconRect, flowDbTextRect, flowDocument, flowDocumentAnchors, flowDocumentIconRect, flowDocumentTextRect, flowInternalStorage, flowInternalStorageIconRect, flowInternalStorageTextRect, flowExternStorage, flowExternStorageAnchors, flowExternStorageIconRect, flowExternStorageTextRect, flowQueue, flowQueueIconRect, flowQueueTextRect, flowManually, flowManuallyAnchors, flowManuallyIconRect, flowManuallyTextRect, flowDisplay, flowDisplayAnchors, flowDisplayIconRect, flowDisplayTextRect, flowParallel, flowParallelAnchors, flowComment, flowCommentAnchors} from 'topology-flow-diagram';// 3. 向引擎注册图形库图形及其相关元素registerNode('flowData', flowData, flowDataAnchors, flowDataIconRect, flowDataTextRect);registerNode('flowSubprocess', flowSubprocess, null, flowSubprocessIconRect, flowSubprocessTextRect);registerNode('flowDb', flowDb, null, flowDbIconRect, flowDbTextRect);registerNode('flowDocument', flowDocument, flowDocumentAnchors, flowDocumentIconRect, flowDocumentTextRect);// ...// 下面是简单的注册函数介绍,详情请参考api文档// registerNode: 注册一个自定义图形节点node.// name - node名称.// drawFn - node渲染函数。传入canvas ctx和node数据,自己决定如何绘画node// anchorsFn - 计算node的锚点,如果为null,表示使用缺省计算锚点方法// iconRectFn - 计算node的图标区域,如果为null,表示使用缺省计算图标区域方法// textRectFn - 计算node的文字区域,如果为null,表示使用缺省计算文字区域方法// force - 如果已经存在同名node,是否覆盖.export function registerNode( name: string, drawFn: (ctx: CanvasRenderingContext2D, node: Node) => void, anchorsFn?: (node: Node) => void, iconRectFn?: (node: Node) => void, textRectFn?: (node: Node) => void, force?: boolean);开发自己的图形库参考开发文档:https://www.yuque.com/alsmile... ...

September 9, 2019 · 1 min · jiezi

canvas入门圆绘制

arc语法:arc(x, y, radius, startAngle, endAngle, anticlockwise) 前面两个参数是x坐标,y坐标,第三个参数是半径,第四个参数是开始的弧度,第五个参数是结束的弧度,第六个参数是顺时针还是逆时针,默认是顺时针。 看下面代码,这样就能绘制一个圆了。 ctx.arc(100, 100, 50, 0, 2 * Math.PI)ctx.stroke()效果图: 这里要说明的一点是,不管顺时针还是逆时针,圆的弧度的位置是不变的,不会因为顺势转或者逆时针而改变,0.5pi的位置 ctx.arc(100, 100, 50, 0, 1.5 * Math.PI)ctx.stroke() ctx.arc(100, 100, 50, 0, 1.5 * Math.PI,true)ctx.stroke() 上面代码第一个是顺时针绘制的,3/4 个弧度,用逆时针的话就是 1/4 个弧度,它的意思是从 0 开始,顺时针到 1.5pi的位置

September 9, 2019 · 1 min · jiezi

canvas入门用canvas制作倒计时项目总结

项目演示 此项目是慕课网上的视频,这篇文章自己的学习总结。 点阵在canvas中绘制数字,可以使用点阵的方式。 下面是5的点阵布局,1代表要绘制,0代表不要绘制。这里数字采用的是10 * 7的网格系统,冒号是10 * 4的网格系统。 [ [1, 1, 1, 1, 1, 1, 1], [1, 1, 0, 0, 0, 0, 0], [1, 1, 0, 0, 0, 0, 0], [1, 1, 1, 1, 1, 1, 0], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [0, 0, 0, 0, 0, 1, 1], [1, 1, 0, 0, 0, 1, 1], [0, 1, 1, 1, 1, 1, 0]]有了这个这个网格的系统,我们就能将数字绘制出来了。 ...

September 9, 2019 · 3 min · jiezi

canvas入门线段绘制

moveTo、lineTo使用canvas绘制一条线段,默认已经拿到canvas的上下文context 绘制一条直线需要使用context提供的moveTo、lineTo方法 moveTo是线段的起点,lineTo是线段下一个点,最后使用stroke进行描边。如下代码 context.moveTo(100,100)context.lineTo(200,200)context.stroke()效果图: 如果一段线段分为多段,可以多次使用lineTo方法,将每个顶点都填进去就可以了 context.moveTo(100,100)context.lineTo(200,200)context.lineTo(200,400)context.stroke()效果图: strokeStyle、lineWidthcontent还提供了strokeStyle和lineWidth两个方法,这两个方法可以的作用是描边颜色,线宽。 ctx.moveTo(100, 100)ctx.lineTo(200, 200)ctx.lineTo(200, 400)ctx.strokeStyle = 'red'ctx.lineWidth = 5ctx.stroke()效果图 如果我想绘制两条不同的线段呢?使用moveTo就能开始新的一条线段绘制 ctx.moveTo(100, 100)ctx.lineTo(200, 200)ctx.lineTo(200, 400)ctx.strokeStyle = 'red'ctx.lineWidth = 5ctx.stroke()ctx.moveTo(300, 100)ctx.lineTo(400, 200)ctx.lineTo(400, 400)ctx.strokeStyle = 'blue'ctx.lineWidth = 10ctx.stroke()效果图: 看到效果图之后,发现和预想的不一样啊,为什么第一段代码没有生效? 这是因为canvas是基于状态绘制的,上面一段代码写了strokeStyle = 'red',然后stroke绘制;然后下面开始一段新的线段绘制写了strokeStyle = 'blue',在写这句话的时候,已经把上面的red改成了blue,使用最后一个stroke又进行绘制了。 也就是说不管你上面写了多少属性,只要最后一个stroke前对属性改动,都会被覆盖。 怎么解决这个问题呢? beginPath、closePathcontext提供了beginPath和closePath,这两个方法是告诉canvas我要开始绘制和结束绘制了,你不要给我随便改变属性 ctx.beginPath()ctx.moveTo(100, 100)ctx.lineTo(200, 200)ctx.lineTo(200, 400)ctx.strokeStyle = 'red'ctx.lineWidth = 5ctx.closePath()ctx.stroke()ctx.beginPath()ctx.moveTo(300, 100)ctx.lineTo(400, 200)ctx.lineTo(400, 400)ctx.strokeStyle = 'blue'ctx.lineWidth = 10ctx.closePath()ctx.stroke()效果图: 这里有个比较奇怪的是,最后我没有回到原点呀,为什么最后给我连起来了呢? 这是因为使用了closePath这个方法,使用closePath时,如果最后一个点没有回到起始位置,它会为你把首尾连接起来。 如果之后想连接起来,可以不用写closePath,其实,只要写了beginPath,canvas就已经知道了你要重新绘制新线段了。 ctx.beginPath()ctx.moveTo(100, 100)ctx.lineTo(200, 200)ctx.lineTo(200, 400)ctx.strokeStyle = 'red'ctx.lineWidth = 5ctx.stroke()ctx.beginPath()ctx.moveTo(300, 100)ctx.lineTo(400, 200)ctx.lineTo(400, 400)ctx.strokeStyle = 'blue'ctx.lineWidth = 10ctx.stroke()效果图: ...

September 8, 2019 · 1 min · jiezi

canvas

基本介绍什么是canvas<canvas>标签是h5新增的元素,可以用 js脚本语言(canvas API)绘制图形。例如,绘制图形,制作照片,创建动画,甚至进行实时视频处理和渲染。<canvas>只是画布,js是画笔,可以在画布上画画。canvas标签只有两个属性:width(默认300px),height(默认150px)<canvas id="canvas" width="300" height="150"></canvas>var canvas = document.getElementById('canvas');// canvas.width = 30;// canvas.height = 300;canvas解决了什么问题在互联网出现的早期,Web 只不过是静态文本和链接的集合。1993 年,有人提出了 img 标签,它可以用来嵌入图像。 由于互联网的发展越来越迅猛,Web 应用已经从 Web 文档发展到 Web 应用程序。但是图像一直是静态的,人们越来越希望在其网站和应用程序中使用动态媒体(如音频、视频和交互式动画等),于是 Flash 就出现了。 但是随着 Web 应用的发展,出现了 HTML5,在 HTML5 中,浏览器中的媒体元素大受青睐。包括出现新的 Audio 和 Video 标签,可以直接将音频和视频资源放在 Web 上,而不需要其他第三方。 其次就是为了解决只能在 Web 页面中显示静态图片的问题,出现了 Canvas 标签。它是一个绘图表面,包含一组丰富的 JavaScript API,这些 API 使你能够动态创建和操作图像及动画。img 对静态图形内容起到了哪些作用,Canvas 就可能对可编写脚本的动态内容起到哪些作用。 Canvas 是为了解决 Web 页面中只能显示静态图片这个问题而提出的,一个可以使用 JavaScript 等脚本语言向其中绘制图像的 HTML 标签。 渲染上下文canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。<canvas> 元素有一个叫做 getContext() 的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()只有一个参数,上下文的格式。 var canvas = document.getElementById('canvas');var ctx = canvas.getContext('2d');

September 7, 2019 · 1 min · jiezi

手摸手教你用canvas实现给图片添加平铺水印

最近项目中遇到一个需求,需要把一张图片加上平铺的水印 类似这样的效果首先想到的是用canvas完成这种功能,因为我之前也没有接触过canvas,所以做这个功能的时候,就是一步一步的摸索中学习,过程还是挺nice的,接下来跟我一步步来实现这个功能以及发现一些canvas的坑吧。 因为这个功能需要的都是一些canvas基础的api,也不涉及什么原理性的问题,这里我就直接贴js代码 var img = new Image();// 因为我项目中的业务是,要把淘宝的图片添加水印,所以这里就放一个淘宝商品的主图img.src = 'https://gd4.alicdn.com/imgextra/i3/155/O1CN01XKkJqL1D11wYZbeI2_!!155-0-lubanu.jpg_400x400.jpg';img.onload = () => { // 准备canvas环境 var canvas = document.getElementById("myCanvas"); var ctx = canvas.getContext("2d"); // 先把图片绘制到canvas上 ctx.drawImage(img, 0, 0, 200, 200); // 绘制水印到canvas上 for (let i = 0; i < 20; i++) { ctx.rotate((-45 * Math.PI) / 180); // 水印初始偏转角度 ctx.font = "20px microsoft yahei"; ctx.fillStyle = "rgba(0,0,0,0.5)"; ctx.fillText("mmmmmmmmmmmmmmmmmmmmmmm",-300,i * 25); ctx.rotate((45 * Math.PI) / 180); // 把水印偏转角度调整为原来的,不然他会一直转 }html ...

August 19, 2019 · 1 min · jiezi

Canvas动画

1:Canvas动画原理快速切换的静态画面。 2:基本步骤绘制 - 清空 - 绘制 - 清空 - 绘制 ...3:控制函数setTimeoutsetIntervalrequestAnimationFrame4:四种运动线性运动const canvas = document.getElementById('canvas'); /* 获得 2d 上下文对象 */ const ctx = canvas.getContext('2d'); let radialGradient; let distance = -50; const speed = 5; const draw = (axisX) => { /* 清空画布(或部分清空) */ ctx.clearRect(0, 0, 600, 600); radialGradient = ctx.createRadialGradient(distance, 300, 10, distance, 300, 50); radialGradient.addColorStop(0, "#FFFFFF"); radialGradient.addColorStop(1, "#EA7F26"); ctx.fillStyle = radialGradient; ctx.beginPath(); ctx.moveTo(distance, 300); ctx.arc(distance, 300, 50, 0, 2 * Math.PI, false); ctx.fill(); distance = distance + speed; if (distance > 650) distance = -50; requestAnimationFrame(draw); } requestAnimationFrame(draw);ctx.clearRect(0, 0, 600, 600);ctx.beginPath();ctx.moveTo(distance, 300);ctx.arc(distance, 300, 50, 0, 2 * Math.PI, false);ctx.fill();distance = distance + speed;从左到右匀速运动 ...

July 14, 2019 · 2 min · jiezi

丰富Canvas的应用

这篇文章主要介绍了多媒体样式,Canvas填充、文本以及图片等多媒体样式应用, 结合状态保存与恢复介绍了Canvas如何绘制多彩的内容。 1:添加样式主要有填充样式(fillStyle),轮廓的样式(strokeStyle),然后还有一个透明度(transparency)和线条样式(line style)。它们有一个共同的属性是设置之后都会成为默认属性。 填充样式填充(fillStyle),填充最基本的就是使用CSS的颜色值。 ctx.fillStyle = '#329FD9'; ctx.fillRect(0, 0, 400, 100);除此之外,还可以添加渐变对象(Gradient),渐变在css里面主要分为两种,一种是线性渐变,一种是径向渐变。在canvas里面也有这两种的渐变方式的添加:createLinearGradient 和 createRadialGradient。1.createLinearGradient //创建线性渐变对象,接收四个参数,可以理解为两个坐标点(0, 300)和(400, 0) const linearGradient = ctx.createLinearGradient(0, 300, 400, 0); //添加渐变颜色 addColorStop,接收两个参数,第一个数值范围在0-1之间,第二个是颜色值 linearGradient.addColorStop(0, "#8A469B"); linearGradient.addColorStop(0.5, "#FFFFFF"); linearGradient.addColorStop(1, "#EA7F26"); ctx.fillStyle = linearGradient; ctx.fillRect(0, 100, 400, 100);2.createRadialGradient 关于canvas的径向渐变,我们可以理解为,有两个小圆,作两个圆的切线,让两者有一个交点,如果两圆重合了,那么得到的就是一个向上的漏斗,我们可以理解为的范围就是无限大的,或者说在整个区域内都是我们可以见到的范围。如果两圆分开,看到的渐变范围就是一个锥形。两圆重合就是无限大的,两个圆心的连线就是渐变的范围。 /* 径向渐变 - 同心圆 *///创建径向渐变对象,接收六个参数,如上图,左边蓝色是一个圆,有一个圆心一个半径,200,250,10分别对应左边圆的圆心位置和圆半径;同样的右边绿色的圆对应的就是后面三个参数的圆的位置。 const radialGradient1 = ctx.createRadialGradient(200, 250, 10, 200, 250, 60); radialGradient1.addColorStop(0, "#8A469B"); radialGradient1.addColorStop(1, "#EA7F26"); ctx.fillStyle = radialGradient1; ctx.fillRect(0, 200, 400, 100); /* 径向渐变 - 非同心圆 */ const radialGradient2 = ctx.createRadialGradient(60, 350, 10, 350, 350, 60); radialGradient2.addColorStop(0, "#8A469B"); radialGradient2.addColorStop(1, "#EA7F26"); ctx.fillStyle = radialGradient2; ctx.fillRect(0, 300, 400, 100);图案(视频)对象(pattern)ctx.createPattern(image, "repeat | repeat-x | repeat-y | no-repeat"); ...

July 12, 2019 · 2 min · jiezi

Canvas-的简单介绍

1:基本概念Canvas是HTML5的一个新特性,canvas又叫做画板。顾名思义,我们可以在canvas上绘制我们需要的图形。最开始,canvas它是由苹果公司提出的,当时不叫canvas,叫widget,因为HTML并不存在一套二维的绘图API。Canvas本身是一个HTML元素,需要HTML元素的配合高度和宽度属性而定义出的一块可绘制区域,定义区域之后使用JavaScript的脚本绘制图像的HTML元素。 它可以基本的绘制图形,进一步的制作照片,绘制动画,更进一步可以处理和渲染视频。(应用场景) 2:浏览器兼容 3:<canvas>是一个元素canvas有两个属性,一个是宽度(width),一个是高度(height)。宽度和高度可以使用内联的属性,如下所示: <canvas width="300px" height="150px" id="canvas"> 您的浏览器不支持Canvas <img src="./backup.jpg" alt=""> </canvas>4:坐标系统 5:准备画布,Rending Context(渲染上下文 - 画笔)const canvas = document.getElementById('canvas');/* 获得 2d 上下文对象 */const ctx = canvas.getContext('2d');6:绘制画布原生绘制API 原生绘图API有三个: fillRect(x, y, width, height) 填充矩形strokeRect(x, y, width, height) 矩形边框clearRect(x, y, widh, height) 清空 ctx.fillRect(0, 0, 50, 50); ctx.strokeRect(50, 50, 100, 100); ctx.clearRect(10, 10, 30, 30);绘制线段 - Path ctx.beginPath();ctx.moveTo(0, 0);ctx.lineTo(300, 150);ctx.closePath();ctx.stroke();绘制三角形 - Path ctx.beginPath();ctx.moveTo(0, 0);ctx.lineTo(300, 0);ctx.lineTo(150, 150);ctx.closePath();ctx.fill();绘制圆/圆弧- Path arc(x, y, radius, startAngle, endAngle, anticlockwise) ...

July 11, 2019 · 1 min · jiezi

FireFox下Canvas使用图像合成绘制SVG的Bug

本文适合适合对canvas绘制、图形学、前端可视化感兴趣的读者阅读。楔子所有的事情都会有一个起因。最近产品上需要做一个这样的功能:给一些图形进行染色处理。想想这还不是顺手拈来的事情,早就研究过图形染色的技术。于是我把之前写好的两种算法发给了小伙伴,让他参照实现,第一种算法是操纵像素、第二种使用了图像合成:globalCompositeOperation。所有的事情都可能会有意外,写程序更是如此了。没多久,小伙伴说,第二种算法在firefox下不起作用。 探索原因听说有bug,心中一惊。我测试过了的,FireFox下面也测试过的。于是我打开火狐浏览器,启动示例,发现是好的,没有问题。 但是小伙伴集成到产品中就有问题。 差别在哪儿呢? 通过一起排查,最终发现我的示例代码和产品中代码的一个区别是:示例代码用的是png图片,而产品中用的是svg图片。难道是svg图片的问题,拿一个svg图片放到示例代码中,果然不对。结论已经明显:FireFox浏览器下,用Canvas下绘制绘制SVG图的时候,globalCompositeOperation的设置将不生效。下面是一段用于测试的代码,ctx.globalCompositeOperation = 'destination-out' 表示用源图像的形状去挖空目标图像。在其他浏览器中,以下代码中是生效的,又挖空的效果。但是在在FireFox 下不生效: <html><head> <script> function init() { var canvas = document.getElementById('c'); var ctx = canvas.getContext('2d'); var img = new Image(); img.onload = function () { ctx.drawImage(img, 0, 0, img.width * 2, img.height * 2); ctx.globalCompositeOperation = 'destination-out'; } img.src = 'diffuse.png'; var svg = new Image; svg.src = "./d.svg"; function drawPoint(pointX, pointY) { ctx.drawImage(svg, pointX - svg.width / 4, pointY - svg.height / 4, svg.width / 2, svg.height / 2); } canvas.addEventListener('click', function (e) { drawPoint(e.clientX, e.clientY); }, false); } </script></head><body onload="init();" style="background: red"> <div> <canvas id="c" width="1000" height="1000"></canvas> </div></body></html>>如何解决找到问题的原因了,解决方法其实简单。事情往往就是这样,很多时候,找到问题所在往往比解决问题要难。解决方案其实很简单 ...

July 10, 2019 · 1 min · jiezi

canvaswebpack构建flappybird小游戏

效果链接 github地址

July 9, 2019 · 1 min · jiezi

Canvas-进阶三ts-canvas-重写辨色小游戏

1. 背景之前写过一篇文章 ES6 手写一个“辨色”小游戏, 感觉好玩挺不错。岂料评论区大神频出,其中有人指出,打开控制台,输入以下代码: setInterval( ()=>document.querySelector('#special-block').click(),1)即可破解,分数蹭蹭上涨,这不就是bug吗?同时评论区 【爱编程的李先森】建议,让我用 canvas 来画会更简单,因此有了这篇文章。 话不多说,先上 Demo 和 项目源码 有趣的是,在我写完这篇文章之后,发现【爱编程的李先森】也写了一篇canvas手写辨色力小游戏,实现方式有所不同,可以对比下。 2. 实现本项目基于 typescript 和 canvas 实现 (1) 首先定义配置项一个canvas标签,游戏总时长time, 开始函数start, 结束函数end interface BaseOptions { time?: number; end?: Function; start?: Function; canvas?: HTMLCanvasElement;}定义类 ColorGame 实现的接口 ColorGameType, init()初始化方法,nextStep()下一步,reStart()重新开始方法 interface ColorGameType { init: Function; nextStep: Function; reStart: Function;}定义一个坐标对象,用于储存每个色块的起始点 interface Coordinate { x: number; y: number;}(2) 实现类 ColorGame定义好了需要用到的接口,再用类去实现它 class ColorGame implements ColorGameType { option: BaseOptions; step: number; // 步 score: number; // 得分 time: number; // 游戏总时间 blockWidth: number; // 盒子宽度 randomBlock: number; // 随机盒子索引 positionArray: Array<Coordinate>; // 存放色块的数组 constructor(userOption: BaseOptions) { // 默认设置 this.option = { time: 30, // 总时长 canvas: <HTMLCanvasElement>document.getElementById("canvas"), start: () => { document.getElementById("result").innerHTML = ""; document.getElementById("screen").style.display = "block"; }, end: (score: number) => { document.getElementById("screen").style.display = "none"; document.getElementById( "result" ).innerHTML = `<div class="result" style="width: 100%;"> <div class="block-inner" id="restart"> 您的得分是: ${score} <br/> 点击重新玩一次</div> </div>`; // @ts-ignore addEvent(document.getElementById("restart"), "click", () => { this.reStart(); }); } // 结束函数 }; this.init(userOption); // 初始化,合并用户配置 } init(userOption: BaseOptions) { } nextStep() {} // 重新开始其实也是重新init()一次 reStart() { this.init(this.option); }}(3)实现 init() 方法init() 方法实现参数初始化,执行 start() 方法,并在最后执行 nextStep() 方法,并监听 canvas的 mousedown 和 touchstart 事件。 ...

July 8, 2019 · 5 min · jiezi

Canvas-进阶二写一个生成带logo的二维码npm插件

背景最近接触到的需求,前端生成一个带企业logo的二维码,并支持点击下载它。 实现在前面的文章有讲到如何用 canvas 画二维码,在此基础上再画一个公司logo,并提供下载的方法供调用,再封装成 npm 插件 模块名称: qrcode-with-logos github地址:https://github.com/zxpsuper/qrcode-with-logos npm地址:https://www.npmjs.com/package/qrcode-with-logos 核心代码将整个封装成一个 QrCodeWithLogo类,并提供三个方法: interface IQrCodeWithLogo { toCanvas(): Promise<any>; toImage(): Promise<any>; downloadImage(name: string): void;}class QrCodeWithLogo implements IQrCodeWithLogo { option: BaseOptions; constructor(option: BaseOptions) { this.option = option; return this; } toCanvas = () => { return toCanvas.call(this, this.option); }; toImage = () => { return toImage.call(this, this.option); }; downloadImage = (name: string) => { saveImage(this.option.image, name); };}1. toCanvas()此方法用到了库qrcode的toCanvas方法 export const toCanvas = (options: BaseOptions) => { return renderQrCode(options) .then(() => options) .then(drawLogo);};这里先用qrcode库画出二维码的canvas ...

July 1, 2019 · 4 min · jiezi

H5-canvas生成图片并上传文件转成PDF下载canvas文字排版

H5 canvas生成图片并上传文件转成PDF下载最近遇到一个业务需求,在小程序端定制预览功能,并在预览的图片中使用指定的外部字体。将预览的图片上传OSS,后端生成PDF,在管理系统中下载。但是…………,经过实践发现,小程序尽管做了分包处理,依旧不能在本地存放字体包,把字体放OSS上返回,但是出现跨域,尽管配置了允许跨域,依旧不行。而且!!!小程序的canvas API没法设置字体,没有h5中canvas中的context.font = '字体名称'方法。最终决定曲线救国,放弃小程序端的预览生成canvas功能,将canvas引入字体,生成图片等操作放在管理系统中,采用原生canvas来实现。 技术要点canvas文字排版canvas设置指定背景颜色canvas引入外部字体canvas绘制文字图片将canvas生成的base64图片转成file上传(这里根据后端协商,此处后端要求)将图片生成PDF,并点击批量下载实现步骤canvas文字排版在一般HTML容器中,如果要实现文字的排版很容易。比如:实现文本超出自动换行,默认文本超出容器宽度就会自动换行,也可以使用word-wrap:break-word实现强制换行。实现文字竖排,有几种方式: 给文本容器设置writing-mode样式:(存在兼容性问题)writing-mode:vertical-rl;//垂直方向自右而左的书写方式。即 top-bottom-right-left或者writing-mode:vertical-lr;//垂直方向内内容从上到下,水平方向从左到右具体效果如图: 但是这个对于浏览器也存在一定兼容性问题,使用的时候需要注意。 使用宽度控制换行:(不存在兼容性,推荐方式)设置每行的宽度为一个字大小,利用文本超出默认换行的特性,或者设置超出强制换行,实现文本竖排。 利用br标签实现或者每个文字存放一个标签实现换行:(很死板的写法,比较low,不推荐)给每个文字后添加br标签,或者每个文字放一个标签,这样写灵活性不高,非常不推荐! 在canvas中实现文字排版实现文字横排canvas中,如果文本超出canvas大小,并不会自动换行,会直接在超出的后面继续绘制成一排。canvas中也没有直接可以设置换行的api,那该怎么实现换行呢?可以通过js控制,通过计算当前绘制文字的x坐标,如果x坐标大于canvas的宽度,将x坐标赋值为0(绘制的起始点x坐标),y坐标累加一个文字的高度,从而实现文本换行。 实现文字竖排竖排的逻辑和横排是一样的。文字竖排只是y坐标累加,趟超过canvas的高度时,将y坐标赋值为0(绘制的起始点y坐标),x坐标累加一个文字的高度,从而实现竖排且文本换行。 部分代码片段 /** * canvas绘制文字 * @param {CanvasRenderingContext2D对象} context * @param {绘制内容} text * @param {起始点x坐标制} x * @param {起始点y坐标制} y */ drawTextVertical(context, text, x, y) { let startX = x, startY = y; //记录开始的位置,用于文字换行赋值 let spaceCount = 0; let arrText = text.trim().split(''); let formatText = text.replace(/\//g, '').split(''); // 去掉单斜杠 let align = context.textAlign; let baseline = context.textBaseline; context.textAlign = 'center'; context.textBaseline = 'middle'; context.font = 'Pacifico' // 开始逐字绘制 arrText.forEach(function (letter, index) { // 确定下一个字符的纵坐标位置 // 是否需要旋转判断 let code = letter.charCodeAt(0); // 计算文字间距 let letterWidth = 22 * 2.3; if (code <= 256) { context.translate(x, y); // 英文字符,旋转90° context.rotate(90 * Math.PI / 180); context.translate(-x, -y); } if (code !== 47) context.fillText(letter, x, y); // 旋转坐标系还原成初始态 context.setTransform(1, 0, 0, 1, 0, 0); // 单斜杠换行或者长度超过8 此处要过滤在第9字是换行的符号的情况 if ((code === 47 && !spaceCount) || (!spaceCount && index && index % 7 === 0)) { // 单斜杠/ 代表换行 charCode=47 spaceCount += 1; y = startY; x = index ? (startX + letterWidth) : x; startX = x; } else if (code !== 47) { // 如果是空格 减少字间距 if (code !== 32) { y = y + letterWidth; } else { y = y + letterWidth / 2 } } }); // 水平垂直对齐方式还原 context.textAlign = align; context.textBaseline = baseline; } ...

June 30, 2019 · 3 min · jiezi

带着canvas去流浪10文字烟花

示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 [TOC] 一. 文字烟花文字烟花的小控件是下面这样的效果,你或许在很多个人博客中见过: 这一节我们就来讲述一下这个小动画的实现方法。 二. 动画原理首先动画的主框架仍然是我们反复使用的逐帧动画框架,烟花生成以后的部分也不难理解,我们之前已经对物理碰撞进行过仿真,这里实际上就是模拟了带有初速度的自由落体。所以这个小动画里唯一的难点,就是如何根据文字生成烟花,只要做到这一步,其他的部分都比较容易实现。 2.1 像素操作这里就要用到canvas像素操作的API——context.getImageData( )了,它可以将画布上指定矩形区域以像素点的形式返回回来,像素数据挂载在返回对象的data属性上,它是一个一维的Uint8ClampedArray定型数组,每个值只能取0-255之间的整数,如果赋值超过这个范围,则自动修改为[0,255]而不会报错。这个一维数组是矩形区域的像素点数据逐行拼接在一起的,每4个点代表一个像素点的RGBA的颜色数据,最后一个通道是透明度数据,例如一个红色的像素点的数据就是[...,255,0,0,0....]。比如你截取了一个长为200像素高为10像素的矩形区域的数据点,那么就会得到一个200*10*4=8000个数据点的数组。 这是canvas非常重要的一个API,它的应用场景非常多,例如结合WebRTC输入的流数据来做视频弹幕,或者使用算法对像素数据进行加工实现各种各样的图片滤镜等,还可以使用离屏canvas来进行性能提升,具体的应用就留给你自己去探索喽。 2.2 烟花生成算法获取到像素数据后,我们就可以对其进行分析,分析算法如下: 将要获取像素的部分分成大小适中的网格,网格太小则渲染压力大,网格太大动画效果不好。遍历每一个网格,取出小方块区域内所有像素点,也可以一次性读取整个区域的像素点然后按小区域来取用,然后统计其中dirty的像素点数量(判断其对应的颜色值是否都为255,如果不是则判定为dirty),如果区域内脏点的比例超过一定阈值(示例中为60%)则判定该区域需要被烟花点替换。在需要生成烟花的区域以随机大小和颜色生成一个小球,并根据其位置指定水平初速度的方向,小球均受到竖直向下的重力影响。在帧动画中更新小球状态。2.3 计时器最后,我们还需要一个新的timer对象,之前我们接触到的精灵动画大都是连续的,每一帧都需要进行状态更新,而本节中时间文字的更新是离散的,一秒钟才更新一次,烟花由于有动画过程,也不太适合每秒都生成。所以我们需要在timer中实现一个内部计时器,每1秒更新一次渲染文字,每2秒触发一次。如果对时间精度要求较高,可以记录时间戳进行比对,如果精度要求不高,可以在update方法中递增直接对更新周期进行取模即可。 Timer类的定义如下: //计时器类class Timer{ constructor(){ this.lastTime = Date.now(); //初始化的时候记录一次时间 this.label = new Date(this.lastTime).Format('hh:mm:ss');//Format是自定义的格式化方法 this.step = 0;//标记是否刷新时间文字 this.shouldAnim = 0;//标记是否需要生成新的烟花 } update(){ this.step = (this.step + 1) % 60;//时间文字每60帧刷新一次 this.shouldAnim = (this.shouldAnim + 1) % 120;//烟花每120帧生成一次 if (!this.step) { this.lastTime = Date.now(); this.label = new Date(this.lastTime).Format('hh:mm:ss'); } } paint(ctx){ context.fillStyle = "#353535"; ctx.fillText(this.label, 200, 100); }}主框架部分的代码已经讲过非常多次,本文不再赘述。 ...

June 29, 2019 · 1 min · jiezi

带着canvas去流浪8碰撞

示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 [TOC] 经过前面章节相对枯燥的练习,相信你已经能够上手canvas的原生API了,那么从这一节开始,我们就开始接触点好玩的东西——动画。 一. canvas的能力如果你以为canvas只能绘制图表那真的就图样图森破了,且不谈webgl的绘图上下文,单就2d空间的画笔就可以做很多有意思的事情,比如实现一些酷炫的动画效果,比如做一些物理仿真,图片滤镜,直播弹幕,甚至做游戏开发等等,画面的变化大多依赖于canvas提供的像素操作能力,而动效几乎都是靠canvas在短时间内逐帧绘制而形成的,和电影的原理是一样的。 我们知道javascript中和时间控制有关的函数setTimeout( ) 以及setInterval( )最终执行时的时间点并不准确,因为在事件队列中会被其他异步任务影响甚至直接阻塞,那么在不断重复的绘制中,就有可能会出现卡顿或者忽快忽慢;另一方面,假设我们使用的电脑显示屏刷新率为60帧/秒,也就是大约16.7ms重绘一次,那么即时我们在16.7ms时间内执行了很多次计算和绘制命令,实际上最终呈现出的也只是最后一次结果,就好比对一段很密集的数据进行了隔点采样,轻则浪费性能,重则会在画面呈现时出现跳帧。为了配合显示器刷新,我们可以使用另一个方法——requestAnimationFrame(fn),这是javascript中专门用来绘制逐帧动画的,它会配合显示器的刷新频率进行必要的图像更新,节省不必要的性能浪费。 二. 动画框架在canvas上实现基本的动画,可以遵循一个基本的编程框架: function step(){ /** *在每一帧中要执行的逻辑 *...... */ requestAnimationFrame(step);}step();//启动执行你没看错,这就是canvas动画最核心的一段代码,step()函数会在每个绘图周期内重复执行。那么每一帧中需要做哪些工作呢? 我们将canvas想象成一个舞台stage,每一个需要绘制在画布上的元素被称为精灵,无论它们拥有怎样的属性,它们都具备update( )和paint( )两个基本方法,前者用于在每一帧中计算更新精灵的参数属性,后者用于将这个精灵对象绘制在画布上。那么step函数在每一帧中所执行的逻辑就变得明朗了,对画布进行必要的擦除,接着更新每一个精灵的状态(可能是位置,颜色等等),然后将其绘制在画布上。 比如现在要在画布上表现一段太阳东升西落得动画,对应的伪代码就是下面这个样子的: let stage = [];stage.push(background, tree, cloud, sun);function step(){ cleanStage();//对画布进行必要擦除 background.update();//更新土地的属性 tree.update();//更新树的属性 cloud.update();//更新云的属性 sun.update();//更新太阳的属性(属性中必然包含着太阳的位置数据) background.paint();//绘制土地 tree.paint();//绘制树 cloud.paint();//绘制云 sun.paint();//绘制太阳 requestAnimationFrame(step);}如果你理解了上面的过程,那么接下来我们对上述代码进行一些抽象和改写: //建立舞台及添加元素的代码let stage = [];stage.push(background, tree, cloud, sun....);//逐帧动画代码function step(){ cleanStage(); stage.map(sprite=>{ sprite,update(); sprite.paint(ctx); }); requestAnimationFrame(step);}每一个精灵对象都需要实现自己的update( )和 paint( )方法来描述自己的参数如何变化,以及如何在每一帧中被绘制,被添加进stage数组的都是精灵的实例,一般会将canvas绘图上下文传入paint(context)方法,这样就可以将精灵绘制在指定的画布上。上面的范式只是一个简陋的核心模型,但是已经足够说明canvas动画的本质。 三. 在canvas中模拟碰撞现在我们就通过一个碰撞仿真的例子来学习canvas动画以及基本的物理仿真分析,示例虽然精简,但包含了canvas动效最核心的精灵动画和碰撞检测主题。为了方便二维向量操作并隐藏各种数学计算的细节,我们直接使用一个已经定义好的Vector2类,其中封装了很多向量的基本操作,都是初高中数学的知识,如果你已经记不太清楚,可以找一些有关的资料复习一下。 3.1定义小球的属性将每一个小球视为一个精灵,我们需要为它增加一些基本属性以便在每一帧中能够将其绘制出来。通过位置,半径和颜色信息,就能够绘制出小球;通过速度信息,就可以计算小球的位置变化,以便在绘制下一帧时使用。 class Ball{ constructor(x,y,id){ this.pos = new Vector2(x,y);//初始化小球的位置 this.id = id; this.color = '';//绘制的颜色 this.r = 20;//小球半径,为方便演示,此处使用给定值 this.velocity = null;//小球的速度 }}3.2 生成新的小球为了增加演示效果,我们使用一个定时函数来随机生成小球,每次生成时为其赋予一个颜色,并给定一个随机的初始速度。 ...

June 24, 2019 · 2 min · jiezi

移动端图片裁剪器安装即用

h5-cropper移动端图片裁剪器,基于 cropperjs 制作的简单裁剪器 项目地址https://github.com/sayll/h5-cropper 案例演示HTML部分: <!DOCTYPE html><html lang="cn"><head> <meta charset="utf-8"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, minimum-scale=1, user-scalable=no" /> <title>裁剪案例</title></head><script src="./h5-cropper.js"></script><body style="font-size: 14px"> <img id="image" src="http://placekitten.com/g/720/400" width="100%" alt=""> <div> <button style="width: 100%; font-size: 18px;" id="button">开始裁剪</button> </div></body><script src="./index.js"></script></html>JS部分: const img = document.getElementById('image')document.getElementById('button').onclick = function () { var cropper = new window.H5Cropper(img.src, function (base64) { img.src = base64 })}演示案例:https://sayll.github.io/h5-cropper/index.html 手机模式查看,效果更佳。使用方式直接下载使用通过下载,直接引用dist/index.js,通过全局的window对象访问。 const cropper = new window.H5Cropper('http://placekitten.com/g/300/200', function (base64) { // to do something console.log(base64)})npm 下载使用下载方式: npm i -S h5-cropper使用方式: import H5Cropper from 'h5-cropper'const cropper = new H5Cropper('http://placekitten.com/g/300/200', function (base64) { // to do something console.log(base64)})接口定义调用方式: ...

June 20, 2019 · 1 min · jiezi

带着canvas去流浪7绘制水球图

示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 [TOC] 一. 任务说明使用原生canvasAPI绘制水球图,这将是一个非常有意思的挑战任务。水球图是一种常见的加载动画,属于扩展图形,在echarts中使用时需要下载扩展库(同为扩展库的还包括文字云插件和地图插件,项目地址为https://github.com/ecomfe/echarts-liquidfill)。 二. 重点提示水球图的绘制有以下几个难点: 水波的绘制水波的绘制实际上是运用简谐振动公式来模拟的,也就是x = A*(wt +),其中振幅A决定了水波的波纹高低,角频率w决定了水波的快慢,相位决定了初始位移差,再加上一些y轴方向的位移偏差和颜色的差异,就可以模拟出不同的水波,接着只需要在帧动画中不断改变并重绘曲线,就可以模拟出水波效果了。 球形剪裁区域水波的范围是不能流出球形的外轮廓的,此处的做法是在绘制水波之前,先使用context.clip( )方法将水波的可见绘图区域控制在水球之内即可,如果还有水球外的图形需要绘制,记得在每一帧绘制完水波后调用context.restore( )取消掉之前的剪裁。 文字的绘制如果只是绘制漂浮于水球图之上的文字,是比较容易实现的,但是如果想要实现一些细节更丰富的效果,并不那么容易。我们期望实现的效果是,当文字未被水波浸入时,显示水纹的蓝色,而被水浸润的部分显示为白色,这样看起来更加生动。但是绘制起来却并不容易,如果将文字绘制成蓝色,那么被水淹没的部分就会消失在水纹中,如果绘制成白色,那么水纹高度较小时,会完全看不到文字。那么这样的渲染文字要如何实现呢? 三. 示例代码let options = { value:0, a:20,//振幅 pos:[300,300],//水球图位置 r:160,//水球图半径 color:['#2E5199','#1567c8','#1593E7','#42B8F9']//水纹颜色};start(options);/** * 绘制水球图 */function start(options) { //移动绘图坐标至水球图左边界点 context.translate(options.pos[0],options.pos[1]); context.font = 'bold 60px Arial'; context.textAlign='center'; context.textBaseLine = 'baseline'; //计算水球图绘图数据 createParams(options); //开启帧动画 requestAnimationFrame(startAnim);}//生成水波动画参数,位置坐标公式为 y = A * (wt + )function createParams(options) { options.w = [];//存储水波的角速度 options.theta = [];//存储每条水波的位移 for(let i = 0; i < 4; i++){ options.w.push(Math.PI /(100 + 20*Math.random())); options.theta.push(20*Math.random()); }}//绘制水波线function drawWaterLines(options) { let offset; let A = options.a;//正弦曲线振幅 let y,x,w,theta; let r = options.r; //遍历每一条水纹理 for(let line = 0; line < 4; line++){ context.save(); //每次绘制时水波的偏移距离 theta = Math.random(); offset = r + A / 2 - (r*19/8 + A) * (options.value / 100 ) + line * r/12; //获取正弦曲线计算参数 w = options.w[line]; theta = options.theta[line]; context.fillStyle = options.color[line]; context.moveTo(0,0); context.beginPath(); //以0.1为步长绘制正弦曲线 for(x = 0; x <= 2*r; x+=0.1){ y = A * Math.sin(w * x + theta) + offset; //绘制点 context.lineTo(x,y); } //绘制为超出水球范围的封闭图形 context.lineTo(x,r); context.lineTo(x - 2 * r,r); context.lineTo(0, A * Math.sin(theta) - options.height); context.closePath(); //填充封闭图形得到一条水波 context.fill(); //截取水波范围,绘制文字(此处将在后文解释) context.clip(); context.fillStyle = 'white'; context.fillText(parseInt(options.value,10) + '%',options.r + 10,10); context.restore(); }}//绘制最底层文字function drawText1(options) { context.fillStyle = options.color[0]; context.fillText(parseInt(options.value,10) + '%',options.r + 10,10);}//帧动画循环function startAnim() { //用位移变化模拟水波 options.theta = options.theta.map(item=>item-0.03); //用百分比进度计算水波的高度 options.value += options.value > 100 ? 0:0.1; context.save(); resetClip(options);//剪切绘图区 drawText1(options);//绘制蓝色文字 drawWaterLines(options);//绘制水波线 context.restore(); requestAnimationFrame(startAnim);}/**设置水球范围为剪裁区域*(本例中并没有水球以外的部分需要绘制,实际上这里不需要加入帧动画循环中,只需要在开头设置一次即可。)*/function resetClip(options) { let r = options.r; context.strokeStyle = '#2E5199'; context.fillStyle = 'white'; context.lineWidth = 10; context.beginPath(); context.arc(r, 0, r + 10, 0, 2*Math.PI, false); context.closePath(); context.fill(); context.stroke(); context.beginPath(); context.arc(r, 0, r, 0, 2*Math.PI, true); context.clip();}浏览器中可查看效果: ...

June 19, 2019 · 2 min · jiezi

源码阅读基于Canvas贝塞尔曲线算法的平滑手写板

signature_pad一个基于Canvas的平滑手写画板工具介绍实现手写有多种方式。 一种比较容易做出的是对鼠标移动轨迹画点,再将两点之间以直线相连,最后再进行平滑处理,这种方案不需要什么算法支持,但同样,它面对一个性能和美观的抉择,打的点多,密集,性能相对较低,但更加美观,视觉上更平滑; 此处用的另一种方案,画贝塞尔曲线。 由于canvas没有默认的画出贝塞尔曲线方法,因此曲线是通过不断画出一个个点形成的,那么问题来了,这些点谁来定? 这里使用了贝塞尔曲线的一系列算法,包括求控制点,求长度,计算当前点的大小,最后用canvas画出每一个确定位置的点。 参数及配置介绍提供的可配置参数如下 export interface IOptions { // 点的大小(不是线条) dotSize?: number | (() => number); // 最粗的线条宽度 minWidth?: number; // 最细的线条宽度 maxWidth?: number; // 最小间隔距离(这个距离用贝塞尔曲线填充) minDistance?: number; // 背景色 backgroundColor?: string; // 笔颜色 penColor?: string; // 节流的间隔 throttle?: number; // 当前画笔速度的计算率,默认0.7,意思就是 当前速度=当前实际速度*0.7+上一次速度*0.3 velocityFilterWeight?: number; // 初始回调 onBegin?: (event: MouseEvent | Touch) => void; // 结束回调 onEnd?: (event: MouseEvent | Touch) => void;}这里要注意的是并没有线条粗细这个选项,因为这里面的粗细不等线条都是通过一个个大小不同的点构造而成; throttle这个配置可以参考loadsh或者underscore的_.throttle,功能一致,就是为了提高性能。 ...

June 18, 2019 · 2 min · jiezi

通过json用canvas生成分享海报支持微信小程序和web

需求在项目里写过几个canvas生成分享海报页面后,觉得这是个重复且冗余的工作.于是就想有没有能通过类似json直接生成海报的库. 然后就在github找到到两个项目: wxa-plugin-canvas,不太喜欢配置文件的写法.就没多去了解mp_canvas_drawer,使用方式就比较符合直觉,不过可惜功能有点少.然后就想着能不能自己再造个轮子.于是就有了这个项目 json2canvas,你可以简单的理解为是mp_canvas_drawer的增强版吧. json2canvas canvas绘制海报,写个json就够了.项目的canvas绘制是基于cax实现的.所以天然的带来一个好处,json2canvas同时支持小程序和web功能支持缩放. 如果设计稿是750,而画布只有375时.你不需要任何换算,只需要将scale设置为0.5即可.支持图片圆角支持圆型,矩形,矩形圆角(背景色支持线性渐变)长文本自动换行(感谢 coolzjy@v2ex 提供的正则 https://regexr.com/4f12l ,优化了换行的计算方式(不会粗暴的折断单词))支持分组(cax里很好用的一个功能)同时支持小程序和web示例demo-web 界面左边的json,可以进行编辑,直接看效果哟~小程序demogit clone https://github.com/willnewii/json2canvas.git微信开发者工具导入项目 example/weapp/小程序安装npm i json2canvas微信开发者工具->工具->构建npm在需要使用的界面引入Component { "usingComponents": { "json2canvas":"/miniprogram_npm/json2canvas/index" }}举个例子想要生成一个这样的海报,需要怎么做?(红框是图片元素,蓝框是文字元素,其余的是一张背景图。) 简单,一个json搞定.具体支持的元素和参数,请查看项目readme { "width": 750, "height": 1334, "scale": 0.5, "children": [ { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/bg_concerts_1.jpg", "width": 750, "height": 1334 }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/wxapp_code.jpg", "width": 100, "x": 48, "y": 44, "isCircular": true, }, { "type": "circle", "r": 50, "lineWidth": 5, "strokeStyle": "#CCCCCC", "x": 48, "y": 44, }, { "type": "text", "text": "歌词本", "font": "30px Arial", "color": "#FFFFFF", "x": 168, "y": 75, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/medal_concerts_1.png", "width": 300, "x": "center", "y": 361 }, { "type": "text", "text": "一生活一场 五月天", "font": "38px Arial", "color": "#FFFFFF", "x": "center", "y": 838, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "text", "text": "北京6场,郑州2场,登船,上班,听到你想听的歌了吗?", "font": "24px Arial", "color": "#FFFFFF", "x": "center", "y": 888, "shadow": { "color": "#000", "offsetX": 2, "offsetY": 2, "blur": 2 } }, { "type": "rect", "width": 750, "height": 193, "fillStyle": "#FFFFFF", "x": 0, "y": "bottom" }, { "type": "image", "url": "http://res.mayday5.me/wxapp/wxavatar/tmp/wxapp_code.jpg", "width": 117, "height": 117, "x": 47, "y": 1180 }, { "type": "text", "text": "长按识别小程序二维码", "font": "26px Arial", "color": "#858687", "x": 192, "y": 1202 }, { "type": "text", "text": "加入五月天 永远不会太迟", "font": "18px Arial", "color": "#A4A5A6", "x": 192, "y": 1249 }] }问题反馈有什么问题可以直接提issue ...

June 18, 2019 · 2 min · jiezi

带着canvas去流浪6绘制雷达图

示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 [TOC] 一. 任务说明使用原生canvasAPI绘制雷达图。(截图以及数据来自于百度Echarts官方示例库【查看示例链接】)。 二. 重点提示雷达图绘制的看起来并不复杂,无非就是一些路径点的连线,其中的难点都在于一些细节。 坐标转换为了避免在绘制过程中不断根据夹角来计算某个数据点的坐标,我们可以让坐标系先移动到绘图中心,然后在绘制过程中逐步旋转并使用context.lineTo(x,y)来连线即可,这样做的好处是很明显的。比如在绘制背景六边形的时候,每次旋转后,路径点压根就不需要移动,直接在循环中每次都调用context.lineTo( )方法连线至同一个数据点即可,看起来位移没有变,实际上随着坐标系的旋转,连线绕过的是多边形的轨迹。 文字的对齐为了让文字保持正常的方向,我们需要将坐标系的旋转恢复到初始状态再进行绘制。绘制的过程中可以根据绘制点和中心连线相对于x轴的角度来动态修改其绘制时的相对点(left,right,center),否则就会出现下图的结果,也就是文字区域的中心到图形中心的距离的确是一致的,但这并不是我们想要的效果。 canvas坐标系请时刻记得canvas坐标系的初始方向是x轴向右,y轴向下,和普通笛卡尔坐标系是不一样的,尤其是在旋转角度和坐标计算的时候,很容易出现和预期角度不相符的结果。 三. 示例代码//options数据来自于百度Echarts官方示例库start(options);/** * 绘制图表 */function start(options) { drawBg(options); drawData(options);//绘制雷达图 drawText(options);//绘制文字}function drawBg(options) { let length = options.radar.indicator.length; let angleStep = -2 * Math.PI / length; context.strokeStyle="#b2b2b2"; context.lineWidth = 1; //调整坐标系 //移动中心点 context.translate(500,300); //将x轴旋转至竖直向上 context.rotate(-90 * 2 * Math.PI / 360); //每次以不同旋转半径绘制多个由大到小的图形 for(let r = 200; r > 0 ; r -=40){ //移动至第一个绘图点 context.save(); context.beginPath(); context.moveTo(r,0); //转动坐标系绘制所有点 for(let i = 0; i < length; i++){ context.rotate(angleStep); context.lineTo(r,0); } context.closePath(); context.stroke(); //明暗色替换填充,此处从大到小切换颜色覆盖式绘制即可 context.fillStyle = Math.round(r / 40) % 2 ? 'white':'#eaeaea'; context.fill(); context.restore(); }}/** * 绘制数据 */function drawData(options) { //解构赋值拿到数据关键点 let {radar:{indicator:indicators},series:[{data:data}]} = options; let colors = ['#c43e3a','#364c5a']; let length = indicators.length; let angleStep = -2 * Math.PI / length; for(let i = 0; i < data.length; i++){ context.save(); context.beginPath(); context.moveTo(200 * data[i].value[0] / indicators[0].max,0); //遍历每组数据 for(let j = 1; j < data[i].value.length; j++){ context.rotate(angleStep); context.lineTo(200 * data[i].value[j] / indicators[j].max,0); } context.restore(); context.lineTo(200 * data[i].value[0] / indicators[0].max,0); context.strokeStyle = colors[i]; context.lineWidth = 2; context.stroke(); } context.restore();}//绘制文字function drawText(options) { let {radar:{indicator:indicators}} = options; let length = indicators.length; let angleStep = 2 * Math.PI / length; let r = 220; context.fillStyle = 'black'; context.font = "14px bold 黑体"; context.textAlign = 'center'; context.rotate(90 * Math.PI * 2 / 360); for(let i = 0; i < indicators.length; i++){ let curAngle = -90*2*Math.PI/360 - angleStep*i; //根据方向调整文字的对齐点 let cos = Math.cos(curAngle); if (Math.abs(cos) < 10e-4) { context.textAlign = 'center'; }else if(cos > 0){ context.textAlign = 'left'; }else{ context.textAlign = 'right'; } console.log(indicators[i].name, Math.cos(curAngle)) context.fillText(indicators[i].name, r * Math.cos(curAngle), r * Math.sin(curAngle)); }}浏览器中可查看效果: ...

June 17, 2019 · 2 min · jiezi

使用canvas一步步实现图片打码功能

使用canvas一步步实现图片打码功能原文地址https://github.com/MY729/front-common-funtion/blob/master/picture-code-demo/README.md 预览地址https://my729.github.io/front-common-funtion/picture-code-demo/picture-code.html 准备工作demo 基于 vue + elelment-ui 首先创建一个html文件, 并引入 vue 和 elelment-ui(注意还有样式文件) <!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> <!-- elelment-ui样式 --> <link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"></head><body> </body><!-- 引入vue --><script src="https://cdn.jsdelivr.net/npm/vue"></script><!-- 引入element-ui --><script src="https://unpkg.com/element-ui/lib/index.js"></script></html>接下来就可以写我们的打码功能啦 实现思路创建canvas画布,并将要打码的图片绘制上去监听鼠标在图片上的点击,移动、松开事件,在canvas画布上绘制要打码的区域处理绘制的打码区域保存打码后的图片将要打码的图片绘制到canvas画布上// 初始化 绘制图片toCode (currentImg) { this.$nextTick(() => { // 获取将要绘制的canvas的父元素节点 let parentId = document.getElementById('parentId') // 初始化图片 let drawImg = new Image() drawImg.setAttribute('crossOrigin', 'anonymous') drawImg.crossOrigin = 'Anonymous' drawImg.src = currentImg // 创建canvas元素并添加到父节点中 let addCanvas = document.createElement('canvas') parentId.appendChild(addCanvas) let canvas = parentId.lastElementChild canvas.id = 'imgCanvas' if (canvas.getContext) { let ctx = canvas.getContext('2d') // 绘制图片 drawImg.onload = function () { canvas.width = 720 canvas.height = 500 ctx.drawImage(drawImg, 0, 0, 720, 500) } } })}点击打码按钮,绘制打码区域思路: ...

June 17, 2019 · 5 min · jiezi

canvas绘制圆形动画圆角进度条

如果不想看步骤的可以直接看最后面有完整的代码最近在做一个圆形的进度条,在网上看了一些例子有些地方不太理解,后来自己写了个一个分享一下 先上一个最终的效果 首先画一整个圆 const cvsWitdh = 220 const cvsHeight = 220 const progess = 50 // 定义进度为50 const maxPro = 100 // 定义总进度为100 const r = 100 // 定义圆的半径为100 this.cvs.width = cvsWitdh this.cvs.height = cvsHeight const ctx = this.cvs.getContext('2d') ctx.lineWidth = 10 ctx.strokeStyle = '#15496B' ctx.arc(r + 10, r + 10, r, 0, 2 * Math.PI) ctx.stroke() // 至此大圆画完上面的代码需要注意的是 arc 方法的最后一侧参数是弧度(2)不是角度,画圆的起点是表的3点的位置开始画的不是12点位置然后是画一个进度的圆弧画圆弧度,主要是需要计算出起点的弧度和终点的弧度 ctx.beginPath() ctx.lineCap = 'round' // 下面是渐变的代码不需要的可以换成纯色 let grd = ctx.createLinearGradient(0, 0, 220, 220) grd.addColorStop(0, 'red') grd.addColorStop(1, 'blue') ctx.strokeStyle = grd const startRadian = progress >= maxPro ? 0 : Math.PI * 1.5 const rate = progress / maxPro const endRadian = progress >= maxPro ? 2 * Math.PI : 2 * Math.PI * rate - Math.PI / 2 ctx.arc(r + 10, r + 10, r, startRadian, endRadian) ctx.stroke()上面的代码中 ctx.lineCap = 'round' 这个是设置最终绘制的线是带圆角的起点的弧度计算方式const startRadian = progess >= maxPro ? 0 : Math.PI * 1.5 ...

June 14, 2019 · 3 min · jiezi

带着canvas去流浪5绘制K线图

示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 [TOC] 一. 任务说明使用原生canvasAPI绘制K线图。(截图以及数据来自于百度Echarts官方示例库【查看示例链接】)。 二. 重点提示K线图最常见的是在金融市场,尤其是股市中,它的绘制算法和表达的意思是直接相关的: 一般一个数据点包含开盘价,收盘价,当日最高价,当日最低价4个数据点。当开盘价低于收盘价时,当天为涨价,则图形为红色,反之则为绿色。图形中间的细线是当日最高价和当日最低价之间的连线。图形中的矩形是在开盘价和收盘价之间的范围。了解了上述基本知识,K线图的绘制和折线图其实并没有太大区别,按部就班去绘制就好了。如果仔细观察Echarts官方提供的示例会发现图例中还有 MA5,MA10这样的图例标记,这里其实指的是N天的移动平均值Moving Average N,是减小数据波动性展示其宏观规律的常用方法之一,示例中的MA5就是指依次将源数据中每5个点的值求平均值作为当前点的数据(至于5个点是从当前点开始算,还是从当前点结束都是可以的)。 三. 示例代码实现难度较低,本文不再赘述。 /*数据点来自于百度Echarts官方示例库* 每个数据点意义:[日期,开盘(open),收盘(close),最低(lowest),最高(highest)]* 例如: ['2013/2/7', 2430.69,2418.53,2394.22,2433.89],*//** * 绘制数据 */function drawData(options) { let data = options.data; let xLength = options.chartZone[2] - options.chartZone[0]; let c;//记录当前绘制点的颜色 let gap = xLength / options.xAxisLabel.length; let activeX = 0;//记录绘制过程中当前点的坐标 let activeY = 0;//记录绘制过程中当前点的y坐标 context.strokeWidth = 2; context.beginPath(); context.moveTo(options.chartZone[0],options.chartZone[3]);//先将起点移动至0,0坐标 for(let i = 0; i < data.length; i++){ //获取绘图颜色 c = getColor(data[i]); context.strokeWidth = 1; context.strokeStyle = context.fillStyle = c; //计算绘制中心点x坐标 activeX = options.chartZone[0] + (i + 1) * gap; //绘制最高最低线; context.beginPath(); context.moveTo(activeX,transCoord(data[i][3])); context.lineTo(activeX,transCoord(data[i][4])); context.closePath(); context.stroke(); //绘制开盘收盘矩形 if (data[i][0] >= data[i][5]) { context.fillRect(activeX - 5 , transCoord(data[i][0]) , 10, transCoord(data[i][6]) - transCoord(data[i][0])); } else{ context.fillRect(activeX - 5 , transCoord(data[i][7]) , 10, transCoord(data[i][0]) - transCoord(data[i][8])); } } }//根据K线图的数据中开盘价和收盘价计算绘图颜色function getColor(data) { return data[0] >= data[1] ? '#1abc9c' : '#DA5961';}//从可视坐标系坐标转换为canvas坐标系坐标function transCoord(coord) { return options.chartZone[3] - (options.chartZone[3] - options.chartZone[1])*(coord - options.yMin) / (options.yMax - options.yMin);}浏览器中可查看效果: ...

June 13, 2019 · 1 min · jiezi

Canvas-进阶一二维码的生成与扫码识别

背景前些日子当前端面试官,问了一个问题:“你了解过canvas吗?” “这个我知道,我有做过DEMO,这个不难吧,看着它的api接口就能实现!” 看着他这么(蜜汁)自信,我决定深入了解(为难)一下他! “电商中大转盘,九宫格,刮刮乐,如何使用canvas实现,讲讲你的思路?” “二维码的生成和扫码识别如何实现?” “图片的粒子爆炸效果呢?” “......” 因此,打算写一系列关于 canvas 的文章,探索学习提升自己的同时顺便分享给大家。 二维码的生成 二维码的生成需借助第三方库,利用其算法对文本转化成二维码,并用 canvas 绘画出来。利用 canvas.toDataURL('image/png') 获取二维码转 base64 值,再将其赋值给 img 标签的 src 属性 这里我使用了一个库,qrcodejs. 可点击 《Demo》 查看效果 使用方法如下: <!-- index.html --><!DOCTYPE html><html> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <title>Suporka Vue App</title> <style> .container { padding: 60px; margin: 0 auto; line-height: 50px; } input { display: inline-block; width: 200px; height: 32px; line-height: 1.5; padding: 4px 7px; font-size: 12px; border: 1px solid #dcdee2; border-radius: 4px; color: #515a6e; background-color: #fff; background-image: none; position: relative; cursor: text; transition: border 0.2s ease-in-out, background 0.2s ease-in-out, box-shadow 0.2s ease-in-out; } button { color: #fff; background-color: #19be6b; border-color: #19be6b; outline: 0; vertical-align: middle; line-height: 1.5; display: inline-block; font-weight: 400; text-align: center; -ms-touch-action: manipulation; touch-action: manipulation; cursor: pointer; background-image: none; border: 1px solid transparent; white-space: nowrap; -webkit-user-select: none; -moz-user-select: none; -ms-user-select: none; user-select: none; padding: 5px 15px 6px; font-size: 12px; border-radius: 4px; transition: color 0.2s linear, background-color 0.2s linear, border 0.2s linear, box-shadow 0.2s linear; } #qrcode { margin-top: 20px; } </style> </head> <body> <div class="container"> <input type="text" placeholder="请输入您想转化成二维码的字符串" id="input" /> <button onclick="creatQRcode();">一键生成</button> <div id="qrcode"></div> </div> <script src="https://zxpsuper.github.io/Demo/qrcode/qrcode-dev.js"></script> <script type="text/javascript"> var qrcode = null; function creatQRcode() { document.getElementById("qrcode").innerHTML = ""; qrcode = new QRCode(document.getElementById("qrcode"), { text: document.getElementById("input").value, width: 200, height: 200, colorDark: "#000000", colorLight: "#ffffff", correctLevel: QRCode.CorrectLevel.H }); } </script> </body></html>options ...

June 12, 2019 · 6 min · jiezi

带着canvas去流浪4绘制散点图

示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 [TOC] 一. 任务说明使用原生canvasAPI绘制散点图。(截图以及数据来自于百度Echarts官方示例库【查看示例链接】)。 二. 重点提示学习过折线图的绘制后,如果数据点只有坐标数据,则通过基本的坐标转换在对应的点上绘制出散点并不难实现。而在气泡图中,当我们直接将百度Echarts示例中的数据拿来经过一定的线性缩小后作为半径直接绘制散点时,就会出现一些问题,数据集的范围跨度较大,导致大部分点呈现后都非常小,这个时候就需要使用某种方法从真实数据值映射到散点圆半径进行映射,来缩小它们之间的差异,否则一旦数据集中有一个偏离度较大的点,就会造成其他点所对应的散点半径都很大或者都很小,对数据呈现来说是不可取的。例如在下面的示例中,当使用几种不同的映射方法来处理数据后,可以看到绘制的散点图是不一样的。 //求散点半径时所使用的公式//1.直接数值r = value * 5 / 100000000;//2.求对数r = Math.log(value);//3.求指数r = Math.pow(value,0.4) / 100;所绘制出的散点图如下所示: 坐标映射的实现思路其实并不算复杂,它的概念可以参考算法的时间复杂度来进行理解,挑选一个增长更快的映射函数来区分相近的点,或者挑选一个增长更慢的映射函数来减小大跨度数据之间的差异,在数据可视化中是非常实用的技巧。本文示例中的效果是笔者自己手动调的,如果要实现根据数据集自动挑选适当的映射函数,还需要设计一些计算方法,感兴趣的读者可以自行研究。 三. 示例代码气泡散点图绘制示例代码(坐标轴的绘制过程在前述博文中已经出现过很多次,故不再赘述,有需要的小伙伴可以直接翻看这个系列之前的博文或者查看本篇的demo): /*数据点来自于百度Echarts官方示例库,每个数值分别表示[横坐标,纵坐标,数值,国家,年份]*[28604,77,17096869,'Australia',1990]*//** * 绘制数据 */function drawData(options) { let data = options.data;//获取数据集 let xLength = (options.chartZone[2] - options.chartZone[0]); let yLength = (options.chartZone[3] - options.chartZone[1]); let gap = xLength / options.xAxisLabel.length; //遍历两个年份 for(let i = 0; i < data.length ;i++){ let x,y,r,c; context.fillStyle = options.colorPool[i];//从颜色池中选取颜色 context.globalAlpha = 0.8;//为避免点覆盖,采取半透明绘制 //遍历各个数据点 for(let j = 0; j < data[i].length ; j++){ //计算坐标 x = options.chartZone[0] + xLength * data[i][j][0] / 70000; y = options.chartZone[3] - yLength * (data[i][j][4] - 55) / (85 - 55); //直接数值 r = data[i][j][5] * 5 / 100000000; //求对数 r = Math.log(data[i][j][6]); //开根号 r = Math.pow(data[i][j][7],0.4) / 100; //绘制散点 context.beginPath(); context.arc(x, y , r , 0 , 2*Math.PI,false); context.fill(); context.closePath(); } }}浏览器中可查看效果: ...

June 11, 2019 · 3 min · jiezi

在Vue中用canvas实现二维码和图片合成海报

在项目中经常会遇到需要将不同的二维码放到一张通用图片上,提供用户下载简单来说,就是利用canvas将同等比例的二维码在图片上叠加,生成海报 1. 设置相应比例一般来说海报背景都是固定的,可以直接放在public文件夹,二维码可根据后台返回数据,也可用canvas生成,在此不多赘述 import posterBgImg from '../public/images/poster_bg.png';// 海报底图import qrcodeImg from '../public/images/qrcode.png';// 二维码export default{ name: 'qrcode-in-poster', data(){ return { posterBgImg, qrcodeImg, posterSize: 930/650,// 海报高宽比例 qrCodeSize: {// 二维码与海报对应比例 =》 用于设置二维码在海报中的位置 width: 270/650, height: 270/930, left: 190/650, top: 448/650 }, poster: '',// 合成图片 } }};2. 获取屏幕宽度限定移动端最大宽度为 480px computed: { screenWidth(){ let w = document.body.clientWidt || document.documentElement.clientWidth || 375; return w > 480 ? 480 : w ; }};3. 组合图片methods: { combinedPoster(_url){ let that = this, qrcode = this.qrcodeImg; // 二维码地址 console.log("open draw: ", _url, qrcode) let base64 = '', canvas = document.createElement('canvas'), ctx = canvas.getContext("2d"), _w = this.screenWidth * 2, // 图片宽度: 由于手机屏幕时retina屏,都会多倍渲染,在此只设置2倍,如果直接设置等于手机屏幕,会导致生成的图片分辨率不够而模糊 _h = this.posterSize * _w, // 图片高度 _qr_w = this.qrCodeSize.width * _w, // 二维码宽 = 比例 * 宽度 _qr_h = this.qrCodeSize.height * _h, // 二维码高 = 比例 * 高度 _qr_t = this.qrCodeSize.top * _w, // 二维码顶部距离 = 比例 * 宽度 _qr_l = this.qrCodeSize.left * _w; // 二维码左侧距离 = 比例 * 宽度 // 设置canvas宽高 canvas.width = _w; canvas.height = _h; ctx.rect(0, 0, _w, _h); ctx.fillStyle = '#fff'; // 填充颜色 ctx.fill(); // 迭代生成: 第一层(底图)+ 第二层(二维码) // file:文件,size:[顶部距离,左侧距离,宽度,高度] let _list = [ { file: _url, size: [0, 0, _w, _h] }, { file: qrcode, size: [_qr_l, _qr_t, _qr_w, _qr_h] } ]; // 开始绘画 let drawing = (_index) => { // 判断当前索引 =》 是否已绘制完毕 if (_index < _list.length) { // 等图片预加载后画图 let img = new Image(), timeStamp = new Date().getTime(); // 防止跨域 img.setAttribute('crossOrigin', 'anonymous') // 链接加上时间戳 img.src = _list[_index].file + '?' + timeStamp img.onload = function() { // 画图 ctx.drawImage(img, ..._list[_index].size) // 递归_list drawing(_index + 1) } } else { // 生成图片 base64 = canvas.toDataURL("image/png") if (base64) { // 赋值相应海报上 this.$set(that, 'poster', base64) } } } drawing(0) }};mounted(){ // 需要合成海报的图片 this.draw(this.posterBgImg)}4. 下载点击下载合成图片 ...

June 10, 2019 · 2 min · jiezi

带着canvas去流浪3绘制饼图

示例代码托管在:http://www.github.com/dashnowords/blogs博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 [TOC] 一. 任务说明使用原生canvasAPI绘制饼图(南丁格尔玫瑰)。(截图以及数据来自于百度Echarts官方示例库【查看示例链接】)。 二. 重点提示南丁格尔玫瑰图的画法有很多种,Echarts中提供的以半径或面积两种不同模式,本文中以面积比例画法为例,绘制算法如下: 确定每个扇区的角度。由于所有扇区的角度加在一起为2 ,我们先按照数据比例来计算角度: $$_i = \frac{data_i}{\sum_{i=0}^ndata_i}*2\pi$$ 每个扇区面积与总面积之间的比例即为数值的比,将给定参数数组options.radius中的最大和最小数值作为数值最大的一块扇形的绘图数据,代入如下公式即可求得总面积S:$$\frac{\pi(R_{i}^2 - R_{min}^2)*_{i}/2\pi}{S} = \frac{data_i}{data_{sum}}$$ 再利用上述公式分别计算出每个扇形对应的外圆半径,在canvas中绘制路径并填充即可。三. 示例代码南丁格尔玫瑰图绘制示例代码: //绘制饼图drawPieChart(options); /** * 绘制饼图 * @param {[type]} options [description] * @return {[type]} [description] */function drawPieChart(options) { //记录最大数值以反求面积总和 options.maxValue = 0; //求数据集总和以在后续计算每个扇形的角度比例 options.totalNum = options.data.reduce((pre,cur)=>{ if (cur.value > options.maxValue) { options.maxValue = cur.value; } return pre+cur.value; },0); /*以最大值对应最大半径来计算面积总和,并覆盖原值 *使得最大的一块扇形外圆半径为options.radius[0] *内圆半径为options.radius[1] */ let Rmin = options.radius[0]; let Rmax = options.radius[1]; let r = Math.sqrt((Rmax*Rmax - Rmin*Rmin)*options.totalNum / options.maxValue + Rmin*Rmin); options.radius[1] = r; //移动坐标系原点至绘图中心 let paintingCenter={ x:parseInt(options.center[0],10)/100 * (options.chartZone[2] - options.chartZone[0]) + options.chartZone[0], y:parseInt(options.center[1],10)/100 * (options.chartZone[3] - options.chartZone[1]) + options.chartZone[1] } context.translate(paintingCenter.x, paintingCenter.y); //绘制每个扇形,过程中累加旋转角度 let allAngle = options.data.reduce((prev,cur,index)=>{ context.fillStyle = options.colorPool[index] let angle = calcPaintingData(cur,options); return prev + angle; },0); //绘制中空白色圆 context.beginPath(); context.fillStyle = 'white'; context.arc(0,0,options.radius[0],0,2*Math.PI,false); context.fill();}/** * 计算每个扇形所需要的绘图参数 */function calcPaintingData(data,options) { let scale = data.value / options.totalNum; let angle = scale * 2 * Math.PI; let Rmin = options.radius[0]; let Rmax = options.radius[1]; let r = Math.sqrt(scale * (Rmax*Rmax - Rmin*Rmin) + Rmin*Rmin); data.r = r; //绘制扇形 paintFan({ r:r, angle:angle, data:data, options:options }); return angle;//将角度值返回给外层函数以供累加}//绘制扇形function paintFan(opt) { context.beginPath(); context.lineTo(opt.r,0); context.arc(0,0,opt.r,0,opt.angle,false); context.lineTo(0,0); context.closePath(); context.fill(); context.rotate(opt.angle);}浏览器中可查看效果: ...

June 9, 2019 · 1 min · jiezi

vue项目中实现图片裁剪功能

原文地址https://github.com/MY729/pict... 在vue项目中实现图片裁剪功能演示地址https://my729.github.io/picture-crop-demo/dist/#/ 前言vue版本:3.6.3 https://cli.vuejs.org/zh/cropperjs: 1.5.1 https://github.com/fengyuanchen/cropperjselementUI https://element.eleme.io/#/zh-CN使用 cropperjs插件 和 原生canvas 两种方式实现图片裁剪功能使用cropperjs插件安装cropperjsyarn install cropperjs初始化一个canvas元素,并在上面绘制图片<canvas :id="data.src" ref="canvas"></canvas>// 在canvas上绘制图片drawImg () { this.$nextTick(() => { // 获取canvas节点 let canvas = document.getElementById(this.data.src) if (canvas) { // 设置canvas的宽为canvas的父元素宽度,宽高比3:2 let parentEle = canvas.parentElement canvas.width = parentEle.offsetWidth canvas.height = 2 * parentEle.offsetWidth / 3 let ctx = canvas.getContext('2d') ctx.clearRect(0, 0, canvas.width, canvas.height) let img = new Image() img.crossOrigin = 'Anonymous' img.src = this.data.src img.onload = function () { ctx.drawImage(img, 0, 0, canvas.width, canvas.height) } } })}如果遇到canvas跨域绘制图片报错,设置图片img.crossOrigin = 'Anonymous',并且服务器响应头设置Access-Control-Allow-Origin:*创建cropperjs// 引入import Cropper from 'cropperjs'// 显示裁剪框initCropper () { let cropper = new Cropper(this.$refs.canvas, { checkCrossOrigin: true, viewMode: 2, aspectRatio: 3 / 2 })}更多方法和属性,参考官网: https://github.com/fengyuanchen/cropperjs具体实现,可以查看源码的cropper.vue 或 cropper.one.vue组件: ...

June 6, 2019 · 3 min · jiezi

原生js实现Canvas实现拖拽式绘图支持画笔线条箭头三角形和圆形等等图形绘制功能

一、实现的功能1、基于oop思想构建,支持坐标点、线条(由坐标点组成,包含方向)、多边形(由多个坐标点组成)、圆形(包含圆心坐标点和半径)等实体 2、原生JavaScript实现,不依赖任何第三方js库和插件 3、多图形绘制(支持画笔、线条、箭头、三角形、矩形、平行四边形、梯形以及多边形和圆形绘制) 4、拖拽式绘制(鼠标移动过程中不断进行canvas重绘) 5、图片绘制(作为背景图片时重绘会发生闪烁现象,暂时有点问题,后面继续完善) 5、清空绘制功能 6、新版本优化绘制性能(使用共享坐标变量数组,减少了大量的对象创建操作) 7、新版本支持箭头绘制功能 二、完整实现代码 DrawingTools =(function(){ //公共方法 var getDom=function(id){return document.getElementById(id)}; var isNull=function(s){return s==undefined||typeof(s)=='undefined'||s==null||s=='null'||s==''||s.length<1}; var hideDefRM=function(){document.oncontextmenu=function(){return false}};//屏蔽浏览器默认鼠标事件 /**绘图容器*/ var cbtCanvas; /**绘图对象*/ var cxt; /**绘制的图形列表*/ var shapes=new Array(); var graphkind={'cursor':0,'pen':1,'line':2,'trian':3,'rect':4,'poly':5,'circle':6,'arrow':21,'parallel':41,'trapezoid':42}; //背景图片绘制配置 var bgPictureConfig={ pic:null,//背景图片地址或路径 repaint:true,//是否作为永久背景图,每次清除时会进行重绘 }; //加载并绘制图片(src:图片路径或地址),默认重绘背景图 var loadPicture=function(src){ if(isNull(bgPictureConfig.repaint)||bgPictureConfig.repaint){bgPictureConfig.pic=src} var img = new Image(); img.onload = function(){cxt.drawImage(img,0,0)} img.src =src; } //绘图基础配置 var paintConfig={lineWidth:1,//线条宽度,默认1 strokeStyle:'red',//画笔颜色,默认红色 fillStyle:'red',//填充色 lineJoin:"round",//线条交角样式,默认圆角 lineCap:"round",//线条结束样式,默认圆角 }; //重新载入绘制样式 var resetStyle=function(){ cxt.strokeStyle=paintConfig.strokeStyle; cxt.lineWidth=paintConfig.lineWidth; cxt.lineJoin=paintConfig.lineJoin; cxt.lineCap=paintConfig.lineCap; cxt.fillStyle=paintConfig.fillStyle; } //鼠标图形 var cursors=['crosshair','pointer']; /** 切换鼠标样式*/ var switchCorser=function(b){ cbtCanvas.style.cursor=((isNull(b)?isDrawing():b)?cursors[0]:cursors[1]); } //操作控制变量组 var ctrlConfig={ kind:0,//当前绘画分类 isPainting:false,//是否开始绘制 startPoint:null,//起始点 cuGraph:null,//当前绘制的图像 cuPoint:null,//当前临时坐标点,确定一个坐标点后重新构建 cuAngle:null,//当前箭头角度 vertex:[],//坐标点 } /**获取当前坐标点*/ var getCuPoint=function(i){ return ctrlConfig.cuPoint[i]; } /**设置当前坐标点*/ var setCuPoint=function(p,i){ return ctrlConfig.cuPoint[i]=p; } /**设置当前临时坐标点值*/ var setCuPointXY=function(x,y,i){ if(isNull(ctrlConfig.cuPoint)){ var arr=new Array(); arr[i]=new Point(x,y); ctrlConfig.cuPoint=arr; }else if(isNull(ctrlConfig.cuPoint[i])){ setCuPoint(new Point(x,y),i); }else{ ctrlConfig.cuPoint[i].setXY(x,y); } return getCuPoint(i); } /**是否正在绘制*/ var isDrawing=function (){ return ctrlConfig.isPainting; } /**开始绘制状态*/ var beginDrawing=function(){ ctrlConfig.isPainting=true; } /**结束绘制状态*/ var stopDrawing=function(){ ctrlConfig.isPainting=false; } /**是否有开始坐标点*/ var hasStartPoint=function(){ return !isNull(ctrlConfig.startPoint); } /**设置当前绘制的图形*/ var setCuGraph=function(g){ ctrlConfig.cuGraph=g; } /**获取当前绘制的图形*/ var getCuGraph=function(){ return ctrlConfig.cuGraph; } /**设置开始坐标点(线条的起始点,三角形的顶点,圆形的圆心,四边形的左上角或右下角,多边形的起始点)*/ var setStartPoint=function(p){ ctrlConfig.startPoint=p; } /**获取开始坐标点*/ var getStartPoint=function(){ return ctrlConfig.startPoint; } /**清空全部*/ var clearAll=function(){ cxt.clearRect(0,0,cbtCanvas.width,cbtCanvas.height); } /**重绘*/ var repaint=function(){ clearAll(); /* if(bgPictureConfig.repaint){ loadPicture(bgPictureConfig.pic); }*/ } /**点(坐标,绘图的基本要素,包含x,y坐标)*/ var Point=(function(x1,y1){ var x=x1,y=y1; return{ set:function(p){ x=p.x,y=p.y; }, setXY:function(x2,y2){ x=x2;y=y2; }, setX:function(x3){ x=x3; }, setY:function(y3){ y=y3; }, getX:function(){ return x; }, getY:function(){ return y; } } }); /**多角形(三角形、矩形、多边形),由多个点组成*/ var Poly=(function(ps1){ var ps=isNull(ps1)?new Array():ps1; var size=ps.length; return{ set:function(ps2){ ps=ps2; }, getSize:function(){ return size; }, setPoint:function(p,i){ if(isNull(p)&&isNaN(i)){ return; } ps[i]=p; }, setStart:function(p1){ if(isNull(ps)){ ps=new Array(); return ps.push(p1); }else{ ps[0]=p1; } }, add:function(p){ if(isNull(ps)){ ps=new Array(); } return ps.push(p); }, pop:function(){ if(isNull(ps)){ return; } return ps.pop(); }, shift:function(){ if(isNull(ps)){ return; } return ps.shift; }, get:function(){ if(isNull(ps)){ return null; } return ps; }, draw:function(){ cxt.beginPath(); for(i in ps){ if(i==0){ cxt.moveTo(ps[i].getX(),ps[i].getY()); }else{ cxt.lineTo(ps[i].getX(),ps[i].getY()); } } cxt.closePath(); cxt.stroke(); } } }); /*线条(由两个点组成,包含方向)*/ var Line=(function(p1,p2,al){ var start=p1,end=p2,angle=al; var drawLine=function(){ cxt.beginPath(); cxt.moveTo(p1.getX(),p1.getY()); cxt.lineTo(p2.getX(),p2.getY()); cxt.stroke(); } //画箭头 var drawArrow=function() { var vertex =ctrlConfig.vertex; var x1=p1.getX(),y1=p1.getY(),x2=p2.getX(),y2=p2.getY(); var el=50,al=15; //计算箭头底边两个点(开始点,结束点,两边角度,箭头角度) vertex[0] = x1,vertex[1] = y1, vertex[6] = x2,vertex[7] = y2; //计算起点坐标与X轴之间的夹角角度值 var angle = Math.atan2(y2 - y1, x2 - x1) / Math.PI * 180; var x = x2 - x1,y = y2 - y1,length = Math.sqrt(Math.pow(x, 2) + Math.pow(y, 2)); if (length < 250) { el/=2,al/2; }else if(length<500){ el*=length/500,al*=length/500; } vertex[8] = x2 - el * Math.cos(Math.PI / 180 * (angle + al)); vertex[9] = y2- el * Math.sin(Math.PI / 180 * (angle + al)); vertex[4] = x2- el* Math.cos(Math.PI / 180 * (angle - al)); vertex[5] = y2 - el * Math.sin(Math.PI / 180 * (angle - al)); //获取另外两个顶点坐标 x=(vertex[4]+vertex[8])/2,y=(vertex[5]+vertex[9])/2; vertex[2] = (vertex[4] + x) / 2; vertex[3] = (vertex[5] + y) / 2; vertex[10] = (vertex[8] +x) / 2; vertex[11] = (vertex[9] +y) / 2; //计算完成,开始绘制 cxt.beginPath(); cxt.moveTo(vertex[0], vertex[1]); cxt.lineTo(vertex[2], vertex[3]); cxt.lineTo(vertex[4], vertex[5]); cxt.lineTo(vertex[6], vertex[7]); cxt.lineTo(vertex[8], vertex[9]); cxt.lineTo(vertex[10], vertex[11]); cxt.closePath(); cxt.fill(); cxt.stroke(); } return{ setStart:function(s){ start=s; }, setEnd:function(e){ end=e; }, getStart:function(){ return start; }, getEnd:function(){ return end; }, draw:function(){ if(angle){ drawArrow(); }else{ drawLine(); } } } }); /**圆形(包含圆心点和半径)*/ var Circle=(function(arr){ //包含起始点(圆心)和结束点,以及圆半径 var startPoint=arr.start,endPoint=arr.end,radius=arr.radius; /*绘制圆*/ var drawCircle=function(){ cxt.beginPath(); var x=startPoint.getX(); var y=startPoint.getY(); if(isNull(radius)){ radius=calculateRadius(startPoint,endPoint); } //x,y,半径,开始点,结束点,顺时针/逆时针 cxt.arc(x,y,radius,0,Math.PI*2,false); // 绘制圆 cxt.stroke(); } //计算圆半径 var calculateRadius=function(p1,p2){ var width=p2.getX()-p1.getX(); var height=p2.getY()-p1.getY(); //如果是负数 if(width<0||height<0){ width=Math.abs(width); } //计算两点距离=平方根(width^2+height^2) c=Math.sqrt(Math.pow(width,2)+Math.pow(height,2)); return c; } return{ set:function(params){ startPoint=params.start; endPoint=params.end; radius=params.radius; }, setPoint:function(p1){ p=p1; }, getPoint:function(){ return p; }, setRadius:function(r1){ radius=r1; }, getRadius:function(){ return radius; }, calcRadius:calculateRadius, //绘制 draw:drawCircle, } }); /**绘制线条工具方法*/ var drawLine=function(p){ cxt.beginPath(); cxt.moveTo(startPosition.getX(),startPosition.getY()); cxt.lineTo(p.getX(),p.getY()); cxt.stroke(); } /**绘制三角形工具方法*/ var drawTrian=function(ps){ cxt.beginPath(); var a=ps.get(); cxt.moveTo(a[0].getX(),a[0].getY()); cxt.lineTo(a[1].getX(),a[1].getY()); cxt.lineTo(a[2].getX(),a[2].getY()); cxt.closePath(); cxt.stroke(); } /**绘制矩形工具方法*/ var drawRect=function(p2){ var p=getStartPoint(); var width=p.getX()-p2.getX(); var height=p.getY()-p2.getY(); cxt.beginPath(); cxt.strokeRect(x,y,width,height);//绘制矩形 } /*绘制多边形工具方法*/ var drawpolygon=function(ps){ if(ps.length>1){//保证只有两个坐标点才是矩形 cxt.beginPath(); var p=ctrlConfig.startPoint; var x=p.getX(); var y=p.getY(); cxt.moveTo(x,y); for(p1 in ps){ cxt.lineTo(p1.getX(),p1.getY()); } cxt.stroke(); } } /*绘制圆角矩形工具方法*/ var drawRoundedRect=function(x,y,width,height,radius){ cxt.beginPath(); cxt.moveTo(x,y+radius); cxt.lineTo(x,y+height-radius); cxt.quadraticCurveTo(x,y+height,x+radius,y+height); cxt.lineTo(x+width-radius,y+height); cxt.quadraticCurveTo(x+width,y+height,x+width,y+height-radius); cxt.lineTo(x+width,y+radius); cxt.quadraticCurveTo(x+width,y,x+width-radius,y); cxt.lineTo(x+radius,y); cxt.quadraticCurveTo(x,y,x,y+radius); cxt.stroke(); } /*绘制圆工具方法*/ var drawCircle=function(c){ var p=c.getPoint();//坐标点 var x=p.getX(); var y=p.getY(); var r=c.getRadius(); cxt.beginPath(); //x,y,半径,开始点,结束点,顺时针/逆时针 cxt.arc(x,y,r,0,Math.PI*2,false); // 绘制圆 cxt.stroke(); } //计算圆半径工具方法 var calculateRadius=function(p1,p2){ var width=p2.getX()-p1.getX(); var height=p2.getY()-p1.getY(); //如果是负数 if(width<0||height<0){ width=Math.abs(width); } //计算两点距离=平方根(width^2+height^2) c=Math.sqrt(Math.pow(width,2)+Math.pow(height,2)); return c; } //鼠标按键点击(首次点击确定开始坐标点,拖动鼠标不断进行图形重绘) var mouseDown = function(e){ var btnNum = e.button; if(btnNum==0){ console.log("选择:"+ctrlConfig.kind); //设置起始点 switch(ctrlConfig.kind){ case graphkind.pen://画笔(不松开鼠标按键一直画) beginDrawing();//开始绘制 cxt.beginPath(); cxt.moveTo(e.offsetX,e.offsetY); break; case graphkind.poly://多边形 var p=new Point(e.offsetX,e.offsetY); if(isDrawing()){ getCuGraph().add(p);//添加到 }else{//第一次确定开始坐标 beginDrawing();//开始绘制 setStartPoint(p); var poly=new Poly(); poly.add(p); setCuGraph(poly);//设置当前绘制图形 } break; case graphkind.line://线条 case graphkind.arrow://方向 case graphkind.trian://三角形 case graphkind.rect://矩形 case graphkind.parallel://平行四边形 case graphkind.trapezoid://梯形 beginDrawing();//开始绘制 var p=new Point(e.offsetX,e.offsetY); setStartPoint(p); var poly=new Poly(); poly.add(p); setCuGraph(poly);//设置当前绘制图形 break; case graphkind.circle://圆 console.log("确定图形绘制开始坐标点:"+e.offsetX+","+e.offsetY);//点击确定图形的开始坐标点 beginDrawing();//开始绘制 var p=new Point(e.offsetX,e.offsetY); setStartPoint(p); var circle= new Circle({'start':p}); setCuGraph(circle); break; case ctrlConfig.cursor: //手型鼠标 default://默认是手型鼠标,不允许绘制 } }else if(btnNum==2){ console.log("右键由于结束多边形绘制"); if(isDrawing()){ if(ctrlConfig.kind==graphkind.poly){ repaint(); getCuGraph().draw(); stopDrawing();//结束绘制 } } } hideDefRM();//屏蔽浏览器默认事件 } //鼠标移动(拖动,根据鼠标移动的位置不断重绘图形) var mouseMove = function(e){ if(isDrawing()&&hasStartPoint()){//检查是否开始绘制,检查是否有开始坐标点 //画笔不需要重绘 if(ctrlConfig.kind>1){ repaint();//重绘 } var p=setCuPointXY(e.offsetX,e.offsetY,0);//设置共享的临时坐标点,用于防止重复创建对象 switch(ctrlConfig.kind){ case graphkind.pen://画笔(一直画) cxt.lineTo(e.offsetX,e.offsetY); cxt.stroke(); break; case graphkind.poly://多边形 var poly=getCuGraph(poly); var size=poly.getSize(); poly.setPoint(p,(size-1)); poly.draw(); break; case graphkind.line://线条 var line=new Line(getStartPoint(),p,false); ctrlConfig.cuGraph=line; line.draw(); break; case graphkind.arrow://方向 var line=new Line(getStartPoint(),p,true); ctrlConfig.cuGraph=line; line.draw(); break; case graphkind.trian://三角形 var lu=getStartPoint(); var x2=p.getX(); var x1=lu.getX(); //三角形左边的点坐标计算方法:(x1-(x2-x1),y2) var x3=x1-(x2-x1); var l=setCuPointXY(x3,p.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象 var poly=getCuGraph();//获取当前图形 poly.set([lu,p,l]); poly.draw();//即时绘制 break; case graphkind.parallel://平行四边形 var lu=getStartPoint(); var x3=p.getX(); var x1=lu.getX(); //平行四边形两个未知坐标点计算方法:(x1-(x3-x1),y3),(x1+(x3-x1),y1) var x2=x3+(x3-x1); var x4=x1-(x3-x1); var ld=setCuPointXY(x2,lu.getY(),1);//设置共享的临时坐标点,用于防止重复创建对象 var ru=setCuPointXY(x4,p.getY(),2);//设置共享的临时坐标点,用于防止重复创建对象 var poly=getCuGraph();//获取当前图形 poly.set([lu,ru,p,ld]); poly.draw();//即时绘制 break; case graphkind.trapezoid://梯形 var lu=getStartPoint(); var x3=p.getX(); var x1=lu.getX(); //梯形两个未知坐标点计算方法:(x3-(x3-x1)/2,y1),(x1-(x3-x1)/2,y3) var x2=x3-(x3-x1)/2; var x4=x1-(x3-x1)/2; var ld=setCuPointXY(x2,lu.getY(),1); var ru=setCuPointXY(x4,p.getY(),2); var poly=getCuGraph(); poly.set([lu,ru,p,ld]); poly.draw(); break; case graphkind.rect://矩形 var lu=getStartPoint(); //矩形右上角和左上角坐标计算方法 var ld=setCuPointXY(lu.getX(),p.getY(),1); var ru=setCuPointXY(p.getX(),lu.getY(),2); var poly=getCuGraph(); poly.set([lu,ru,p,ld]); poly.draw(); break; case graphkind.circle://圆 var circle=getCuGraph();//获取当前图形 circle.set({'start':getStartPoint(),'end':p}); circle.draw();//即时绘制 break; } } } //鼠标按键松开 var mouseUp = function(e){ if(isDrawing()){ //console.log("松开鼠标按键:"+e.offsetX+","+e.offsetY); //画笔不需要重绘 if(ctrlConfig.kind>1){ repaint(); getCuGraph().draw(); } if(ctrlConfig.kind!=graphkind.poly){//多边形绘制鼠标按键松开不结束绘制,多边形只有右键点击才能结束绘制 stopDrawing();//结束绘制 } } } //鼠标移出 var mouseOut = function(e){ console.log("鼠标移出绘制区域"+e.offsetX+","+e.offsetY); if(isDrawing()){ console.log("停止绘制"); if(ctrlConfig.kind>1){ repaint(); getCuGraph().draw(); } stopDrawing();//停止绘制 } } return{ isNull:isNull, getDom:getDom, clear:function(){ stopDrawing();//停止绘制 repaint(); }, /**初始化*/ init:function(params){ cbtCanvas=getDom(params.id); //浏览器是否支持Canvas if (cbtCanvas.getContext){ /**绘图对象*/ cxt=cbtCanvas.getContext("2d"); cbtCanvas.onmousedown = mouseDown; cbtCanvas.onmouseup = mouseUp; cbtCanvas.onmousemove = mouseMove; cbtCanvas.onmouseout = mouseOut; resetStyle();//载入样式 return true; }else{ return false; } }, /**设置背景图片*/ setBgPic:loadPicture, /**选择图形类型*/ begin:function(k){ console.log("选择绘制图形:"+k); if(isNaN(k)){//如果不是数字,先转换为对应字符 ctrlConfig.kind=kind[k]; }else{ ctrlConfig.kind=k; } switchCorser(true);//切换鼠标样式 }, /*手型,并停止绘图*/ hand:function(){ ctrlConfig.kind=0; stopDrawing();//停止绘制 switchCorser(false);//切换鼠标样式 } } }) 三、使用方式1、图形类型 ...

June 5, 2019 · 5 min · jiezi

canvas的学习

一、canvas简介 <canvas> 是 HTML5 新增的,一个可以使用脚本(通常为JavaScript)在其中绘制图像的 HTML 元素。它可以用来制作照片集或者制作简单(也不是那么简单)的动画,甚至可以进行实时视频处理和渲染。 它最初由苹果内部使用自己MacOS X WebKit推出,供应用程序使用像仪表盘的构件和 Safari 浏览器使用。 后来,有人通过Gecko内核的浏览器 (尤其是Mozilla和Firefox),Opera和Chrome和超文本网络应用技术工作组建议为下一代的网络技术使用该元素。 Canvas是由HTML代码配合高度和宽度属性而定义出的可绘制区域。JavaScript代码可以访问该区域,类似于其他通用的二维API,通过一套完整的绘图函数来动态生成图形。 Mozilla 程序从 Gecko 1.8 (Firefox 1.5)开始支持 <canvas>, Internet Explorer 从IE9开始<canvas> 。Chrome和Opera 9+ 也支持 <canvas>。 二、Canvas基本使用2.1 <canvas>元素<canvas id="tutorial" width="300" height="300"></canvas>1 <canvas>看起来和<img>标签一样,只是 <canvas> 只有两个可选的属性 width、heigth 属性,而没有 src、alt 属性。 如果不给<canvas>设置widht、height属性时,则默认 width为300、height为150,单位都是px。也可以使用css属性来设置宽高,但是如宽高属性和初始比例不一致,他会出现扭曲。所以,建议永远不要使用css属性来设置<canvas>的宽高。 替换内容 由于某些较老的浏览器(尤其是IE9之前的IE浏览器)或者浏览器不支持HTML元素<canvas>,在这些浏览器上你应该总是能展示替代内容。 支持<canvas>的浏览器会只渲染<canvas>标签,而忽略其中的替代内容。不支持 <canvas> 的浏览器则 会直接渲染替代内容。 用文本替换: 原文:https://blog.csdn.net/u012468...

June 5, 2019 · 1 min · jiezi

带着canvas去流浪2绘制折线图

示例代码托管在:https://github.com/dashnowords/blogs/tree/master/Demo/canvas-echarts/line-chart博客园地址:《大史住在大前端》原创博文目录 华为云社区地址:【你要的前端打怪升级指南】 一. 任务说明使用原生canvasAPI绘制折线图。(柱状图截图来自于百度Echarts官方示例库【查看示例链接】。 二. 重点提示一般折线图是比较好实现的,只需要调用最基本的moveTo()和lineTo( )方法来绘制即可。平滑折线图是一个难点,需要借助贝塞尔曲线来进行绘制,此时每段曲线的控制点算法就成了核心难点,对原理感兴趣的读者可以自行研究,本文直接利用算法的结论来进行实现。 上一节中为了以文字中点为参考,在绘制x轴文字时采用的方法是用measureText( )方法测量文字的宽度,然后偏移该距离的一半来达到效果,事实上我们可以通过设置textAlign属性为'center'来达到以文字宽度方向中线为参考点的绘制。 context.textAlign = 'center';context.drawText('Hello world',x ,y);三. 示例代码坐标轴及绘图参数设置请直接参见 【带着canvas去流浪】(1)绘制柱状图 或在示例demo中查看。 3.1 一般折线图折线图数据绘制示例代码: /** * 绘制数据 */function drawData(options) { let data = options.data;//数据点坐标 let xLength = (options.chartZone[2] - options.chartZone[0])*0.96;//线段尾部留白后x轴长 let yLength = (options.chartZone[3] - options.chartZone[1])*0.98;//线段尾部留白后y轴长 let gap = xLength / options.xAxisLabel.length;//x轴间隙 //缓存从数据值到坐标距离的比例因子 let yFactor =(options.chartZone[3] - options.chartZone[1]) *0.98 / options.yMax let activeX = 0;//记录绘制过程中当前点的坐标 let activeY = 0;//记录绘制过程中当前点的y坐标 context.strokeStyle = options.barStyle.color || '#1abc9c'; //02BAD4 context.strokeWidth = 2; context.beginPath(); context.moveTo(options.chartZone[0],options.chartZone[3]);//先将起点移动至0,0坐标 for(let i = 0; i < data.length; i++){ activeX = options.chartZone[0] + (i + 1) * gap; activeY = options.chartZone[3] - data[i] * yFactor; context.lineTo(activeX, activeY); } context.stroke(); }浏览器中可查看效果: ...

June 4, 2019 · 2 min · jiezi

Canvas2D基础

Canvas2D基础什么是Canvas<canvas>是H5中最受欢迎的元素,在页面上划出一片区域,利用JS在上面画出图形,起初由Apple公司提出,各大浏览器厂商也对其做了不同程度上的实现。canvas中规定了了2D context和3D context(WebGL),目前绝大部分浏览器支持2D context。WebGL的发展还比较缓慢。 基本使用1、toDataURL() 将画好的图像输出为图片 //get data URI of the imagevar imgURI = drawing.toDataURL("image/png");//display the imagevar image = document.createElement("img");image.src = imgURI;document.body.appendChild(image);2、原点是基于canvas元素左上角3、2D Context的两个基本绘画操作 fill and stroke Rectangles(矩形)1、fillRect() context.fillRect(10, 10, 50, 50);//draw a blue rectangle that’s semi-transparentcontext.fillStyle = "rgba(0,0,255,0.5)";context.fillRect(30, 30, 50, 50);2、strokeRect() //draw a red outlined rectanglecontext.strokeStyle = "#ff0000";context.strokeRect(10, 10, 50, 50);//draw a blue outlined rectangle that’s semi-transparentcontext.strokeStyle = "rgba(0,0,255,0.5)";context.strokeRect(30, 30, 50, 50);3、clearRect() //draw a red rectanglecontext.fillStyle = "#ff0000";context.fillRect(10, 10, 50, 50);//draw a blue rectangle that’s semi-transparentcontext.fillStyle = "rgba(0,0,255,0.5)";context.fillRect(30, 30, 50, 50);//clear a rectangle that overlaps both of the previous rectanglescontext.clearRect(40, 40, 10, 10);Drawing Paths1、如何画一个表盘 ...

May 31, 2019 · 2 min · jiezi

拆弹时刻小程序canvas生成海报二优化方案

海报生成速度缓慢问题的优化 微信头像在app.js中预先加载缓存多图片异步加载流程中断处理 二次授权失败的处理请求或者下载图片失败处理保存图片可被压缩海报生成速度缓慢问题的优化原因分析: 主要的时间消耗在于getImageInfo网络请求获取头像和下载图片获得临时地址的过程,可以看到海报中有3张图片(微信头像、主图、动态二维码(对应不同新闻的ID))需要下载,接下来主要就是对这3张图的优化 微信头像在app.js中预先加载缓存//app.js//可以在app.js中使用小程序默认的全局变量,将头像在加载的时候预先缓存App({ onLaunch: function () { // 获取用户信息 wx.getSetting({ success: res => { if (res.authSetting['scope.userInfo']) { // 已经授权,可以直接调用 getUserInfo 获取头像昵称,不会弹框 wx.getUserInfo({ success: res => { this.globalData.userInfo = res.userInfo; //从返回值中获取微信头像地址 let WxHeader = res.userInfo.avatarUrl; wx.getImageInfo({ src: WxHeader,//下载微信头像获得临时地址 success: res => { //将头像缓存在全局变量里 this.globalData.avatarUrlTempPath = res.path; }, fail: res => { //失败回调 } }); } }) } } }) }, globalData: { userInfo: null, //如果用户没有授权,无法在加载小程序的时候获取头像,就使用默认头像 avatarUrlTempPath: "./images/defaultHeader.jpg" }})大致思路是: 加载App.js的时候==> getSetting(判断是否授权) ==> getUserInfo(获取头像) ==> getImageInfo(生成临时地址) 将需要的网络请求在加载小程序的时候就异步完成,提前将临时地址缓存在全局变量globalData中,这样当用户进入新闻页面,点击生成海报的时候就不需要在请求微信头像,缩短了不少时间。 注意: 如果用户一开始没有微信授权,生成海报时又必须要用户头像不能使用默认的话,那就只能老老实实走之前的流程了。 ...

May 31, 2019 · 2 min · jiezi

web端ES绘图之image2D

作者:心叶时间:2019-05-29 22:31 一.简介首先,让我们来了解一个这个库主要解决的问题是什么,如何使用以及问题反馈等基本信息。 1.1 关注的问题本库致力于提供更简单的Web端二维绘图接口,主要包括这些方面:画笔、辅助计算、结点操作和一些零碎的小工具方法。我们希望绘图是简单而有趣的、高效而愉悦的! 主要是在svg和canvas2D上绘图,虽然有提供比如Maritx4坐标变换等三维相关方法,这是考虑到一些潜在的需求。 1.2 如何使用如果你开发的是一个web项目,直接在页面引入打包后的文件后即可(在代码中通过image2D或$$调用): <script src="./build/image2D.min.js" type="text/javascript"></script>如果你想通过npm方式管理,首先你需要通过命令行安装image2D,就像这样: npm install --save image2d安装好了以后,在需要的地方引入即可: import $$ from 'image2d';1.3 获取帮助在使用image2D的时候,如果遇到任何疑惑或问题,包括建议或对未来版本的想法,请先在 Github issue 上查找是否存在相似内容,然后进行补充或追问,当然也可以增加新的话题进行交流,除非特殊情况,你会在48小时内获得 作者 回复。 二.结点操作为了绘图的方便,我们提供了最基本的结点相关操作。因为这些操作是为了绘图而开发的,可能和纯粹的结点操作方法在设计上有所不同,请知悉。 2.1 结点对象所有的结点操作都是由结点对象提供的,因此,我们首先来看看如何创建一个结点对象: var imageObject=$$(selector[, context]);如上所示,通过执行$$或image2D方法即可获取一个结点对象,我们可以传递二个参数来确定当前结点对象维护的结点是哪些。 结点对象维护了一些结点,调用结点对象上的方法,就是对维护的这些结点进行操作。 第一个参数selector(称为选择器)是必须的,用以确定当前维护的结点是哪些。 第二个参数context是可选的,默认选择器在全局查找,你也可以通过传递一个dom结点指定查找上下文(id选择器会忽略此参数直接在全局查找)。 选择器 任何合法的选择器都应该是下列中的某一种: 模板字符串,比如'<g>'、'<canvas>非常抱歉,您的浏览器不支持canvas!</canvas>'等。ID选择器,比如'#demo'会选中id是'demo'的第一个标签。class和标签选择器,比如'.cls'、'div'、'div.cls'和'g.info.warn'等。全部选择器,也就是字符串'*',会选中全部结点。非查询选择器,包括:结点,结点数组和结点对象。这类选择器不会进行查找,直接把传递的结点作为维护结点,因此也会忽略查找上下文。筛选函数,传递一个函数,函数形参是当前面对的结点,通过返回true或false来判断是否把当前面对的结点加入结点对象中。创建好了结点对象以后,后续依旧可以对维护的结点进行筛选后获取新的结点对象: var new_imageObject=imageObject.filter(filterback);返回新的结点对象,不会修改原来的结点对象。其中filterback叫做筛选函数,有二个形参,分别是当前面对结点序号和维护了当前面对结点的结点对象,通过返回true或false来判断是否把当前面对的结点加入新创建的结点对象中。 2.2 编辑把当前维护的结点加到目标结点内部的结尾: imageObject.appendTo(target[, context]);target是一个合法的选择器即可,context是一个结点,表示目标结点查找上下文,可选,默认全局查找,下同。 把当前维护的结点加到目标结点内部的开头: imageObject.prependTo(target[, context]);把当前维护的结点加到目标结点之后: imageObject.afterTo(target[, context]);把当前维护的结点加到目标结点之前: imageObject.beforeTo(target[, context]);从页面中删除当前维护的结点: imageObject.remove();设置或获取结点中的文本: imageObject.text([content]);2.3 样式和属性修改或获取结点样式: imageObject.css();通过不同的参数来确定是获取样式还是设置样式,具体有下列参数选项可选: (key):获取指定样式。(key,value):设置指定样式。():获取全部样式。(json):设置大量样式。设置或获取结点属性: imageObject.attr();和样式css方法类似,也是通过具体参数来确定是获取还是设置样式: (attr):获取属性。(attr,value):设置指定属性值。(json):设置大量属性。2.4 事件相关给维护的结点绑定事件: imageObject.bind(eventType, callback);获取鼠标相对当前维护的元素左上角位置: imageObject.position(event);2.5 数据绑定绘图就离不开数据,把数据和结点关联起来,会简化结点管理和数据保存问题,这里涉及四个核心方法:data、datum、enter和exit,还有一些相关方法(因为结点对象的各个方法之间不完全是独立的)。 把数据绑定到一组结点或返回第一个结点数据: imageObject.datum();通过具体的参数来判断是获取还是绑定,有下列参数选项可选: ():不带任何参数表示获取数据。(data):带一个参数表示设置结点对象维护的全部结点数据为data。(data, calcback):和带一个参数类似,只不过绑定的数据是经过calcback函数重新计算后返回的值,该函数有二个形参:data和index。把一组数据绑定到一组结点或返回一组结点数据: ...

May 29, 2019 · 2 min · jiezi

Canvas-文字碰撞检测并抽稀

需求背景一般在做地图相关的需求是才会用到文字抽稀,我也是在为公司的地图引擎实现一个功能时才实现了该方法,在这里将其简化了,就在普通的 Canvas 上进行操作,并没有引入地图概念 效果 碰撞检测计算文字在 canvas 中所占据的范围// 计算文字所需的宽度var p = { x: 10, y: 10, name: "测试文字"};var measure = ctx.measureText(p.name);// 求出文字在 canvas 画板中占据的最大 y 坐标var maxX = measure.width + p.x;// 求出文字在 canvas 画板中占据的最大 y 坐标// canvas 只能计算文字的宽度,并不能计算出文字的高度。所以就利用文字的宽度除以文字个数计算个大概var maxY = measure.width / p.name.length + p.y;var min = { x: p.x, y: p.y };var max = { x: maxX, y: maxY };// bounds 为该文字在 canvas 中所占据的范围。// 在取点位坐标作为最小范围时,textAlign、textBaseline 按照以下方式设置会比较准确。// 如设置在不同的位置展示,范围最大、最小点也需进行调整// ctx.textAlign = "left";// ctx.textBaseline = "top";var bounds = new Bounds(min, max);Bounds 范围对象 ...

May 26, 2019 · 1 min · jiezi

用-canvas-制作奥运五环

<canvas id="xxb" width="500" height="500"></canvas> <script type="text/javascript"> var bg = document.getElementById('xxb'); var ctx = bg.getContext('2d'); //第一个圆圈 ctx.beginPath(); ctx.lineWidth=10; //圆圈的边线的大小 ctx.strokeStyle='RGB(43,0,155)'; //圆圈的颜色 ctx.arc(60,60,50,0*Math.PI,2.25*Math.PI,false); // 圆圈的 水平位置 、 垂直位置 、 圆圈的半径、圆圈的弧度 , ctx.stroke(); //第二个圆圈 ctx.beginPath(); ctx.lineWidth=10; ctx.strokeStyle='RGB(35,24,22)'; ctx.arc(180,60,50,0*Math.PI,2*Math.PI,false); ctx.stroke(); //第三个圆圈 ctx.beginPath(); ctx.lineWidth=10; ctx.strokeStyle='RGB(248,3,0)'; ctx.arc(300,60,50,0*Math.PI,2*Math.PI,false); ctx.stroke(); //第四个圆圈 ctx.beginPath(); ctx.lineWidth=10; ctx.strokeStyle='RGB(223,252,22)'; ctx.arc(120,100,50,0*Math.PI,2*Math.PI,false); ctx.stroke(); //第五个圆圈 ctx.beginPath(); ctx.lineWidth=10; ctx.strokeStyle='RGB(37,254,75)'; ctx.arc(240,100,50,0*Math.PI,2*Math.PI,false); ctx.stroke(); </script>

May 25, 2019 · 1 min · jiezi

canvas-绘制

第一次接触canvas是做毕设的时候,在实现登录页时候用了canvas增添一些样式,那时候学的很浅显,是时候来认真了解一下这个canvas。(相关知识来源于MDN) canvas初了解canvas是个HTML5新增的标签,<canvas>标签很像<img>标签,而<canvas> 标签只有两个属性—— width 和 height。当没有设置宽度和高度的时候,canvas会初始化宽度为300像素和高度为150像素。需要注意的是,canvas的高宽不可以用css设置,那会扭曲的,而且一些老的浏览器版本都不支持canvas。 不支持咋么办?这非常简单:只需要在<canvas>标签中提供替换内容。不支持<canvas>的浏览器将会忽略容器并在其中渲染后备内容。而支持<canvas>的浏览器将会忽略在容器中包含的内容,并且只是正常渲染canvas。 你可以这样 <canvas id="canvas">alternative content for browsers without canvas support</canvas>也可以这样 <canvas id="clock" width="150" height="150"> <img src="替换内容.png" width="150" height="150" alt="替换图片"/></canvas>这里可以发现,以上两段代码中canvas的宽高属性设置,可以直接在标签中设置属性值,也可以在JS中设置,没错,canvas是基于JS绘制的。 基本用法先来代码,有个宏观了解 <canvas id="canvas"> alternative content for browsers without canvas support</canvas><script> var canvas = document.getElementById("canvas"); canvas.width = 1024; canvas.height = 500; var context = canvas.getContext("2d"); ...</script><canvas>元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。我们将会将注意力放在2D渲染上下文中。其他种类的上下文也许提供了不同种类的渲染方式;比如,WebGL使用了基于OpenGL ES的3D上下文。canvas起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。<canvas>元素有一个叫做 getContext()的方法,这个方法是用来获得渲染上下文和它的绘画功能。getContext()只有一个参数,上下文的格式。 JS部分代码的第一行通过使用 document.getElementById() 方法来为<canvas>元素得到DOM对象。一旦有了元素对象,你可以通过使用它的 getContext() 方法来访问绘画上下文。 至此,全都是一些前期准备工作,我们获取了DOM节点,获取到执行的上下文环境,现在就开始绘制图形。然而绘制之前还需要了解一个知识点,因为canvas的绘制都是基于坐标的,那就需要了解什么是画布栅格以及坐标空间。 canvas元素默认被网格所覆盖。通常来说网格中的一个单元相当于canvas元素中的一像素。栅格的起点为左上角(坐标为(0,0))。所有元素的位置都相对于原点定位。所以图中蓝色方形左上角的坐标为距离左边(X轴)x像素,距离上边(Y轴)y像素(坐标为(x,y))。具体如下图 好,让我们来绘制一个三角形,老样子先来JS代码,再具体分析 var canvas=document.getElementById("canvas");canvas.width=1024;canvas.height=500;var context=canvas.getContext("2d");context.moveTo(100,100); context.lineTo(400,400); context.lineTo(100,400);context.lineTo(100,100);context.lineWidth=2; context.strokeStyle="#058";context.fillStyle="rgb(2,100,30)"; context.stroke();context.fill();代码前四行是准备工作,就不赘述了。图形的绘制都是基于执行的上下文,我们绘制的三角形的起点使用了moveTo方法,定在(100,100)这个位置,lineTo()方法是绘制直线,也接受坐标值两个参数。例子中的意思就是从(100,100)到(400,400)画一条直线,再从(400,400)到(100,400),最终再回到(100,100),这样就形成一个回路构成一个三角形。 其实,说已经构成了三角形是不严谨的,我们只是先确定了坐标,还没有真正的完整绘制。这里可以设置线条的宽度和颜色,使用stroke() 方法实现图形轮廓的绘制,代码中还有一个fill() 方法,它是用于填充所绘制的图形,当然需要先设置一下填充的颜色。 通过canvas的简单操作,我们就很轻易的绘制了一个三角形。这就是canvas很基础的知识了,掌握这些就可以尝试着画一些矩形、多边形、不规则图形,圆、椭圆,目前似乎还很难实现,这就需要继续学习呀。 绘制矩形绘制一个矩形的边框:strokeRect(x,y,width,height) 绘制一个填充的矩形:fillRect(x,y,width,height) 清除指定矩形区域,让清除部分完全透明:clearRect(x,y,width,height) var canvas = document.getElementById("canvas");canvas.width = 500;canvas.height = 500;var ctx = canvas.getContext("2d");ctx.beginPath();ctx.strokeRect(50, 50, 50, 50);ctx.closePath();ctx.beginPath();ctx.fillRect(150, 50, 50, 50);ctx.closePath();ctx.beginPath();ctx.fillRect(250, 50, 50, 50);ctx.clearRect(265, 65, 20, 20);ctx.closePath();绘制出来的图形是这样的 ...

May 22, 2019 · 1 min · jiezi

Vue用Canvas生成二维码合成海报并解决清晰度问题

前言最近接到一个需求,用文字和图片合成一个海报,用于活动结尾页在微信长按分享,接到需求的第一时间,我就想到用 canvas 来画,但是看到 canvas 繁琐的绘制过程,不由得感到头大,后几经搜索,果然发现已经有人造好了轮子。此篇文章主要记录下实现过程,以及遇到的问题。依赖QRCode 这个依赖主要是用于移动端将 url 生成二维码,注意名字叫 qrcodejs2 别安装错 npm install qrcodejs2 --save import QRCode from "qrcodejs2"html2canvas 这个依赖主要是将当前 HTML 结构以及 css 样式转换为 canvas。比自己用 api 去画方便多了 Canvas2Image Github 这个依赖主要是将 canvas 转换为图片,实际上,Canvas2Image.js 也是基于 canvas.toDataURL 的封装,相比原生的 API 对于转为图片的功能上考虑更为具体(未压缩的包大小为 7.4KB ),适合项目使用。 主要思路将所有的海报结构都写在一个父级结构中,然后调用 html2canvas 转换为图片,创建 image,通过 css 层级和定位,将 image 置为最顶层,来实现长按分享 代码html 部分 <!--html 结构,具体 css 不写了--> <!-- 海报 html 元素 --> <div id="posterHtml" :style="{backgroundImage: 'url('+posterHtmlBg+')'}" > <div class="posterHtml"> <div class="posterklass">我是{{name}},邀请您:</div> <!-- 二维码 --> <div id="qrcodeImg" :if="postcode"></div> </div> </div> <!--image 即将要插入的位置,这样的结构处理,配合方法里面的 css 会自动置为最顶层--> <div id="myCanvas"></div> <!--提示用户的文案,其实也可以写在海报 html 结构中,通过 html2canvas 的 ignore 来忽略生成--> <span class="tip">长按保存该海报,邀请好友来测!</span> js 部分 ...

May 19, 2019 · 3 min · jiezi

canvas塔防游戏

声明该DEMO素材取自于猫狗大战,只用于学习交流目的使用,如果冒犯了权益请联系删除; 项目概述一款横板塔防游戏,制作的很粗糙,不使用任何现有框架,只是DEMO水平;目前实现了:关卡加载人物选择士兵点选攻击、点数计算暂停、over功能尚未解决士兵资源配置冷却和消耗英雄升级下一关功能特效bugBoss血量问题士兵位置计算问题项目预览试玩地址游戏地址项目截图

May 14, 2019 · 1 min · jiezi