• 关于 绘制直线 的搜索结果

问题

web页面画图 并保存到服务器端 时间紧 求现有的库

a123456678 2019-12-01 20:21:09 1025 浏览量 回答数 1

回答

opengl es画图形都是通过三角形来画的,当然还可以画直线和点 画图形的时候有两种方法:glDrawArrays( ) 和glDrawElements( )比如画一个由2个三角形组成的正方形,左上角坐标是l,t,右下角坐标是r,b 使用glDrawArrays绘制时,画2个三角形,需要这样传: (l,t),(r,t),(l,b) (r,t),(r,b),(l,b) 也就是说传的顶点数据就是按照顺时针或者逆时针排好顺序的,两个三角形的6个顶点而用glDrawElements画的话可以这样 float coord4={{l,t},{r,t},{r,b},{l,b}}; 绘制时用索引指定顶点顺序: 0,1,3 1,2,3也就是说glDrawArrays传输或指定的数据是最终的真实数据,在绘制时效能更好 而glDrawElements指定的是真实数据的调用索引,在内存/显存占用上更节省

爵霸 2019-12-02 02:46:13 0 浏览量 回答数 0

问题

网页显示源代码,用什么才能使代码可折叠?

杨冬芳 2019-12-01 19:52:14 1032 浏览量 回答数 0

试用中心

为您提供0门槛上云实践机会,企业用户最高免费12个月

问题

显示源代码,除了用highlight.js使代码高亮外,还要用什么才能使代码可折叠?

杨冬芳 2019-12-01 19:52:13 962 浏览量 回答数 1

回答

您好,可以参考如下代码: 1 import turtle #导入turtle模块 2 turtle.color("blue") #定义颜色 3 turtle.penup() #penup和pendown()设置画笔抬起或放下时是否绘制直线 4 turtle.goto(-110,-25) #初始位置以中心坐标为(0,0) 5 turtle.pendown() 6 turtle.circle(45) #绘制圆的半径 7 8 turtle.color("black") 9 turtle.penup()10 turtle.goto(0,-25)11 turtle.pendown()12 turtle.circle(45)13 14 turtle.color("red")15 turtle.penup()16 turtle.goto(110,-25)17 turtle.pendown()18 turtle.circle(45)19 20 turtle.color("yellow")21 turtle.penup()22 turtle.goto(-55,-75)23 turtle.pendown()24 turtle.circle(45)25 26 turtle.color("green")27 turtle.penup()28 turtle.goto(55,-75)29 turtle.pendown()30 turtle.circle(45)31 32 input()

bbskkb 2019-12-02 01:09:43 0 浏览量 回答数 0

回答

这个应该不难吧、四个点的X坐标是有规律的,Y坐标就通过seeBar获取转换一下就好了 然后把四个点连接起来,连接的话直接使用直线绘制好了 当拖动滑块的时候就进行重绘,我能想到的就这么多了、、######谢谢、 我已经搞定了 3Q药######楼主你好,请问你是怎么获取滑块的坐标的

kun坤 2020-06-14 13:36:58 0 浏览量 回答数 0

问题

Android 的Paint(画笔)及Canvas(画布)? 400 报错

爱吃鱼的程序员 2020-05-29 20:04:37 0 浏览量 回答数 1

问题

Android 的Paint(画笔)及Canvas(画布)? 400 报错

优选2 2020-06-05 16:56:03 0 浏览量 回答数 1

问题

如何检测图像Python上的开始/结束坐标和线的交点?

kun坤 2019-12-28 14:17:12 0 浏览量 回答数 0

回答

现在绘制3条Koch曲线就构成了Koch雪花。 kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length, n); 下一次的迭代同样要用到这样的方法,所以我们可以把这些代码放倒kochCurve函数中。于是kochCurve成了一个递归函数。 为了控制递归的深度,我们需要给kochCurve添加一个参数n。 void kochCurve(double x0, double y0, double angle, double length, int n) { if(n == 0) { double x1 = x0 + length*cos(angle); double y1 = y0 + length*sin(angle); glBegin(GL_LINES); glVertex2d(x0, y0); glVertex2d(x1, y1); glEnd(); } else { length /= 3; n --; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length, n); } } n是函数递归的层数,也是Koch曲线迭代的次数。 kochCurve(x0, y0, angle, length, 0)画出的是初始的图形 kochCurve(x0, y0, angle, length, 1)画出第一次迭代 下面是n = 2, 3, 4, 5的结果。 这里用线段的起点(x0, y0),方向(和正向x轴之间的角度)及长度来描述一条线段。 绘制一条从(-1.0, 0.0)到(1.0, 0.0)的Koch曲线用下面的方法: double length = 2.0; double angle = 0.0; double x0 = -1.0; double y0 = 0.0; kochCurve(x0, y0, angle, length); 一条Koch曲线是由4条比例缩写为整体1/3的Koch曲线组成。 length = 2.0/3; kochCurve(x0, y0, angle, length); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length); 这样就画出了第一次迭代的结果。 理论上说,Koch曲线是由无数无限短的线段组成。绘图的时候当然不可能画出无限短的线段。所以我们根据要求,用若干线段组成。 首先,绘制构造Koch曲线的初始图形,也就是一条直线。 void kochCurve(double x0, double y0, double angle, double length) { double x1 = x0 + length*cos(angle); double y1 = y0 + length*sin(angle); glBegin(GL_LINES); glVertex2d(x0, y0); glVertex2d(x1, y1); glEnd(); }

知与谁同 2019-12-02 01:25:16 0 浏览量 回答数 0

回答

现在绘制3条Koch曲线就构成了Koch雪花。 kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length, n); 下一次的迭代同样要用到这样的方法,所以我们可以把这些代码放倒kochCurve函数中。于是kochCurve成了一个递归函数。 为了控制递归的深度,我们需要给kochCurve添加一个参数n。 void kochCurve(double x0, double y0, double angle, double length, int n) { if(n == 0) { double x1 = x0 + length*cos(angle); double y1 = y0 + length*sin(angle); glBegin(GL_LINES); glVertex2d(x0, y0); glVertex2d(x1, y1); glEnd(); } else { length /= 3; n --; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length, n); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length, n); } } n是函数递归的层数,也是Koch曲线迭代的次数。 kochCurve(x0, y0, angle, length, 0)画出的是初始的图形 kochCurve(x0, y0, angle, length, 1)画出第一次迭代 下面是n = 2, 3, 4, 5的结果。 这里用线段的起点(x0, y0),方向(和正向x轴之间的角度)及长度来描述一条线段。 绘制一条从(-1.0, 0.0)到(1.0, 0.0)的Koch曲线用下面的方法: double length = 2.0; double angle = 0.0; double x0 = -1.0; double y0 = 0.0; kochCurve(x0, y0, angle, length); 一条Koch曲线是由4条比例缩写为整体1/3的Koch曲线组成。 length = 2.0/3; kochCurve(x0, y0, angle, length); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length); x0 += length*cos(angle); y0 += length*sin(angle); angle -= pi*2/3; kochCurve(x0, y0, angle, length); x0 += length*cos(angle); y0 += length*sin(angle); angle += pi/3; kochCurve(x0, y0, angle, length); 这样就画出了第一次迭代的结果。 理论上说,Koch曲线是由无数无限短的线段组成。绘图的时候当然不可能画出无限短的线段。所以我们根据要求,用若干线段组成。 首先,绘制构造Koch曲线的初始图形,也就是一条直线。 void kochCurve(double x0, double y0, double angle, double length) { double x1 = x0 + length*cos(angle); double y1 = y0 + length*sin(angle); glBegin(GL_LINES); glVertex2d(x0, y0); glVertex2d(x1, y1); glEnd(); }

玄学酱 2019-12-02 01:25:20 0 浏览量 回答数 0

问题

移植到 Direct3D 11:报错 

kun坤 2020-06-04 21:07:16 6 浏览量 回答数 1

问题

我如何从Excel导入的列表列表中进行绘图?

is大龙 2020-03-24 15:54:16 0 浏览量 回答数 1

回答

简介编辑Python科学计算Python科学计算VPython是一套简单易用的三维图形库,使用它可以快速创建三维场景和动画。和TVTK相比,它更适合于创建交互式的三维场景,而TVTK则更适合于对数据进行三维可视化。本章将通过几个实例介绍如何使用VPython制作实时、交互式的三维动画演示程序。 [1] 制作动画演示编辑用VPython制作动画的简单之处在于:只要在一个循环体中不断地修改场景中的各个模型以及照相机的各种属性,即可实现动画效果。与场景交互编辑为了和场景中的物体进行交互,VPython提供了如下方便实用的功能:键盘和鼠标事件的处理。控件窗口和4种控件(按钮、滚动条、开关及菜单),用于制作简单的用户界面。绘图窗口,用于绘制二维坐标图。由于篇幅受限,本书只介绍键盘和鼠标事件的处理,请读者参考VPython的文档和演示程序来自学其他部分的内容。响应键盘事件通过场景窗口对象的kb属性可以获得按键信息。kb.keys是窗口中等待处理的键盘事件的个数,调用kb.getkey()可以从键盘事件队列中获取一个待处理的事件。如果队列为空,getkey()将一直等待,直到产生键盘事件为止。getkey()的返回值是一个描述按键的字符串。下面是一个简单的键盘事件测试程序,可以用它查看各个按键的名称。响应鼠标事件鼠标射线和鼠标各个坐标属性之间的关系鼠标射线和鼠标各个坐标属性之间的关系鼠标事件和键盘事件类似,通过场景窗口对象的mouse属性进行鼠标事件的处理。鼠标的坐标是二维视图平面上的一个点,在三维空间中有一条直线上的点都将投影到这个位置,我们称此直线为鼠标射线。scene.mouse是一个mouse_object对象,下面列出它的属性和方法。为了便于理解,右图显示了鼠标射线和pos、pickpos等属性之间的关系。pos:鼠标在三维空间中的坐标,此坐标是鼠标射线与经过点scene.center且平行于屏幕的平面的交点。button:描述鼠标按键的字符串,值可以为None、'left'、'right'、'wheel'。此属性只有在产生事件时才不为None。pick:用鼠标选中的物体对象,与鼠标射线相交的第一个物体。pickpos:鼠标射线与pick物体的表面的交点坐标。camera:当前照相机的位置坐标,旋转或缩放场景时会发生变化。ray:从camera到pos的单位方向矢量,也就是鼠标射线的方向,它正好和窗口视图垂直。鼠标射线在三维空间中的参数方程为camera+t*ray,其中t是一个大于0的任意参数。alt、ctrl、shift:Alt、Ctrl、Shift三个按键的状态。project():计算鼠标射线与任意平面的交点,平面由表示法线方向的normal参数和表示平面上某点坐标的point参数指定。因为与屏幕平行的面的法线方向为scene. forward,所以下面语句的计算结果与scene.mouse.pos相同:scene.mouse.project(normal=scene.forward, point=scene.center)events:待处理的鼠标事件的数目。getevent():从鼠标事件队列中获取最早的鼠标事件。如果队列为空就一直等待事件的发生。getevent()返回的事件对象保存事件发生时的鼠标坐标,也具有上述的属性和方法。除此之外,事件对象还有press、click、drag、drop、release等属性,它们是描述鼠标按键的字符串,分别是产生“按下”、“单击”、“拖”、“放”、“松开”5种鼠标事件的按钮名称。用界面控制场景编辑用TraitsUI的界面控制Visual场景用TraitsUI的界面控制Visual场景VPython提供了一种控制窗口,可以放置按钮、开关及滚动条等简单控件,用以实时设置场景中的物体。但是这些控件不但功能有限,而且不是标准的界面控件,操作起来不是很方便。本节介绍如何使用TraitsUI制作一个能控制VPython场景的界面。VPython和TraitsUI各有自己的独立窗口,TraitsUI界面有自己的消息循环,而Visual窗口有自己的动画控制和消息处理循环。因此我们需要使用多线程或多进程方式,让这两个循环互不影响。下面是使用多线程实现TraitsUI控制场景的完整程序。创建复杂模型编辑VPython只提供了一些简单的立体几何形状,如果要创建复杂的物体,就需要用户自己编写程序,计算物体的多边形网格模型数据,并使用faces()将数据转换为模型进行显示。任何一个三维模型都可以用许多三角形的面来表示,对于每个三角形的每个顶点,我们需要计算如下数据:顶点的坐标:三个浮点数表示的三维坐标。顶点的法线方向:三个浮点数表示的三维方向矢量。顶点的颜色:三个浮点数表示的红、绿、蓝颜色分量。将保存上述数据的三个数组传递给faces()即可创建三维模型。对于一个有N个三角形的模型,每个数组的长度都是33N,也可以传递一个形状为(3*N,3)的二维数组。

xuning715 2019-12-02 01:10:23 0 浏览量 回答数 0

回答

前言 前两天在 echarts 上寻找灵感的时候,看到了很多有关地图类似的例子,地图定位等等,但是好像就是没有地铁线路图,就自己花了一些时间捣鼓出来了这个交互式地铁线路图的 Demo,地铁线路上的点是在网上随便下载了一个,这篇文章记录自己的一些收获(毕竟我还是个菜鸟)以及代码的实现,希望能够帮到一些朋友。当然,如果有什么意见的可以直接跟我说,大家一起交流才会进步。 效果图 地图稍微内容有点多,要全部展示,字显得有点小了,但是没关系,可以按照需求放大缩小,字体和绘制的内容并不会失真,毕竟都是用矢量绘制的~ 界面生成 底层的 div 是通过 ht.graph.GraphView 组件生成的,然后就可以利用 HT for Web 提供好的方法,调用 canvas 画笔随便绘制就好,先来看看怎么生成底层 div: var dm = new ht.DataModel();//数据容器 var gv = new ht.graph.GraphView(dm);//拓扑组件 gv.addToDOM();//将拓扑图组件添加进body中 addToDOM 函数声明如下: addToDOM = function(){ var self = this, view = self.getView(), style = view.style; document.body.appendChild(view); //将组件底层div添加到body中 style.left = '0';//默认组件是绝对定位,所以要设置位置 style.right = '0'; style.top = '0'; style.bottom = '0'; window.addEventListener('resize', function () { self.iv(); }, false); //窗口变化事件 } 现在我就可以在这个 div 上乱涂乱画了~首先我获取下载好的地铁线路图上的点,我将它们放在 subway.js 中,这个 js 文件全部都是下载的内容,我没有做其他的改动,主要是将这些点根据线路来分分配添加到数组中,比如: mark_Point13 = [];//线路 数组内包含线路的起点和终点坐标以及这条线路的名称 t_Point13 = [];//换成站点 数组内包含线路中的换乘站点坐标以及换成站点名称 n_Point13 = [];//小站点 数组内包含线路中的小站点坐标以及小站点名称 mark_Point13.push({ name: '十三号线', value: [113.4973,23.1095]}); mark_Point13.push({ name: '十三号线', value: [113.4155,23.1080]}); t_Point13.push({ name: '鱼珠', value: [113.41548,23.10547]}); n_Point13.push({ name: '裕丰围', value: [113.41548,23.10004]}); 接下来来描绘地铁线路,我声明了一个数组 lineNum,用来装 js 中所有的地铁线路的编号,以及一个 color 数组,用来装所有的地铁线的颜色,这些颜色的 index 与 lineNum 中地铁线编号的 index 是一一对应的: var lineNum = ['1', '2', '3', '30', '4', '5', '6', '7', '8', '9', '13', '14', '32', '18', '21', '22', '60', '68']; var color = ['#f1cd44', '#0060a1', '#ed9b4f', '#ed9b4f', '#007e3a', '#cb0447', '#7a1a57', '#18472c', '#008193', '#83c39e', '#8a8c29', '#82352b', '#82352b', '#09a1e0', '#8a8c29', '#82352b', '#b6d300', '#09a1e0']; 接着遍历 lineNum,将 lineNum 中的元素和颜色传到 createLine 函数中,根据这两个参数来绘制地铁线路以及配色,毕竟 js 文件中的命名方式也是有规律的,哪一条线路,则命名后面一定会加上对应的数字,所以我们只需要将字符串与这个编号结合即可获得 js 中对应的数组了: let lineName = 'Line' + num; let line = window[lineName]; createLine 的定义也非常简单,我的代码设置了不少的样式,所以看起来有点多。创建一个 ht.Polyline 管线,我们可以通过 polyline.addPoint() 函数向这个变量中添加具体的点,通过 setSegments 可以设置点的连接方式。 function createLine(num, color) {//绘制地图线 var polyline = new ht.Polyline();//多边形 管线 polyline.setTag(num);//设置节点tag标签,作为唯一标示 if(num === '68') polyline.setToolTip('A P M');//设置提示信息 else if(num === '60') polyline.setToolTip('G F'); else polyline.setToolTip('Line' + num); if(color) { polyline.s({//s 为 setStyle 的简写,设置样式 'shape.border.width': 0.4,//设置多边形的边框宽度 'shape.border.color': color,//设置多边形的边框颜色 'select.width': 0.2,//设置选中节点的边框宽度 'select.color': color//设置选中节点的边框颜色 }); } let lineName = 'Line' + num; let line = window[lineName]; for(let i = 0; i < line.length; i++) { for(let j = 0; j < line[i].coords.length; j++) { polyline.addPoint({x: line[i].coords[j][0]*300, y: -line[i].coords[j][1]*300}); if(num === '68'){//APM线(有两条,但是点是在同一个数组中的) if(i === 0 && j === 0) { polyline.setSegments([1]); } else if(i === 1 && j === 0) { polyline.getSegments().push(1); } else { polyline.getSegments().push(2); } } } } polyline.setLayer('0');//将线设置在下层,点设置在上层“top” dm.add(polyline);//将管线添加进数据容器中储存,不然这个管线属于“游离”状态,是不会显示在拓扑图上的 return polyline; } 上面代码中添加地铁线上的点有分为几种情况,是因为 js 中设置线的时候 Line68 有一个“跳跃”点的现象,所以我们必须“跳跃”过去,篇幅有限 Line68 数组具体的声明自行看 subway.js。 这里说明一点,如果用的是 addPoint 函数,不设置 segments 时,默认将添加进的点用直线连接,segments 的定义如下: 1: moveTo,占用 1 个点信息,代表一个新路径的起点 2: lineTo,占用 1 个点信息,代表从上次最后点连接到该点 3: quadraticCurveTo,占用 2 个点信息,第一个点作为曲线控制点,第二个点作为曲线结束点 4: bezierCurveTo,占用 3 个点信息,第一和第二个点作为曲线控制点,第三个点作为曲线结束点 5: closePath,不占用点信息,代表本次路径绘制结束,并闭合到路径的起始点 所以我们要做“跳跃”的行为设置 segments 为 1 即可。 最后绘制这些地铁线上的点,这个部分 subway.js 中也分离出来了,命名以“mark_Point”、“t_Point”以及“n_Point”开头,我在前面 js 的展示部分有对这些数组进行解释,大家动动中指划上去看看。 我们在这些点的位置添加 ht.Node 节点,当节点一添加进 dm 数据容器中时,就会在拓扑图上显示,当然,前提是这个拓扑图组件 gv 设置的数据容器是这个 dm。篇幅有限,添加地铁线上的点的代码部分我只展示添加“换乘站点”的点: var tName = 't_Point' + num; var tP = window[tName];//大站点 if(tP) {//有些线路没有“换乘站点” for(let i = 0; i < tP.length; i++) { let node = createNode(tP[i].name, tP[i].value, color[index]);//在获取的线路上的点的坐标位置添加节点 node.s({//设置节点的样式style 'label.scale': 0.05,//文本缩放,可以避免浏览器限制的最小字号问题 'label.font': 'bold 12px arial, sans-serif'//设置文本的font }); node.setSize(0.6, 0.6);//设置节点大小。由于js中每个点之间的偏移量太小,所以我不得不把节点设置小一些 node.setImage('images/旋转箭头.json');//设置节点的图片 node.a('alarmColor1', 'rgb(150, 150, 150)');//attr属性,可以在这里面设置任何的东西,alarmColor1是在上面设置的image的json中绑定的属性,具体参看 HT for Web 矢量手册(http://www.hightopo.com/guide/guide/core/vector/ht-vector-guide.html#ref_binding) node.a('alarmColor2', 'rgb(150, 150, 150)');//同上 node.a('tpNode', true);//这个属性设置只是为了用来区分“换乘站点”和“小站点”的,后面会用上 } }

问问小秘 2020-01-07 11:16:33 0 浏览量 回答数 0

回答

从效果图上我们还看到有几点重要信息: 点赞动画图片大小不一,运动轨迹也是随机的点赞动画图片都是先放大再匀速运动。快到顶部的时候,是渐渐消失。收到大量的点赞请求的时候,点赞动画不扎堆,井然有序持续出现。 那么如何实现这些要求呢?下面介绍两种实现方式来实现(底部附完整 demo): CSS3 实现 用 CSS3 实现动画,显然,我们想到的是用 animation 。 首先看下 animation 合并写法,具体含义就不解释了,如果需要可以自行了解。 animation: name duration timing-function delay iteration-count direction fill-mode play-state; 我们开始来一步一步实现。 Step 1: 固定区域,设置基本样式 首先,我们先准备 1 张点赞动画图片: 看一下 HTML 结构。外层一个结构固定整个显示动画区域的位置。这里在一个宽 100px ,高 200px 的 div 区域。 Step 5: 设置偏移 我们先定义帧动画:bubble_1 来执行偏移。图片开始放大阶段,这里没有设置偏移,保持中间原点不变。 在运行到 25% * 4 = 1s,即 1s之后,是向左偏移 -8px, 2s 的时候,向右偏移 8px,3s 的时候,向做偏移 15px ,最终向右偏移 15px。 大家可以想到了,这是定义的一个经典的左右摆动轨迹,“向左向右向左向右” 曲线摆动效果。 @keyframes bubble_1 { 0% { } 25% { margin-left:-8px; } 50% { margin-left:8px } 75% { margin-left:-15px } 100% { margin-left:15px } } 效果如下: Step 6: 补齐动画样式 这里预设了一种运行曲线轨迹,左右摆动的样式,我们在再预设更多种曲线,达到随机轨迹的目的。 比如 bubble_1 的左右偏移动画轨迹,我们可以修改偏移值,来达到不同的曲线轨迹。 Step 7: JS 操作随机增加节点样式 提供增加点赞的方法,随机将点赞的样式组合,然后渲染到节点上。 let praiseBubble = document.getElementById("praise_bubble"); let last = 0; function addPraise() { const b =Math.floor(Math.random() * 6) + 1; const bl =Math.floor(Math.random() * 11) + 1; // bl1~bl11 let d = document.createElement("div"); d.className = bubble b${b} bl${bl}; d.dataset.t = String(Date.now()); praiseBubble.appendChild(d); } setInterval(() => { addPraise(); },300) 在使用 CSS 来实现点赞的时候,通常还需要注意设置 bubble 的随机延时,比如: .bl2{ animation:bubble_2 $bubble_time linear .4s 1 forwards,bubble_big_2 $bubble_scale linear .4s 1 forwards,bubble_y $bubble_time linear .4s 1 forwards; } 这里如果是随机到 bl2,那么延时 0.4s 再运行,bl3 延时 0.6s …… 如果是批量更新到节点上,不设置延时的话,那就会扎堆出现。随机“ bl ”样式,就随机了延时,然后批量出现,都会自动错峰显示。当然,我们还需要增加当前用户手动点赞的动画,这个不需要延时。 另外,有可能同时别人下发了点赞 40 个,业务需求通常是希望这 40 个点赞气泡都能依次出现,制造持续的点赞氛围,否则下发量大又会扎堆显示了。 那么我们还需要分批打散点赞数量,比如一次点赞的时间($bubble_time)是 4s, 那么 4s 内,希望同时出现多少个点赞呢?比如是 10个,那么 40 个点赞,需要分批 4 次渲染。 window.requestAnimationFrame(() => { // 继续循环处理批次 render(); }); 另外还需要手动清除节点。以防节点过多带来的性能问题。如下是完整的效果图。 class ThumbsUpAni{ constructor(){ const canvas = document.getElementById('thumsCanvas'); this.context = canvas.getContext('2d')!; this.width = canvas.width; this.height = canvas.height; } } Step 2:创建渲染对象 实时渲染图片,使其变成一个连贯的动画,很重要的是:生成曲线轨迹。这个曲线轨迹需要是平滑的均匀曲线。 假如生成的曲线轨迹不平滑的话,那看到的效果就会太突兀,比如上一个是 10 px,下一个就是 -10px,那显然,动画就是忽左忽右左右闪烁了。 理想的轨迹是上一个位置是 10px,接下来是 9px,然后一直平滑到 -10px,这样的坐标点就是连贯的,看起来动画就是平滑运行。 随机平滑 X 轴偏移 如果要做到平滑曲线,其实可以使用我们再熟悉不过的正弦( Math.sin )函数来实现均匀曲线。 看下图的正弦曲线: 这是 Math.sin(0) 到 Math.sin(9) 的曲线图走势图,它是一个平滑的从正数到负数,然后再从负数到正数的曲线图,完全符合我们的需求,于是我们再需要生成一个随机比率值,让摆动幅度随机起来。 const angle = getRandom(2, 10); let ratio = getRandom(10,30)*((getRandom(0, 1) ? 1 : -1)); const getTranslateX = (diffTime) => { if (diffTime < this.scaleTime) {// 放大期间,不进行摇摆偏移 return basicX; } else { return basicX + ratio*Math.sin(angle*(diffTime - this.scaleTime)); } }; 复制代码scaleTime 是从开始放大到最终大小,用多长时间,这里我们设置 0.1,即总共运行时间前面的 10% 的时间,点赞图片逐步放大。 diffTime,是只从开始动画运行到当前时间过了多长时间了,为百分比。实际值是从 0 --》 1 逐步增大。 diffTime - scaleTime = 0 ~ 0.9, diffTime 为 0.4 的时候,说明是已经运行了 40% 的时间。 因为 Math.sin(0) 到 Math.sin(0.9) 曲线几乎是一个直线,所以不太符合摆动效果,从 Math.sin(0) 到 Math.sin(1.8) 开始有细微的变化,所以我们这里设置的 angle 最小值为 2。 这里设置角度系数 angle 最大为 10 ,从底部到顶部运行两个波峰。 当然如果运行距离再长一些,我们可以增大 angle 值,比如变成 3 个波峰(如果时间短,出现三个波峰,就会运行过快,有闪烁现象)。如下图: Y 轴偏移 这个容易理解,开始 diffTime 为 0 ,所以运行偏移从 this.height --> image.height / 2。即从最底部,运行到顶部留下,实际上我们在顶部会淡化隐藏。 const getTranslateY = (diffTime) => { return image.height / 2 + (this.height - image.height / 2) * (1-diffTime); }; 复制代码放大缩小 当运行时间 diffTime 小于设置的 scaleTime 的时候,按比例随着时间增大,scale 变大。超过设置的时间阈值,则返回最终大小。 const basicScale = [0.6, 0.9, 1.2][getRandom(0, 2)]; const getScale = (diffTime) => { if (diffTime < this.scaleTime) { return +((diffTime/ this.scaleTime).toFixed(2)) * basicScale; } else { return basicScale; } }; 复制代码淡出 同放大逻辑一致,只不过淡出是在运行快到最后的位置开始生效。 **const fadeOutStage = getRandom(14, 18) / 100; const getAlpha = (diffTime) => { let left = 1 - +diffTime; if (left > fadeOutStage) { return 1; } else { return 1 - +((fadeOutStage - left) / fadeOutStage).toFixed(2); } }; ** 实时绘制 创建完绘制对象之后,就可以实时绘制了,根据上述获取到的“偏移值”,“放大”和“淡出”值,然后实时绘制点赞图片的位置即可。 每个执行周期,都需要重新绘制 canvas 上的所有的动画图片位置,最终形成所有的点赞图片都在运动的效果。 createRender(){ return (diffTime) => { // 差值满了,即结束了 0 ---》 1 if(diffTime>=1) return true; context.save(); const scale = getScale(diffTime); const translateX = getTranslateX(diffTime); const translateY = getTranslateY(diffTime); context.translate(translateX, translateY); context.scale(scale, scale); context.globalAlpha = getAlpha(diffTime); // const rotate = getRotate(); // context.rotate(rotate * Math.PI / 180); context.drawImage( image, -image.width / 2, -image.height / 2, image.width, image.height ); context.restore(); }; } 这里绘制的图片是原图的 width 和 height。前面我们设置了 basiceScale,如果图片更大,我们可以把 scale 再变小即可。 const basicScale = [0.6, 0.9, 1.2][getRandom(0, 2)]; 实时绘制扫描器 开启实时绘制扫描器,将创建的渲染对象放入 renderList 数组,数组不为空,说明 canvas 上还有动画,就需要不停的去执行 scan,直到 canvas 上没有动画结束为止。 scan() { this.context.clearRect(0, 0, this.width, this.height); this.context.fillStyle = "#f4f4f4"; this.context.fillRect(0,0,200,400); let index = 0; let length = this.renderList.length; if (length > 0) { requestAnimationFrame(this.scan.bind(this)); } while (index < length) { const render = this.renderList[index]; if (!render || !render.render || render.render.call(null, (Date.now() - render.timestamp) / render.duration)) { // 结束了,删除该动画 this.renderList.splice(index, 1); length--; } else { // 当前动画未执行完成,continue index++; } } } 这里就是根据执行的时间来对比,判断动画执行到的位置了: diffTime = (Date.now() - render.timestamp) / render.duration 复制代码如果开始的时间戳是 10000,当前是100100,则说明已经运行了 100 毫秒了,如果动画本来需要执行 1000 毫秒,那么 diffTime = 0.1,代表动画已经运行了 10%。 增加动画 每点赞一次或者每接收到别人点赞一次,则调用一次 start 方法来生成渲染实例,放进渲染实例数组。如果当前扫描器未开启,则需要启动扫描器,这里使用了 scanning 变量,防止开启多个扫描器。 start() { const render = this.createRender(); const duration = getRandom(1500, 3000); this.renderList.push({ render, duration, timestamp: Date.now(), }); if (!this.scanning) { this.scanning = true; requestFrame(this.scan.bind(this)); } return this; } 这里开启定时器,记录定时器里面处理的 thumbsStart 的值,如果有新增点赞,且定时器还在运行,直接更新最后的 praiseLast 值,定时器会依次将点赞请求全部处理完。 定时器的延时时间 time 根据开启定时器的时候,需要渲染多少点赞动画来决定的,比如需要渲染 100 个点赞动画,我们将 100 个点赞动画分布在 5s 内渲染完。 对于热门直播,会同时渲染的动画很多,不会扎堆显示,且动画完全能衔接上,不停的冒泡点赞动画。对于冷门直播,有多余一个的点赞请求,我们能打散到 5s 内显示,也不会扎堆显示。 End 两种方式渲染点赞动画都已经完成,完整源码,源码戳这里 。 源码运行效果图: 再比较 这两种实现方式,都可以满足要求,那么到底哪种更优呢? 我们来看下两者的数据对比。以下为未开启硬件加速的对比,采用不间断疯狂渲染点赞动画的数据对比: 整体来说,差异如下: CSS3 实现简单Canvas 更灵活,操作更细腻CSS3 内存消耗比 Canvas 大,如果开启硬件加速,内存消耗更大一些。

剑曼红尘 2020-04-15 19:17:18 0 浏览量 回答数 0

问题

【精品问答】python技术1000问(1)

问问小秘 2019-12-01 21:57:48 448858 浏览量 回答数 12
阿里云大学 云服务器ECS com域名 网站域名whois查询 开发者平台 小程序定制 小程序开发 国内短信套餐包 开发者技术与产品 云数据库 图像识别 开发者问答 阿里云建站 阿里云备案 云市场 万网 阿里云帮助文档 免费套餐 开发者工具 云栖号物联网 小程序开发制作 视频内容分析 企业网站制作 视频集锦 代理记账服务 2020阿里巴巴研发效能峰会 企业建站模板 云效成长地图 高端建站 云栖号弹性计算 阿里云云栖号 云栖号案例 云栖号直播