typescript中函数

by Changhui Xu

徐昌辉

如何在TypeScript中合成Canvas动画 (How to Compose Canvas Animations in TypeScript)

Today we are going to create a canvas animation with pretty blooming flowers, step by step. You can follow along by playing StackBlitz projects in this blog post, and you are welcome to check out the source code in this GitHub repo.

今天,我们要创建一个漂亮的鲜花盛开, 一步步画布动画。 您可以通过在此博客文章中玩StackBlitz项目来进行后续操作,也欢迎您在GitHub存储库中查看源代码。

In my recent blog post, I described a high level view of composing canvas animations using TypeScript. Here I will present a detailed process of how to model objects and how to animate them on canvas.

在最近的博客文章中 ,我描述了使用TypeScript组成画布动画的高级视图。 在这里,我将详细介绍如何对对象进行建模以及如何在画布上对其进行动画处理。

目录 (Table of Contents)

  • Draw Flowers

    画花

  • Animate Flowers

    动画花

  • Add Interactions to Animation

    将交互添加到动画

画花 (Draw Flowers)

First things first, we need to have a function to draw flowers on canvas. We can break the parts of a flower down into petals and center (pistil and stamen). The flower center can be abstracted as a circle filled with some color. The petals grow around the center, and they can be drawn by rotating canvas with a certain degree of symmetry.

首先,我们需要具有在画布上绘制花朵的功能。 我们可以将花朵的一部分分解为花瓣中心 (雌蕊和雄蕊)。 花中心可以抽象为一个充满某种颜色的圆圈。 花瓣围绕中心生长,可以通过旋转画布以一定程度的对称性来绘制它们。

Notice that the bold nouns (flower, petal, center) imply models in the code. We are going to define these models by identifying their properties.

注意,粗体名词( flower花瓣center )在代码中暗含模型 。 我们将通过识别它们的属性来定义这些模型。

Let’s first focus on drawing one petal with some abstractions. Inspired by this tutorial, we know that petal shape can be represented by two quadratic curves and two Bézier curves. And we can draw these curves using the quadraticCurveTo() and bezierCurveTo() methods in the HTML canvas API.

让我们首先集中精力绘制具有抽象的花瓣。 受本教程的启发,我们知道花瓣的形状可以用两条二次曲线和两条贝塞尔曲线来表示。 我们可以使用HTML canvas API中的quadraticCurveTo()bezierCurveTo()方法绘制这些曲线。

As shown in Figure 1 (1), a quadratic curve has a starting point, an end point, and one control point which determines the curve’s curvature. In Figure 1 (2), a Bézier curve has a starting point, an end point, and two control points.

如图1(1)所示,二次曲线具有起点,终点和一个确定曲线曲率的控制点。 在图1(2)中,贝塞尔曲线具有起点,终点和两个控制点。

In order to smoothly connect two curves (any two curves, either quadratic or Bézier, or other), we need to make sure that the connection point and the two nearby control points are on the same line, so that these two curves have the same curvature at the connection point.

为了平滑地连接两条曲线(任意两条曲线,无论是二次曲线还是贝塞尔曲线,或其他曲线),我们需要确保连接点和附近的两个控制点在同一条线上,以便这两条曲线具有相同的连接点的曲率

Figure 1 (3) shows a basic petal shape consisting of two quadratic curves (green) and two Bézier curve (blue). There are 4 red points representing petal vertices and 6 blue points representing control points of curves.

图1(3)显示了基本的花瓣形状,该形状由两个二次曲线(绿色)和两个贝塞尔曲线(蓝色)组成。 有4个红色点代表花瓣顶点,有6个蓝色点代表曲线的控制点。

The bottom red vertex is the flower’s center point and the top red vertex is the flower petal tip. The middle two red vertices represent the petal’s radius. And the angle between these two vertices against the center point is named petal angle span. You can play with this StackBlitz project about petal shape.

底部的红色顶点是花朵的中心点,顶部的红色顶点是花瓣的尖端。 中间的两个红色顶点代表花瓣的半径。 并将这两个顶点相对于中心点的角度称为花瓣角跨度。 您可以玩这个有关花瓣形状的StackBlitz项目 。

After the petal shape is defined, we can fill the shape with a color and get a petal, as shown in Figure 1 (4). With the information above, we are good to write up our first object model: Petal.

定义花瓣形状后,我们可以用一种颜色填充形状并得到一个花瓣,如图1(4)所示。 根据以上信息,我们很高兴编写出第一个对象模型: Petal

export class Petal {private readonly vertices: Point[];private readonly controlPoints: Point[][];constructor(public readonly centerPoint: Point,public readonly radius: number,public readonly tipSkewRatio: number,public readonly angleSpan: number,public readonly color: string) {this.vertices = this.getVertices();this.controlPoints = this.getControlPoints(this.vertices);}draw(context: CanvasRenderingContext2D) {// draw curves using vertices and controlPoints  }private getVertices() {// compute vertices' coordinates }private getControlPoints(vertices: Point[]): Point[][] {// compute control points' coordinates}
}

The auxiliary Point class in Petal is defined as follows. Coordinates are using integers (via Math.floor()) to save some computing power.

Petal的辅助Point类定义如下。 坐标使用整数(通过Math.floor() )来节省一些计算能力。

export class Point {constructor(public readonly x = 0, public readonly y = 0) {this.x = Math.floor(this.x);this.y = Math.floor(this.y);}
}

The representation of a Flower Center can be parameterized by its center point, circle radius, and color. Thus, the skeleton of the FlowerCenter class is as follows:

花中心的表示可以通过其中心点,圆半径和颜色进行参数设置。 因此, FlowerCenter类的框架如下:

export class FlowerCenter {constructor(private readonly centerPoint: Point,private readonly centerRadius: number,private readonly centerColor: string) {}draw(context: CanvasRenderingContext2D) {// draw the circle}
}

Since we have a petal and a flower center, we are ready to move forward to draw a flower, which contains a center circle and several petals with the same shape.

由于我们有一个花瓣和一个花中心,因此我们可以继续前进以绘制花朵,该花包含一个中心圆和几个具有相同形状的花瓣。

From an Object Oriented perspective, Flower can be constructed as new Flower(center: FlowerCenter, petals: Petal[]) or as new Flower(center: FlowerCenter, numberOfPetals: number, petal: Petal). I use the second way, because no array is needed for this scenario.

从面向对象的角度来看, Flower可以构造为new Flower(center: FlowerCenter, petals: Petal[]) ,也可以构造为new Flower(center: FlowerCenter, petals: Petal[]) new Flower(center: FlowerCenter, numberOfPetals: number, petal: Petal) 。 我使用第二种方法,因为这种情况下不需要数组。

In the constructor, you can add some validations to ensure data integrity. For example, throw an error if center.centerPoint doesn’t match petal.centerPoint.

在构造函数中,您可以添加一些验证以确保数据完整性。 例如,如果center.centerPointpetal.centerPoint不匹配,则抛出错误。

export class Flower {constructor(private readonly flowerCenter: FlowerCenter,private readonly numberOfPetals: number,private petal: Petal) {}draw(context: CanvasRenderingContext2D) {this.drawPetals(context);this.flowerCenter.draw(context);}private drawPetals(context: CanvasRenderingContext2D) {context.save();const cx = this.petal.centerPoint.x;const cy = this.petal.centerPoint.y;const rotateAngle = (2 * Math.PI) / this.numberOfPetals;for (let i = 0; i < this.numberOfPetals; i++) {context.translate(cx, cy);context.rotate(rotateAngle);context.translate(-cx, -cy);this.petal.draw(context);}context.restore();}
}

Pay attention to the drawPetals(context) method. Since the rotation is around the flower’s center point, we need to first translate the canvas to move the origin to flower center, then rotate the canvas. After rotation, we need to translate the canvas back so that the origin is the previous (0, 0).

请注意drawPetals(context)方法。 由于旋转是围绕花的中心点进行的,因此我们首先需要平移画布以将原点移动到花的中心,然后旋转画布。 旋转后,我们需要将画布向后平移,以便原点是前一个(0,0)。

Using these models (Flower, FlowerCenter, Petal), we are able to obtain a flower looks like Figure 1 (5). To make the flower more concrete, we add some shadow effects so that the flower looks like the one in Figure 1 (6). You can also play with the StackBlitz project below.

使用这些模型( FlowerFlowerCenterPetal ),我们可以获得图1(5)所示的花朵。 为了使花朵更具体,我们添加了一些阴影效果,使花朵看起来像图1中的花朵(6)。 您也可以使用下面的StackBlitz项目 。

动画花 (Animate Flowers)

In this section, we are going to animate the flower blooming process. We will simulate the blooming process as increasing petal radius as time passes. Figure 2 shows the final animation in which the flowers’ petals are expanding at each frame.

在本节中,我们将为花朵盛开过程制作动画。 随着时间的流逝,随着花瓣半径的增加,我们将模拟开花过程。 图2显示了最终的动画,其中花朵的花瓣在每一帧处都在扩展。

Before we do the actual animations, we may want to add some varieties to the flowers so that they are not boring. For example, we can generate random points on the canvas to scatter flowers, we can generate random shapes/sizes of flowers, and we can paint random colors for them. This kind of work usually is done in a specific service for the purpose of centralizing logic and reusing code. We then put randomization logic into the FlowerRandomizationService class.

在进行实际动画制作之前,我们可能需要为花朵添加一些变种,以免枯燥。 例如,我们可以在画布上生成随机点以散布花朵,可以生成花朵的随机形状/大小,还可以为其绘制随机颜色。 为了集中逻辑和重用代码,通常在特定的服务中完成这种工作。 然后,我们将随机化逻辑放入FlowerRandomizationService类中。

export class FlowerRandomizationService {constructor(){}getFlowerAt(point: Point): Flower {... // randomization}...  // other helper methods
}

Then we create a BloomingFlowers class to store an array of flowers generated by FlowerRandomizationService.

然后,我们创建一个BloomingFlowers类来存储由FlowerRandomizationService生成的花朵数组。

To make an animation, we define a method increasePetalRadius() in Flower class to update the flower objects. Then by calling window.requestAnimationFrame(() => this.animateFlowers()); in BloomingFlowers class, we schedule a redraw on canvas at each frame. And flowers are updated via flower.increasePetalRadius(); during each redraw. The code snippet below shows a bare minimum animation class.

为了制作动画,我们在Flower类中定义了一个increasePetalRadius()方法来更新花朵对象。 然后通过调用window.requestAnimationFrame(() => this.animateFlowers( )); in BloomingFlow ers类中,我们计划在每一帧的画布上进行重绘。 并通过ia flower.increasePetalRadius ()更新花朵。 在每次重绘期间。 下面的代码片段显示了最低限度的动画类。

export class BloomingFlowers {private readonly context: CanvasRenderingContext2D;private readonly canvasW: number;private readonly canvasH: number;private readonly flowers: Flower[] = [];constructor(private readonly canvas: HTMLCanvasElement,private readonly nFlowers: number = 30) {this.context = this.canvas.getContext('2d');this.canvasWidth = this.canvas.width;this.canvasHeight = this.canvas.height;this.getFlowers();}bloom() {window.requestAnimationFrame(() => this.animateFlowers());}private animateFlowers() {this.context.clearRect(0, 0, this.canvasW, this.canvasH);this.flowers.forEach(flower => {flower.increasePetalRadius();flower.draw(this.context);});window.requestAnimationFrame(() => this.animateFlowers());}private getFlowers() {for (let i = 0; i < this.nFlowers; i++) {const flower = ... // get a randomized flowerthis.flowers.push(flower);}}
}

Notice that the call back function in window.requestAnimationFrame(() => this.animateFlowers()); is using Arrow Function syntax, which is needed to preserve this context of the current object class.

注意, window.requestAnimationFrame(() => this.animateFlowers());回调函数window.requestAnimationFrame(() => this.animateFlowers()); 使用箭头函数语法,这是保留当前对象类的this上下文所必需的。

The above code snippet would result in the flower petal length increasing continually, because it doesn’t have a mechanism to stop that animation. In the demo code, I use a setTimeout() callback to terminate animation after 5 seconds. What if you want to recursively play an animation? A simple solution is demoed in the StackBlitz project below, which utilizes a setInterval() callback to replay the animation every 8 seconds.

上面的代码片段将导致花瓣长度不断增加,因为它没有停止该动画的机制。 在演示代码中,我使用setTimeout()回调在5秒后终止动画。 如果要递归播放动画怎么办? 下面的StackBlitz项目演示了一个简单的解决方案, 该项目利用setInterval()回调每8秒重播一次动画。

That’s cool. What else can we do on canvas animations?

这很酷。 我们还能在画布动画上做什么?

将交互添加到动画 (Add Interactions to Animation)

We want the canvas to be responsive to keyboard events, mouse events, or touch events. How? Right, add event listeners.

我们希望画布能够响应键盘事件,鼠标事件或触摸事件。 怎么样? 正确,添加事件监听器。

In this demo, we are going to create an interactive canvas. When the mouse clicks on the canvas, a flower blooms. When you click at another point on the canvas, another flower blooms. When holding the CTRL key and clicking, the canvas will clear. Figure 3 shows the final canvas animation.

在此演示中,我们将创建一个交互式画布。 当鼠标单击画布时,就会开花。 当您单击画布上的另一点时,另一朵花盛开。 按住CTRL键并单击时,画布将清除。 图3显示了最终的画布动画。

As usual, we create a class InteractiveFlowers to hold an array of flowers. The code snippet of the InteractiveFlowers class is as follows.

像往常一样,我们创建一个InteractiveFlowers类来容纳花朵数组。 InteractiveFlowers类的代码段如下。

export class InteractiveFlowers {private readonly context: CanvasRenderingContext2D;private readonly canvasW: number;private readonly canvasH: number;private flowers: Flower[] = [];private readonly randomizationService = new FlowerRandomizationService();private ctrlIsPressed = false;private mousePosition = new Point(-100, -100);constructor(private readonly canvas: HTMLCanvasElement) {this.context = this.canvas.getContext('2d');this.canvasW = this.canvas.width;this.canvasH = this.canvas.height;this.addInteractions();}clearCanvas() {this.flowers = [];this.context.clearRect(0, 0, this.canvasW, this.canvasH);}private animateFlowers() {if (this.flowers.every(f => f.stopChanging)) {return;}this.context.clearRect(0, 0, this.canvasW, this.canvasH);this.flowers.forEach(flower => {flower.increasePetalRadiusWithLimit();flower.draw(this.context);});window.requestAnimationFrame(() => this.animateFlowers());}private addInteractions() {this.canvas.addEventListener('click', e => {if (this.ctrlIsPressed) {this.clearCanvas();return;}this.calculateMouseRelativePositionInCanvas(e);const flower = this.randomizationService.getFlowerAt(this.mousePosition);this.flowers.push(flower);this.animateFlowers();});window.addEventListener('keydown', (e: KeyboardEvent) => {if (e.which === 17 || e.keyCode === 17) {this.ctrlIsPressed = true;}});window.addEventListener('keyup', () => {this.ctrlIsPressed = false;});}private calculateMouseRelativePositionInCanvas(e: MouseEvent) {this.mousePosition = new Point(e.clientX +(document.documentElement.scrollLeft || document.body.scrollLeft) -this.canvas.offsetLeft,e.clientY +(document.documentElement.scrollTop || document.body.scrollTop) -this.canvas.offsetTop);}
}

We add an event listener to track the mouse click events and mouse position(s). Every click will add a flower to the flowers array. Since we don’t want to let the flowers expand to infinity, we define a method increasePetalRadiusWithLimit() in the Flower class to increase the petal radius until an increment of 20. In this way, each flower will bloom by itself and will stop blooming after its petal radius has increased 20 units.

我们添加了一个事件侦听器,以跟踪鼠标单击事件和鼠标位置。 每次单击都会在花朵数组中添加花朵。 因为我们不想让花朵扩展到无限大,我们定义了一个方法increasePetalRadiusWithLimit()Flower类增加花瓣半径,直到20的增量这样,每朵花将绽放自己,并会停止绽放花瓣半径增加20个单位后

I set a private member stopChanging in flower to optimize the animation, so that the animation will stop when all flowers have finished blooming.

我在花中设置了一个私有成员stopChanging以优化动画,以便当所有花都开花完后动画将停止。

We can also listen to keyup/keydown events and add keyboard controls to the canvas. In this demo, the canvas content will be cleared when the user holds the CTRL key and clicks the mouse. The key press condition is tracked by the ctrlIsPressed field. Similarly, you can add other fields to track other keyboard events to facilitate granular controls on the canvas.

我们还可以侦听keyup / keydown事件,并将键盘控件添加到画布。 在此演示中,当用户按住CTRL键并单击鼠标时,画布内容将被清除。 按键状态由ctrlIsPressed字段跟踪。 同样,您可以添加其他字段来跟踪其他键盘事件,以方便在画布上进行精细控制。

Of course, the event listeners can be optimized using Observables, especially when you’re using Angular. You can play with the StackBlitz project below.

当然,可以使用Observables优化事件侦听器,尤其是在使用Angular时。 您可以使用下面的StackBlitz项目 。

What’s next? We can brush up the interactive flowers demo by adding some sound effects and some animation sprites. We can study how to make it run smoothly across all platforms and make a PWA or mobile app out of it.

下一步是什么? 我们可以通过添加一些声音效果和一些动画精灵来修饰交互式花朵演示。 我们可以研究如何使其在所有平台上平稳运行,并利用它来制作PWA或移动应用。

I hope this article adds some value to the topic of Canvas Animations. Again, the source code is in this GitHub repo and you can also play with this StackBlitz project and visit a demo site. Feel free to leave comments below. Thank you.

我希望本文能为“画布动画”主题增添一些价值。 同样,源代码位于该GitHub存储库中 ,您也可以使用此StackBlitz项目并访问演示站点 。 随时在下面发表评论。 谢谢。

Cheers!

干杯!

翻译自: https://www.freecodecamp.org/news/how-to-compose-canvas-animations-in-typescript-9368dfa29028/

typescript中函数

typescript中函数_如何在TypeScript中合成Canvas动画相关推荐

  1. python 参数个数 同名函数_如何在python中编写不同参数的同名方法

    我在Java背景下学习Python(3.x). 我有一个python程序,我在其中创建一个personObject并将其添加到列表中.p = Person("John") list ...

  2. python的loc函数_如何在pandas中使用loc、iloc函数进行数据索引(入门篇)

    在数据分析过程中,很多时候我们需要从数据表中提取出我们需要的部分,而这么做的前提是我们需要先索引出这一部分数据.今天我们就来探索一下,如何在pandas中使用loc函数和iloc函数索引数据. 今天我 ...

  3. bash中的grep函数_如何在Bash中编写函数

    bash中的grep函数 在编程时,实际上是在定义要由计算机执行的过程或例程 . 一个简单的类比将计算机编程与烤面包进行比较:您一次列出了要设置工作环境的成分,然后列出了最终要面包所必须采取的步骤. ...

  4. mysql节假日函数_如何在MySQL中计算不包括周末和节假日的日期差

    我需要计算两个日期之间的天数(工作日),不包括周末(最重要)和假期 SELECT DATEDIFF(end_date, start_date) from accounts 但是,我不知道该如何在MyS ...

  5. iserror 函数_如何在Excel中使用CLEAN,FIND和ISERROR工作表函数

    iserror 函数 Excel工作表中提供了很多功能,因此用户经常坚持使用最常用的功能,直到他们开始探索尝试找到问题的解决方案为止. 有时候,甚至没有想到可能有一个功能可以解决该问题. 因此,我决定 ...

  6. python的matplotlib库内的函数_如何在matplotlib中找到函数下面的区域?

    我是python和matplotlib库的新手,我试图在绘图中得到函数行下方的区域.我有一个变量a&amp:b,它在我的绘图中移动一个矩形.我也许可以使用原始数学来解决这个问题,但我想知道是否 ...

  7. python怎么创建函数_如何在python中创建自己的map()函数

    调用函数时,请使用星号*: def mapper(func, *sequences): result = [] if len(sequences) > 0: minl = min(len(sub ...

  8. python类中函数_如何在Python类中使用模块函数

    参见英文答案 > How do you call a private module function from inside a class?                           ...

  9. matlab figure函数_如何在Matlab中使用GUI做一个简易音乐播放器? ---- (六)控件间的数据传递...

    我纠结了两个星期是否要写这一章-最后决定还是要写一章收尾,来解释其中的控件间的数据传递问题. 在前五篇中,如果有童鞋跟上了我的思路或者做完了这样一个gui,会发现还有一个一直避开的遗留问题,就是将歌曲 ...

最新文章

  1. ViewState与Session 的重要区别
  2. 扬帆起航 继续前行1 nginx+lua+template+cache
  3. stanford coursera 机器学习编程作业 exercise 3(逻辑回归实现多分类问题)
  4. IDEA 运行键是灰色
  5. 求生之路 服务器优化参数,《求生之路2》服务器及网络参数优化指南
  6. 《Python Cookbook 3rd》笔记(4.4):实现迭代器协议
  7. javascript中addEventListener与removeEventListener
  8. 巧用margin/padidng的百分比值占位,避免闪烁
  9. java访问修饰符_Java访问修饰符
  10. 学习Java适合参加哪些工作?Java需要掌握的技术
  11. mac osx 系统 brew install hadoop 安装指南
  12. 关于ctf竞赛训练 积累的资料
  13. 惠普微型计算机怎么装机,台式小机惠普电脑怎么装系统
  14. python中双重循环_python中双循环
  15. 图片怎么无损放大?不影响清晰度这么做
  16. 普通人学python有意义吗_普通人学python有什么用
  17. 2020年一级计算机考试试题,2020年2016计算机一级考试考点试题
  18. java工作中最有成就感的事_工作中最有成就感的事
  19. [codeforces 1333A] Little Artem 读懂题+找规律+多举例
  20. ORA-01031: insufficient privileges报错如何处理

热门文章

  1. C语言课程设计:医院管理系统
  2. TYPE-C手机如何同时充电不影响传输USB2.0数据功能
  3. STM32-F407入门学习专题(四) STM32外设之USART
  4. 解决Android7.0以上读取不到本地文件的问题
  5. SQL截取字符串,SQL分割字符串函数,SQL字符串按指定的字符拆分
  6. sql字符串截取oracle,SQL Sever和Oracle截取字符串分析
  7. 五分钟理解原码补码反码和移码
  8. http://39.98.219.132 题库标准答案(题库序号:1890)之阿里巴巴与四十大盗(ali)
  9. CodeBlock 基本使用 与 设置断点和单步调试方法 与生活标准普尔图
  10. 【Code pratice】——排他平方数