解决 HTML Canvas 元素在高像素密度/高分辨率屏幕上显示模糊的问题

最近在使用 HTML Canvas 元素时发现通过 fillText() 绘制的文字明显发虚,像是以低分辨率渲染后放大所致,再仔细一检查,发现事实上包括 fillRect() 在内的其他图案也都存在相同的问题。

初步推测和显示设备使用了较高的 DPI 有关,随进行了一番搜索,在 Stackoverflow 、Medium 等站点都有一些提问和回答。然而意外的是,自己能找到的一些解答,至少在自己这边并不能解决问题。

经过摸索,找到了能够解决自己问题的办法,分享于此,以便后人。

以下为 Angular component 代码片段,语言为 TypeScript。

@ViewChild('canvas', { static: true }) canvas: ElementRef; // Canvas 元素
@ViewChild('canvasDiv', { static: true }) canvasDiv: ElementRef; // Canvas 元素的父元素,一个 div
private ctx: CanvasRenderingContext2D; // Canvas context

private dpiRatio: number; // DPI 缩放比例
private canvasDivWidth: number;  // 父元素宽度

……

this.dpiRatio = window.devicePixelRatio;

this.canvasDivWidth = this.canvasDiv.nativeElement.offsetWidth;

// 分别设置 canvas 的宽度和样式宽度,canvas 的显示尺寸将会减半
this.ctx = this.canvas.nativeElement.getContext('2d');
this.ctx.canvas.width = this.canvasWidth * this.dpiRatio;
this.ctx.canvas.style.width = this.canvasWidth + 'px';

// 在绘制时考虑缩放因素
this.ctx.font = 12 * this.dpiRatio + 'px Roboto';
this.ctx.fillText('HIDPI文字', x  * this.dpiRatio, y * this.dpiRatio);

一个悬而未决的问题是,canvas context 本身的 transfer 函数似乎不能做到整体的放大,因此这里作为临时方案暂时在绘制每一个图形时都乘以了缩放倍率。这里应该有更好的办法,不过限于时间,暂且搁置。

Angular 9+ 升级小记 —— 应付 MSAL-Angular 与 ngx-restangular 的兼容性问题

之前几次升级 Angular 版本的经历总体还算顺利,因此原本对 Angular 8 升级到 9 的过程也比较乐观。虽然 Angular 9 开始将默认启动新的 Angular Ivy,但考虑到已经正式发布数月,甚至 Android 10 都已问世,自己的项目又没有用特别冷门的依赖,依然没有担心会遇到问题。可惜墨菲定律无处不在,最后还是花了比想象中更多的时间完成升级。

为此,本文简单介绍自己在从 Angular 8 升级到 9 最终到 10 时遇到的问题及相应的解决方法,仅供参考。

首先,Angular 官方文档提供了详细的升级说明,建议在升级项目前首先通览相关文档,了解可能存在的问题。可以从下面的链接找到升级到 Angular 10 的最新信息。

https://angular.io/guide/updating-to-version-10

下面是自己在升级时实际操作的几个主要步骤:

  • 更新 Angular 8 相关依赖至最新版
    • ng update @angular/core@8 @angular/cli@8
    • ng build –prod –aot
    • 确认变更
  • 更新 @azure/msal-angular 至最新版 1.0.0
    • 安装 1.0.0 版并为新版的接口变化修改代码(1)
    • ng build –prod –aot
    • 确认变更
  • 升级至 Angular 9,检查并乎略一些依赖版本问题
    • ng update @angular/core@9 @angular/cli@9 –force(2)
    • ng build –prod –aot
    • 确认变更
    • 升级 Angular Material
      • ng update @angular/material@9
      • 检查 Angular Material 引用问题(3)
    • 增加 ngcc 作为 postinstall 脚本(4)
    • ng build –prod –aot
    • 确认变更
    • ng add @angular/localize (5)
    • ng serve –ssl 启动并检查应用基本功能
  • 升级至 Angular 10,检查并乎略一些依赖版本问题
    • ng update @angular/core @angular/cli –force
    • 临时手段解决 ngx-restangular 编译问题(6)
    • ng build –prod –aot
    • 确认变更
    • ng update @angular/material
    • ng build –prod –aot
    • 确认变更
    • ng serve –ssl 启动并检查应用基本功能
  • 大功告成

以下具体说明上述步骤中的一些关键操作。

(1)MSAL Angular 代码更改

在升级 Angular 9 之前,自己的项目使用 MSAL Angular 0.1.4 实现对 Microsoft 账户或 ADD 的验证。当时该库的文档和范例代码质量就差强人意,缺少说明和链接失效比比皆是。更重要的是,该库并不兼容 Angular Ivy,这也是没有更早升级 Angular 9 的原因之一。等着 MSAL Angular 花了几个月的时间终于正式发布 1.0.0 版本才开始升级,依然遇到几个问题:

  • 方法接口变化,getUser() 改为 getAccount(),displayableId 成员取消
  • 服务初始化方式变化,结构虽然较过去清晰,但文档和示例代码不足,甚至还有语法拼写错误
  • 请求验证成功后返回的 payload 格式发生变化,token 变量改为 rawIdToken
  • loginRedirect() 似乎不再正常工作,仍需进一步排查原因

无论如何,能够兼容 Angular 9 已经不易,总比 ngx-restangular 那样根本无法成功编译要好些。MSAL Angular 已经两个月没有新版本发布,不知今后的更新是否能解决一些问题。

(2)升级至 Angular 9

这步本身没有什么问题,不过需要检查一些报警的依赖版本冲突。自己的项目中主要是 tslib 已经到了 2.0 而 Angular 9 要求 1.10.0,以及 Angular http 停在了大版本7。确认没有问题后就以 –force 参数强制升级。

(3)检查 Angular Material 引用问题

新版本 Angular Material 要求每个引用都明确指定具体组件,如:

 import { MatSpinner } from '@angular/material/progress-spinner';

而不允许在一句引用中同时引入多个组件,如:

import { MatSpinner, MatSnackBar } from '@angular/material';

正常情况下 ng update 会自动更正代码,不过自己在操作时似乎遇到一些问题,没有顺利完成,只好收到批量做了一些处理。

(4)增加 ngcc 作为 postinstall 脚本

在 package.json 中增加 “postinstall”: “ngcc” 有助于避免可能的库兼容问题。参见 ngcc 的和 ModuleWithProviders 迁移的相关说明。

(5)增加 @angular/localize

如果项目中使用了 Angular 自带的 i18n 功能,可能需要执行 ng add @angular/localize 添加必要的依赖。不过这并非必要步骤,可以在遇到提示时才采取行动。

(6)解决 ngx-restangular 5.0.0 编译问题

很可惜,ngcc 不是银弹。至少,它无法解决 ngx-restangular 的兼容问题。

ngx-restangular 是自己项目中使用的主要第三方库之一,帮助使用 REST API 的使用。这个项目本身算不上太活跃,但基本功能还算充分。可惜的是,半年没有更新的它,果然无法在 Ivy 下正确编译,报错如下:

ERROR in node_modules/ngx-restangular/lib/ngx-restangular.module.d.ts:8:97 - error TS2314: Generic type 'ModuleWithProviders<T>' requires 1 type argument(s).

static forRoot(providers?: any[], configFunction?: (provider: any, ...arg: any[]) => void): ModuleWithProviders;

搜索尝试一些方法无果后,不得已手动修改库文件,增加类型 <RestangularModule> 在 node_modules/ngx-restangular/lib/ngx-restangular.module.d.ts 第八行末尾:

static forRoot(providers?: any[], configFunction?: (provider: any, ...arg: any[]) => void): ModuleWithProviders<RestangularModule>;

在 CI 管道中,则暂时每次都在 ng build 之前以 sed 命令完成代码更改:

sed -i 's/ModuleWithProviders;/ModuleWithProviders<RestangularModule>;/g' node_modules/ngx-restangular/lib/ngx-restangular.module.d.ts

于是,暂时绕过了编译错误。ngx-restangular 的功能本身倒似乎没有问题,可以正常工作。

经过功能确认和测试后,一个 Angular 8 应用终于顺利升级为了 Angular 10 应用。尽管并非一帆风顺,但 Ivy 确实明显提升了编译性能并减小了体积,总管没有白费功夫。如有需要,还可以参考 AngularGo GitHub 地址),了解升级过程中的配置与代码更改。

Angular on Azure Web App Service (IIS)

部署于 Azure App Service 的 Web App 事实上运行于 IIS (Internet Information Services),因此也可以通过 web.config 文件来配置它的行为。

由于 Angular 是一种 SPA Web 框架,需要对 IIS 做一些额外的 URL Rewrite 配置,才能在浏览器刷新页面时依然成功载入内容,参见以下代码。

规则 Index Rule 的作用是把所有匹配的 HTTP 请求全都重定向至 “/” 路径,之后 Angular 自己的路由(Routes)将会继续工作,完成内容的载入和跳转。可以参考此处了解如何实现 lazy loading 。

staticContent 节点则定义了允许的 mimeType,否则直接访问 contentType 为 application/json 等类型的请求将返回 404 Not Found。

AngularGo —— 一个开源 Angular SPA 模板

在接触 Angular 后,这些年工作业余也用 Angular 做了若干实际项目。不过,由于 Angular 在国内的流行度不高,各种原创内容和参考资料也相对较少。虽然网络上也能找到各种各样的技术文章,官方的文档也很全面,但总的来讲,信息还是有些分散。

很惭愧,几年里没有做什么特别有技术含量的工作。 只是提炼出一个很基础的模板,帮助开发者快速创建一个 SPA(Single Page Application)站点,或是供对 Android 感兴趣的读者了解 Android 的语法和基本的框架机制。该模板先分享于此 —— AngularGo on GitHub

该模板基于目前最新的 Angular 8,且会持续跟进更新。在 Angular CLI 自动创建的范例项目的基础上,目前版本的 AngularGo 还包含以下内容:

  • Angular 8 以及相关依赖的最新版本配置
  • 一种可能的源文件结构示例
  • 一种与 Cordova 共享代码库的可能方式
  • 基础 Angular 组件及依赖注入的使用范例
  • 基础 Angular Material 控件的使用范例
  • 支持桌面和移动设备的抽屉式侧滑菜单
  • 支持 lazy loading 的 app-routing 全局路由
  • 支持 authentication guard 的模块路由
  • 支持 bearer token 验证的 Restangular 初始化及 service 用例
  • 基于 HttpClient 的用户注册与登录 API 调用
  • 基于 scss 的 Angular Material 样式(尚未采用 BEM 命名规则)
  • Azure Application Insights 集成
  • 适用于 Windows server/ Azure App Service 的 web.config 配置

AngularGo 还很初步,很多细节因水平有限和时间限制写得也比较粗糙,想必会有其他优秀的开源模板提供了更好的实现。从某种意义上来讲,该模板一方面是对自己经验得一个整理,同时期望能起到一个抛砖引玉的作用。希望对读者有帮助,也欢迎批评指教。

两分钟了解 Node.js

本文粗略介绍 Node.js 特点与用途,以及意外地很多 Windows 用户不那么容易找到的使用 npm 的方法。

  • Node.js 是高速的服务器端单线程事件驱动异步非阻塞式 JavaScript 平台
  • 占用内存少,可无缝处理客户端与服务器端数据,适用于 SPA 或 WebSocket 应用
  • 异步处理使得连接大量客户端时性能依然较好,无需等待磁盘I/O等低速操作
  • 通过npm(node package manager)管理模块依赖

访问官方站点获取详细信息 https://nodejs.org/ 。

如无特殊需求,通过 .msi 安装包安装 Node.js 后在开始菜单启动 Node.js command prompt 即可执行命令,如,输入 npm install g @angular/cli 便能安装 Angular CLI。

 

Angular 快速入门

之前做过一些 AngularJS 的小型项目,用的工具和流程却大多是过时的一套,大半精力也仍花费在后台业务逻辑。时至今日虽然晚了一些,打算跟上时代,尝试一下 Angular 的开发。

有几个关注点:

  • 以 Angular 4 入手,系统学习 Angular 框架的概念与机制
  • 完全的前后端分离
  • 使用 Angular CLI 创建模板
  • 借助 VSTS 管理项目代码与发布
  • Angular 部分完全使用 VS Code 开发

Angular 4 并不是最新版本,大版本升级的周期是每6个月,但从 Angular 2 起每个大版本都能基本兼容,Angular 4 的文档也相对丰富,故选择从这里开始。完全前后端分离可以更高效分工,带来一系列好处,如提高团队合作效率。而 Angular CLI 几乎已经成为默认的选项,这次也打算应用。至于 VSTS,最近的项目经历感觉没有最大程度发挥其价值,所以希望在也与项目中进一步探索其可能性。VS Code 虽已成为自己的常用工具,但能否完全担当起开发之职,也会是这次的一个课题。

起动步骤极为简单。

在安装 Node.js 后,通过命令行安装 Angular CLI:

CD 定位至希望的文件夹后,新建 Angular 项目:

运行新建的项目,并以 open 选项打开浏览器页面,默认端口为4200:

此时,通过 VS Code 对代码做任何修改并保存后都会立即体现在程序上。

至此,尝试 Angular 的第一步已经踏出。今后将不定期更新心得感想。

JavaScript 避免 Ajax Get 方法因缓存而返回错误数据结果的问题

代码与范例:

方法4 – 页面 meta tag

说明:Ajax 调用存在缓存,再次以相同参数调用时可能会直接调取先前结果,造成结果错误。可以用以上四种方式回避该问题。

在 Windows 下安装与配置 Karma

1. 安装 Karma 与相关插件

karma 与 karma-chrome-launcher 将被安装至当前工作目录下的 node_modules 并以 devDependencies 形式保存于 package.json。这样一来,其他开发者只需通过 npm install 来获取这些已经安装的依赖。之后,通过 -g 指定在全局安装 CLI 以实现命令行操作。

2. 创建配置文件
在项目的测试用例文件夹下执行以下命令

3. 启动 Karma

或在配置文件路径下执行

4. 终止 Karma

AngularJS 中为 input 添加字母大小写不敏感正则表达式匹配验证

代码与范例:

说明:虽然包括 JavaScript 在内的正则表达式实现支持大小写不敏感匹配(i),但 HTML5 中,input 元素的 pattern 属性并不支持该标记位。为此,要实现该功能,必须借助额外的逻辑。如需显示错误信息,可为错误信息添加 ng-show=‘!isEmailValid’ 属性。

JavaScript 中设置与读取 Cookie

代码与范例:

说明:如果要设置过期,则需要为expires赋值。否则默认为 session cookie,可能在浏览器结束后被自动清除。