Hi there 👋

There is nothing~

倒腾一个 Canvas 引擎

最近对 canvas 绘图产生了点兴趣,工作中偶尔会用到一点 canvas,但是不多。空闲时间折腾了点东西,顺便写个笔记记录一下,在这之前并没有了解过 canvas 引擎相关的技术原理,只是工作中接触过 pixi.js 的部分 API。 所以这里纯粹是个人的折腾笔记。 一、Canvas 知识要点 canvas 是 HTML5 的一个特性,提供了一系列 Javascript 接口来进行图形的绘制,它除了支持 js 2d 的绘制,还支持 WebGL 3D 渲染。想要 canvas 用的溜,基础知识得搞定,有几个点我前前后后折腾过几次,总是记不住,或许写下来,就变得通透了。 模糊问题 如果是第一次开发 canvas ,基本上都会遇到图像模糊的问题,比如下图中的圆边缘看起来有些模糊 这个模糊问题通常是由于设备的 DPR 大于 1 导致的,在 DPR = 1 的设备中,1个逻辑像素由一个物理像素绘制,但是 DPR = 2 的设备中,1个逻辑像素将绘制到2个物理像素中,相当于把图形放大了一倍,所以看起来会模糊。 要解决这个问题,了解了 DPR 后,还需要了解两个知识点:画布宽高和样式宽高。 画布宽高为 canvas 标签中设置的宽高,如 <canvas width=“600” height=“300></canvas> ,样式宽高则是 css 样式中指定的宽高。 画布宽高定义了画布的真实尺寸,而样式宽高则是画布最终展现出来的尺寸,两者如果比例不一致,则会出现图像拉伸变形的情况。 前面说的模糊问题可以理解为图形被放大了 DPR 倍,那缩小回来即可,先将画布尺寸设置为样式尺寸的 DPR 倍 此时可以看到图形已经变得清晰了,但是位置和大小似乎和预期的不一致,解决这个问题,还需要设置一个缩放比例 ctx.scale(DPR, DPR) 模糊的问题到这里大概可以告一段落了,至少知道了该如何解决。 动画 canvas 的动画实现为帧动画,在每一帧中都需要去重绘整个画布,当然也可以设计局部重绘的方案,不过局部重绘在进行 diff 的阶段的开销很可能会大于全量重绘,复杂度也会提升几个量级。...

December 11, 2022 · 8 min · 1620 words · Johnson

Canvas 与视频处理

canvas 是一个可以使用 Javascript 来绘制内容的 HTML 元素。相信大多数人都对 canvas 有一些了解,我们经常用它开发一些动画,也有人用它来开发游戏、画板等应用,或者用来做一些简单的图像处理。 除了这些场景外,还可以用它来进行视频处理,视频就是图像集合按照时间顺序排列起来的,canvas 既然能处理图像,那肯定也能处理视频。 canvas 的基本用法 要在 canvas 中绘制内容,我们得先有一个 canvas,然后在 js 中获取 canvas 的上下文并使用上下文提供的 API 进行绘制 <canvas id="canvas" width="1280" height="720" style="width: 640px; height: 360px; border: 1px solid #ddd"> 很遗憾,你的浏览器不支持 canvas </canvas> <script> const canvas = document.getElementById('canvas'); const ctx = canvas.getContext('2d'); // 获取上下文 ctx.scale(2, 2); ctx.fillStyle = 'red'; // 设置填充颜色 ctx.beginPath(); // 开始一个路径 ctx.arc(50, 50, 20, 0, Math.PI * 2); // 圈出一个圆形区域 ctx.fill(); // 填充这个圆形 ctx....

April 26, 2022 · 3 min · 490 words · Johnson

让你喜欢的音乐动起来🎵

我们在听音乐的时候,可能会注意到音乐播放器里面总是会有各种各样,跟随着音乐节拍律动的特效。但是有没有思考过它是如何实现的呢?今天就来研究研究这个主题——音频可视化。 音频可视化的应用非常广泛,凡是将音频的信息通过视觉方式呈现出来的,都是音频可视化。比如我们使用录音软件时,呈现的曲线 或者测试设备麦克风🎤的时候显示的音量 甚至一些大型的音乐喷泉,舞台灯光效果等都可以算是音频可视化。 web 音频可视化依赖于 web audio 提供的 api,我在《Web Audio 概览》中已经分享过常用 api 的概念和用法,这篇文章就不再赘述。 声音信号 在写代码之前,我们先了解一下声音信号,以及可视化依赖的基础理论知识。 我们都知道,声音是通过物体振动产生的,它在空气中以波的形式传播,也称为声波。声音有三个要素:响度、音调、音色。 原始的声音信号可以看作是一个连续的信号,我们可以通过一个余弦函数来研究声音信号。下面是余弦函数 y = cos(x) 的时域图像。 上面的图像中,标出了周期和振幅,它们分别对应了三要素中的音调和响度: 周期(通常转换成频率使用)越短,频率越高则音调越高 振幅越大,响度就越大 还有一个音色,从时域分析(后面会从频域分析),它与波形有关。比如下面这个函数,他的波形和前一个看起来不太一样,对应的播放出来音色也会有区别。 声音信号的三个要素都携带了特定的信息,而当我们听音乐的时候,优美的旋律通常都是音调的组合、变化,所以对于音乐来说,声音信号的频率携带了音乐的关键信息。 所以在音频可视化的应用场景中,以频率的可视化居多,随着音乐节奏律动的效果也大都是基于声音的频率制作的。这篇文章也主要基于频率来介绍音频可视化。 那么我们应该如何使用这个频率呢?上面的图例是一个基本的模型,他只有一个频率,仅依靠这个数据是无法实现丰富的动效的,而声音的波形也会非常复杂,并且随时间变化。这时候就需要使用傅立叶变换了。 傅立叶变换 对于傅立叶变换,有兴趣的可以深入学习,这里我们只需要知道它是做什么的即可。 傅立叶变换(Fourier transform)是一种线性积分变换,用于信号在时域时域(或空域)和频域之间的变换,在(物理学)和(工程学)中有许多应用。 下面的图片可以看到,一个方波信号被拆分成多个正弦信号的叠加,这实际上涉及到了傅立叶变换的前置知识——傅里叶级数。 🔼图片来自:维基百科 上面引用了维基百科的例子,同时我找了一个静态的图片来理解傅立叶变换。它的作用就是通过一系列数学变换,得到一个函数的频域图像(频谱),频域图像即以频率为横坐标,系数为纵坐标的图形。 🔼图片来自:维基百科 通过傅立叶变换,我们就可以获得声音信号中包含的不同成分(谐波分量)的频率,也就是频谱,然后就可以使用频谱来制作动效了。 扩展:前面说到的音色,从频域分析,除了基波频率外,其它谐波分量不同,导致了音色的差异。它反映到时域就是波的形状差别。 获取音频的频谱 大概了解了傅立叶变换后,我们来看一下如何获取一个音频的频谱数据。web audio 提供了一个 AnalyserNode,它是 AudioNode 中的一种,我们可以使用 audioContext.createAnalyser() 来创建这个节点。 const ac = new AudioContext(); // 创建音频分析节点 const analyser = ac.createAnalyser(); analyser.fftSize = 2048; // 使用正弦波做示例 const source = ac.createOscillator(); source.type = 'sine' source....

December 21, 2021 · 2 min · 402 words · Johnson

乱七八糟的图标

图标(icon)是一个应用中非常重要的角色。在应用内设置了合适的图标,可以极大的丰富用户界面,提升用户体验。 在前端业务开发过程中,几乎每天都会接触到图标,如果项目使用了组件库,那么通常是直接使用组件库提供的Icon组件;或者是使用UI设计师给的图标;又或者自己封装一个Icon组件。 最近在研究组件库,看到Icon组件的时候,发现并没有想象中那么简单。于是仔细研究了一下图标,希望你看完这篇文章后,不再为图标而烦恼。 图标类型 图标是一个统称,它包含了不同的格式或实现方式。图标不一定是一个图片,比如字体图标(Iconfont)。以图标的格式或实现方式可以分为一下几种: 图片图标:png、jpg等图片格式,栅格化图标 SVG图标:SVG文档,矢量化图标 字体图标:使用字体来渲染图标,矢量化图标 图片图标在使用上和图片无异,在过去的网页开发中是非常常用的图标格式,通常会使用雪碧图进行加载优化。 SVG图标是现在最常见的图标形式之一,因为它可以自由定制,也能很方便的导出为图片格式。 字体图标则是将图标以字体的形式渲染出来,它也可以使用SVG图标来制作。 在这里我们主要研究SVG图标和字体图标。 字体图标原理 字体图标相对比较特殊,这里简单介绍一下它的原理。浏览器渲染文字的时候,会先将其转为unicode编码,然后去匹配对应的字体文件并将其渲染到屏幕上。基于这个原理,我们便能制作一个特殊的字体,渲染的是一个图形,而不是一个文字。 浏览器有一些默认支持的字体图标,它是一组unicode字符集,完整列表可访问此链接。下面是一个示例,直接在HTML中输入unicode编码,就能显示对应的字体图标。支持十进制和十六进制两种格式。下面是一个简单的示例。 <p>Icon: &#9733;</p> <p>Icon: &#x2605;</p> 除了直接在标签中使用,我们也可以将unicode通过css代码放到content属性中,font-awesome等多数字体图标库都是基于此方式实现的。 <p> Icon: <i class="icon-star"></i> </p> .icon-star::before { content: "\2605"; } 在content属性中的语法是 “\ + 十六进制代码”,与HTML中有点区别。 图标尺寸 图标尺寸通常不是固定的,在不同的位置需要设置不同的大小。我们在设置尺寸时一般都会使用 width 和 height 来设置宽高。但是对于图标来说,经常需要与文字对齐,使用宽高来设置尺寸会比较麻烦。因此我们可以使用另一种方式来设置图标的尺寸—— font-size 。 使用font-size设置尺寸 使用 font-size 来设置尺寸属于css的一个技巧,其原理是基于css中的 em 单位,它是一个动态的单位,真实大小取决于 font-size 的值。因此我们可以将需要定制尺寸的地方,按比例替换为 em 单位,理论上就能使用 font-size 来控制尺寸。比如下面的代码,我们尝试使用 font-size 来控制一个 div 的大小。 // <div class="box"></div> .box { width: 1em; height: 1em; background: red; } 此时我们去设置 div 的 font-size ,便可以改变它的尺寸。下图左边是默认(16px)尺寸,右边的 font-size 为32px。...

December 20, 2021 · 2 min · 393 words · Johnson

Typescript 面向对象程序设计

众所周知,JavaScript是一门面向对象编程语言。大家或多或少都学习过一些JavaScript面向对象相关的知识,比如构造函数、实例化、继承等。 面向对象是一种程序编程范式,也是一种程序设计思想。通过对业务场景的进行抽象、建模来设计逻辑清晰、易维护、可扩展的应用程序。 本篇文章不讨论JavaScript面向对象的具体技术实现方式,比如:继承的实现方式,实例化的方法等。而是基于typescript来学习面向对象的程序设计思想。 Why typescript 因为身为前端,就应该使用typescript,并且语法上与Java比较接近,容易理解。可以套用绝大部分在Java中的最佳实践。 面向对象的设计思想,其应用不局限于具体的语言形式,只要是支持面向对象编程的语言,都能够使用面向对象的思想去设计程序。 三大特性 面向对象程序设计有三大特性,分别是封装、继承、多态。这三个特性都是抽象概念,依赖于具体的技术实现。 本文主要就是围绕三大特性,结合简单的例子来讲述面向对象编程。 封装 封装的概念大家都很熟悉,就是把相互关联的一些属性、方法等放到一起统一管理,再根据需求,决定对外部暴露哪些属性、方法。封装通常会使用到类(class)来实现,结合访问控制关键字来精确控制成员属性的可见性。 常用的访问控制关键字有:private、protected、public、static、readonly。在设计类的时候,应该尽可能的将可见性控制在最小的范围,没有必要暴露给外加的数据,就是用private。 在面向对象思想中,类与类之间依赖接口(Interface)进行交互。我们在使用一个对象的时候,无需关心这个对象是个什么东西,只要它提供(实现)了我们需要使用的接口,那么我们就能使用它。 思考一个场景:世界上的汽车种类数不胜数,不同的品牌、外观、性能、体积,我们只要学会了开车,无论是什么品牌、任何形状的汽车,都能开走,因为它有一套共同的结构——方向盘、刹车、油门。 如果我们把汽车抽象成一个类,那么提供给驾驶员操作的方法、属性就是汽车提供给驾驶员的接口,驾驶员只要知道如何使用这些接口就能把车开走,无论车是什么车。 我们可以简单写个代码 class Cayenne implements Drivable { startEngine(key: Key): boolean { console.log('启动引擎'); return true; } accelerate(force: number): void { console.log('踩油门,力度:' + force * 100 + '%'); } brake(force: number): void { console.log('踩刹车,力度:' + force * 100 + '%'); } turnTo(angle: number): void { console.log('转向:' + angle + '度'); } } interface Drivable { startEngine(key: Key): boolean; // 启动 accelerate(force: number): void; // 加速 brake(force: number): void; // 制动 turnTo(angle: number): void; // 转向 } class Driver { drive(car: Drivable) { if (car....

September 15, 2021 · 3 min · 459 words · Johnson