理解 canvas 的 save() 和 restore()

本来是打算自己写 save() 和 restore(),但是发现一篇博客对于 save() 和 restore() 的理解很清晰易懂,就翻译过来了。 原文链接:https://html5.litten.com/understanding-save-and-restore-for-the-canvas-context/

--------------------------------- O(∩∩)O 以下是正文 O(∩∩)O-------------------------------**

首先,曾几何时我也无法理解 save() 和 restore() 的真正用意。其实呢,非常简单,这里有几个栗子能帮助大家更好的理解这两个方法。 <a name="PFcpq"></a>

我们先看下官方文档对 save 和 restore 的定义:

每个 canvas 的 context 都包含一个保存绘画状态的栈。以下内容都属于绘画状态:

  • 当前的 transformation matrix(变换矩阵)
  • 当前的裁剪区域
  • strokeStyle、fillStyle、globalAlpha、lineWidth、lineCap、lineJoin、miterLimit、shadowOffsetX、shadowOffsetY、shadowBlur、shadowColor、globalCompositeOperation、font、textAlign、textBaseline,这些属性的当前值

**

当前路径和当前的位图并不属于绘画状态。当前路径是永久的一直存在的,要清楚或重置的话,只能通过 beginPath 方法。而当前位图是属于 canvas 的属性,而不是 context 的。 **

context.save() 会把当前的状态推入栈中。 **

context.restore() 会从栈中取出最顶部的状态,context 就还原到取出的状态。

因为一个 canvas只能有一个 2d context, 所以 save 和 restore 被广泛用于各种情况。其中最普遍的就是用于 transformation(变换矩阵)。

<a name="mhgsv"></a>

一个栗子告诉你 save() 和 restore() 是如何在 transformations(多次矩阵变换) 中发挥作用的。

当我们使用 transformation (矩阵变换)时,整个 context 的坐标系统都会发生变换。通常变换之后,我们下一步可能会想让坐标系统还原成变换之前正常的状态。我们要再次通过 transformation 反着让坐标系统还原吗,这显然不是一个快速可靠的方法。此时,我们就可以在变换之前,先通过 save 把正常的坐标系通过状态保存下来入栈,做完矩阵变换后在通过 restore 从栈中取出我们之前保存的状态。我们看个栗子后应该能更加清晰一些。

刚开始我们通过 canvascontext.save() 保存下当前的绘画状态,并且拷贝一份当前状态推入绘画状态栈中。 可以看到下图是一个正常坐标系统,我们保存下来当前状态。 然后我们做矩阵变换,可以看到坐标系统是发生了变化的。 接着我们画完我们想要的形状并且填充一些颜色。 现在我们想画更多的形状在画布上,但是我们不想使用当前的矩阵变换,我们就通过 restore() 把最近的状态从绘画状态栈里取出来,也就是我们之前保存的有正常坐标系统的状态。那么当前的状态就变成我们 save 之前的状态了。 可以看到背景的网格恢复到了正常的坐标系统的样子,但是我们后画的形状并没有变化,因为我们保存的状态只保存了 transformation 的状态,我们并没有设置其他想要保存的状态。

<a name="RJDVz"></a>

我们再来一个更直观的栗子

完整代码及注释

现在我们来看一个更加直观的栗子,来说明绘画状态栈与 save() 和 restore() 是如何配合运作的。

<!doctype html>
<html>
<head>
<meta charset="UTF-8" />
<title>Canvas Test</title>
</head>
<body>
<header> </header>
<nav> </nav>
<section>

<div>
<canvas id="canvas" width="320" height="200">
This text is displayed if your browser does not support HTML5 Canvas.
</canvas>
</div>

<script type="text/javascript">
var canvas;
var ctx;

function init() {
canvas = document.getElementById("canvas");
ctx = canvas.getContext("2d");
draw();
}


function draw() {
// 画了一个 橘色的矩形,设置阴影
ctx.fillStyle = '#FA6900';
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(0,0,15,150);
  
// 把当前状态,我们叫他状态1(橘色、阴影相关属性)推入栈中
ctx.save();
  
// 画了一个 土黄色的矩形,设置阴影
ctx.fillStyle = '#E0E4CD';
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(30,0,30,150);
  
// 把当前状态,我们叫他状态2(土黄色,阴影相关属性)推入栈中
ctx.save();
  
// 画一个 翠绿色的矩形,设置阴影
ctx.fillStyle = '#A7DBD7';
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 15;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';
ctx.fillRect(90,0,45,150);
  
// 把当前状态,我们叫他状态3(翠绿色,阴影相关属性)推入栈中
ctx.save();
  
// 我们取出状态栈的顶部 状态3,那么当前绘制状态就是状态3了,即翠绿色和阴影相关属性
ctx.restore();
  
// 当前状态是状态3 画一个圆并填充,那么他是一个翠绿色带阴影的圆
ctx.beginPath();
ctx.arc(185, 75, 22, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

// 从状态栈中再取顶部状态出来,现在是状态2了,即 土黄色和阴影相关属性
ctx.restore();
  
// 当前状态是状态2的状态,画一个圆并填充,那么他是土黄色和阴影相关属性
ctx.beginPath();
ctx.arc(260, 75, 15, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();

// 从状态栈中再取一个顶部状态出来,现在是状态1了,即 橘色和阴影相关属性
ctx.restore();
  
// 当前状态是状态1的状态,画一个圆并填充,那么他是橘色和阴影相关属性
ctx.beginPath();
ctx.arc(305, 75, 8, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
}

init();
</script>
</section>
<aside> </aside>
<footer> </footer>
</body>
</html>

分段代码、效果图、注释具体说明

接下来我们分段代码并结合效果图来说明,会更加清晰。

/* 我们画了一个橘色的矩形,设置了阴影 */
ctx.fillStyle = '#FA6900'; 
ctx.shadowOffsetX = 5;
ctx.shadowOffsetY = 5;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';      
ctx.fillRect(0,0,15,150);
/* 把当前状态(fillStyle 橘色,阴影属性),推入到状态栈中,假设叫状态1 */
ctx.save();  

可以看到下图状态栈中多了一个状态,并且很贴心的颜色也与矩形一直,让人更好理解。

can1.gif
state1.gif
/* 然后我们画了一个 土黄色的矩形,设置了阴影 */
ctx.fillStyle = '#E0E4CD'; 
ctx.shadowOffsetX = 10;
ctx.shadowOffsetY = 10;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';      
ctx.fillRect(30,0,30,150);
/* 把当前状态(fillStyle 土黄色,阴影属性),推入到状态栈中,假设叫状态2 */
ctx.save(); 

我们看到状态栈中有两个状态了:状态1(橘色的)、状态2(土黄色的)

can2.gif
state2 (1).gif
/* 然后我们画了一个 翠绿色的矩形,设置了阴影 */
ctx.fillStyle = '#A7DBD7'; 
ctx.shadowOffsetX = 15;
ctx.shadowOffsetY = 15;
ctx.shadowBlur    = 4;
ctx.shadowColor   = 'rgba(204, 204, 204, 0.5)';      
ctx.fillRect(90,0,45,150);
/* 把当前状态(fillStyle 翠绿色,阴影属性),推入到状态栈中,假设叫状态3 */
ctx.save();

我们看到状态栈中有三个状态了:状态1(橘色的)、状态2(土黄色的)、状态3(翠绿色的)

can3.gif
state3 (1).gif
/*我们从状态栈顶部取出状态3,当前绘制状态就变成了状态3了(翠绿色的)*/
ctx.restore();
/*画一个翠绿色的圆*/
ctx.beginPath();
ctx.arc(185, 75, 22, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill(); 
can4.gif
state3 (1).gif
/*我们从状态栈顶部取出状态3,当前绘制状态就变成了状态2了(土黄色的)*/
ctx.restore();
/*画一个土黄色的圆*/
ctx.beginPath();
ctx.arc(260, 75, 15, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
can5.gif
state2.gif
/*我们从状态栈顶部取出状态3,当前绘制状态就变成了状态1了(橘色色的)*/
ctx.restore();
/*画一个橘色的圆*/
ctx.beginPath();
ctx.arc(305, 75, 8, 0, Math.PI*2, true);
ctx.closePath();
ctx.fill();
can6.gif
state1.gif

--------------------------------- O(∩∩)O 正文结束 O(∩∩)O--------------------------------**

这样下来是不是很简单?!希望我有翻译清楚。欢迎指正。

我是“南丘啊南丘”,希望大家在平凡的日子里好好学习,热爱生活,日日是好日!

点赞.gif
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342