webassembly 的那些事

2018/01/23 · JavaScript
· webassembly

原文出处: 刘艳   

简介

JS于1995年问世,设计的初衷不是为了执行起来快。直到08年性能大战中,许多浏览器引入了即时编译
JIT(just-in-time编译器),JavaScript 代码的运行渐渐变快。正是由于这些 JIT
的引入,使得 JavaScript 的性能达到了一个转折点,JS 代码执行速度快了 20 —
50倍。

JIT 是使 JavaScript 运行更快的一种手段,通过监视代码的运行状态,把 hot
代码(重复执行多次的代码)进行优化。通过这种方式,可以使 JavaScript
应用的性能提升很多倍。

更多JIT工作原理,有兴趣请移步:)

 

随着性能的提升,JavaScript
可以应用到以前根本没有想到过的领域,比如用于后端开发的
Node.js。性能的提升使得 JavaScript 的应用范围得到很大的扩展。

JavaScript的无类型是JavaScript引擎的性能瓶颈之一,在过去几年,我们看到越来越多的项目问世,它们试图通过开发编译程序,将其他语言代码转化为
JavaScript,以此让开发者克服 JavaScript
自身存在的一些短板。其中一些项目专注于给编程语言增加新的功能,比如微软的
TypeScript 和 Google 的
Dart,【设计一门新的强类型语言并强制开发者进行类型指定】或是加快
JavaScript 的执行速度,例如 Mozilla 的 asm.js
项目和Google的PNaCI【给现有的JavaScript加上变量类型】。

现在通过
WebAssembly,我们很有可能正处于第二个拐点。图片 1

 

什么是webAssembly?

WebAssembly是一种新的适合于编译到Web的,可移植的,大小和加载时间高效的格式,是一种新的字节码格式。它的缩写是”.wasm”,.wasm
为文件名后缀,是一种新的底层安全的“二进制”语法。它被定义为“精简、加载时间短的格式和执行模型”,并且被设计为Web
多编程语言目标文件格式。
这意味着浏览器端的性能会得到极大提升,它也使得我们能够实现一个底层构建模块的集合.

webAssembly的优势

webassembly相较于asm.js的优势主要是涉及到性能方面。根据WebAssembly
FAQ的描述:在移动设备上,对于很大的代码库,asm.js仅仅解析就需要花费20-40秒,而实验显示WebAssembly的加载速度比asm.js快了20倍,这主要是因为相比解析
asm.js 代码,JavaScript 引擎破译二进制格式的速度要快得多。

主流的浏览器目前均支持webAssembly。

  • Safari 支持 WebAssembly的第一个版本是11
  • Edge 支持 WebAssembly的第一个版本是16
  • Firefox 支持 WebAssembly的第一个版本是 52
  • chrome 支持 WebAssembly的第一个版本是 57

使用WebAssembly,我们可以在浏览器中运行一些高性能、低级别的编程语言,可用它将大型的C和C++代码库比如游戏、物理引擎甚至是桌面应用程序导入Web平台。

妙用 scale 与 transfrom-origin,精准控制动画方向

2018/04/25 · CSS · 1
评论 ·
scale,
transfrom-origin

本文作者: 伯乐在线 –
chokcoco
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

上次发完 不可思议的纯 CSS
导航栏下划线跟随效果 这篇文章之后,很多朋友找我讨论,感叹
CSS 的奇妙。

然后昨天,群里一位朋友问到了一个和这个效果比较类似的效果,问如何

将下面这个动画的下划线效果,从左进入,右边离开修改为从上方进入,下方离开。

描述很难理解,看看原本的效果:

图片 2

CSS3 SVG实现可爱的动物哈士奇和狐狸动画

2017/09/28 · CSS,
HTML5 ·
SVG

原文出处: David
Khourshid   译文出处:码农网
– 小峰   

今天,我想向大家展示如何巧妙地使用HTML、CSS排序动画和SVG滤镜把生活中可能最可爱的东西之一——动物画到网页上。我们将探讨绘制动物的两种技术:一种使用纯HTML和CSS,另一种使用内联SVG背景图像。

此演示高度实验性质——动画SVG滤镜目前仅在Chrome中可用。

所涉及的动画也很复杂,因此本教程将重点介绍创建这些动物以及栩栩如生的动作所涉及的不同技术。放飞你的创意,自行创作独特和俏皮的动物动画吧。

话不多说,开始咯!

图片 3

在线演示
源码下载

开发前准备工作(MAC系统)

1.安装 cmake brew install cmake

2.安装 pyhton brew insatll python

3.安装 Emscripten
(调整下电脑的休眠时间,不要让电脑进入休眠,安装时间较长)

安装步骤如下:

图片 4

执行 source
./emsdkenv.sh,并将shell中的内容添加到环境变量中(~/.bashprofile):

图片 5

执行: source ~/.bash_profile

4.安装 WABT(将.wast文件转成
.wasm文件)图片 6

5.浏览器设置

图片 7

如果浏览器太旧,请更新浏览器,或者安装激进版浏览器来体验新技术。

6.一个本地web服务器.

Emscripten,它基于 LLVM ,可以将 C/C++ 编译成 asm.js,使用 WASM
标志也可以直接生成 WebAssembly 二进制文件(后缀是 .wasm)

图片 8图片 9

注:emcc 在 1.37 以上版本才支持直接生成 wasm 文件

Binaryen
是一套更为全面的工具链,是用C++编写成用于WebAssembly的编译器和工具链基础结构库。WebAssembly是二进制格式(Binary
Format)并且和Emscripten集成,因此该工具以Binary和Emscript-en的末尾合并命名为Binaryen。它旨在使编译WebAssembly容易、快速、有效。

图片 10

 

  • wasm-as:将WebAssembly由文本格式编译成二进制格式;
  • wasm-dis:将二进制格式的WebAssembly反编译成文本格式;
  • asm2wasm:将asm.js编译到WebAssembly文本格式,使用Emscripten的asm优化器;
  • s2wasm:在LLVM中开发,由新WebAssembly后端产生的.s格式的编译器;
  • wasm.js:包含编译为JavaScript的Binaryen组件,包括解释器、asm2wasm、S表达式解析器等。

WABT工具包支持将二进制WebAssembly格式转换为可读的文本格式。其中wasm2wast命令行工具可以将WebAssembly二进制文件转换为可读的S表达式文本文件。而wast2wasm命令行工具则执行完全相反的过程。

  • wat2wasm: webAssembly文本格式转换为webAssembly二进制格式(.wast 到
    .wasm)
  • wasm2wat: 将WebAssembly二进制文件转换为可读的S表达式文本文件(.wat)
  • wasm-objdump: print information about a wasm binary. Similiar to
    objdump.
  • wasm-interp: 基于堆栈式解释器解码和运行webAssembly二进制文件
  • wat-desugar: parse .wat text form as supported by the spec
    interpreter
  • wasm-link: simple linker for merging multiple wasm files.
  • wasm2c: 将webAssembly二进制文件转换为C的源文件

难点所在

第一眼看到这个效果,我的内心毫无波澜。以为只是简单的一个下划线 hover
效果,经过友人提醒,才发现,这个动画效果中,下划线是从一端进入,从另外一端离开的。而且,这个
hover 动画是纯 CSS 实现的。

图片 11

先不考虑上面说的修改需求,先想一想,如果就是还原上述效果,仅仅使用
CSS,该如何做呢?

 

塑造动物外形

演示使用两种不同的技术来创建动物不同身体部位的形状。哈士奇使用CSS
border-radius属性,狐狸使用内联背景SVG图像,因为后者的形状更复杂。

webAssembly的方法

还原效果

嗯,正常而言,我们一个 hover
效果,可能就是从哪里来,回哪里去,大部分的应该是这样的:

图片 12

DEMO1

现在,难点就在于如何在 hover 离开的时候,改变动画行进的方向。

下面我们将一个 hover 动画分解为 3 个部分:

  1. hover 进入状态
  2. hover 停留状态
  3. hover 离开状态

但是,对于一个 hover
效果而言,正常来说,只有初始状态,和hover状态两种。可能我们的代码是这样:

div { xxxx… } div:hover { xxxx… }

1
2
3
4
5
6
7
div {
    xxxx…
}
div:hover {
    xxxx…
}

对于一个 hover transition 动画,它应该是从:

  • 正常状态 -> hover状态 -> 正常状态 (三个步骤,两种状态)

所以,必须要有一种方法,能够使得 hover
动画的进入与离开产生两种不一样的效果,实现:

  • 状态1 -> hover状态 -> 状态2 (三个步骤,三种状态)

 

HTML标记

两只动物都使用嵌套的HTML部分对身体部位进行分组。分组的概念对于创造逼真的动画效果非常重要——当头部移动时,眼睛和耳朵也应该保持一起移动,因为它们是长在头上的。

<!– Markup for the fox head –> <div class=”fox-head”>
<div class=”fox-face”> <div class=”fox-ears”> <div
class=”fox-ear”/> <div class=”fox-ear”/> </div> <div
class=”fox-skull”/> <div class=”fox-front”/> <div
class=”fox-eyes”/> <div class=”fox-nose”/> </div>
</div> <!– Markup for the husky head –> <div
class=”husky-head”> <div class=”husky-ear”/> <div
class=”husky-ear”/> <div class=”husky-face”> <div
class=”husky-eye”/> <div class=”husky-eye”/> <div
class=”husky-nose”/> <div class=”husky-mouth”> <div
class=”husky-lips”/> <div class=”husky-tongue”/> </div>
</div> </div>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
<!– Markup for the fox head –>
 
 
<div class="fox-head">
  <div class="fox-face">            
    <div class="fox-ears">
      <div class="fox-ear"/>
      <div class="fox-ear"/>
    </div>
    <div class="fox-skull"/>
    <div class="fox-front"/>
    <div class="fox-eyes"/>
    <div class="fox-nose"/>
  </div>
</div>
 
 
 
<!– Markup for the husky head –>
 
 
<div class="husky-head">
  <div class="husky-ear"/>
  <div class="husky-ear"/>
  <div class="husky-face">
    <div class="husky-eye"/>
    <div class="husky-eye"/>
    <div class="husky-nose"/>
    <div class="husky-mouth">
      <div class="husky-lips"/>
      <div class="husky-tongue"/>
    </div>
  </div>
</div>

每个部分均可以独立移动,并随着其父元素的移动而移动,这样会产生更逼真的效果。不知道你发现没有,尾巴是深深嵌套到其他尾部组件中的。当每个尾巴部分相对于其母体定位,然后旋转相同的量时,就会产生均匀曲线的视觉感。

图片 13

webAssembly.validate

webAssembly.validate() 方法验证给定的二进制代码的 typed array
是否是合法的wasm
module.返回布尔值。图片 14

使用

图片 15

webAssembly.Module

WebAssembly.Module() 构造函数可以用来同步编译给定的 WebAssembly
二进制代码。不过,获取 Module 对象的主要方法是通过异步编译函数,如
WebAssembly.compile(),或者是通过 IndexedDB 读取 Module
对象.图片 16

参数: 一个包含你想编译的wasm模块二进制代码的 typed array(类型数组) or
ArrayBuffer(数组缓冲区).

重要提示:由于大型模块的编译可能很消耗资源,开发人员只有在绝对需要同步编译时,才使用
Module() 构造函数;其他情况下,应该使用异步 WebAssembly.compile()
方法。

实现控制动画方向的关键点

所以,这里的关键点就在于(划重点):

使得 hover 动画的进入与离开产生两种不一样的效果 。

接下来,也就是本文的关键所在,使用 transform: scale() 以及 transform-origin 实现这个效果。

 

用CSS塑造图形

CSS的border-radius属性大量用来塑造哈士奇的形象。对于许多元素要素,需要对每个边界半径进行逐个控制。例如,下面是如何构造哈士奇后腿的代码:

.husky-hind-leg { // … border-top-left-radius: 35% 100%;
border-top-right-radius: 40% 100%; }

1
2
3
4
5
.husky-hind-leg {
  // …
  border-top-left-radius: 35% 100%;
  border-top-right-radius: 40% 100%;
}

第一个数字表示曲线从顶部/底部边缘开始的深度,第二个数字表示曲线从左/右边缘开始的深度。

其他形状,如前腿,不能单独用border-radius成形,需要使用transform成形:

.husky-front-legs > .husky-leg:before { transform: skewY(-30deg)
skewX(10deg); transform-origin: top right; }

1
2
3
4
.husky-front-legs > .husky-leg:before {
  transform: skewY(-30deg) skewX(10deg);
  transform-origin: top right;
}

一旦图形就位,那么每个元素就能在其父元素中被赋予绝对的基于百分比的位置。这确保每个身体部位的精确放置以及响应性。

webAssembly.compile

WebAssembly.compile()
方法编译WebAssembly二进制代码到一个WebAssembly.Module
对象。图片 17

transform: scale() 实现线条运动

transform: scale
大家应该都很熟悉了,通俗来说是用于缩放,用官方的话说,就是:

CSS 函数 scale()
用于修改元素的大小。可以通过向量形式定义的缩放值来放大或缩小元素,同时可以在不同的方向设置不同的缩放值。

这里我们使用 transform: scaleX(0) 与 transform: scaleX(1) 来改变线条的显示与隐藏,它的
CSS 代码简单来看,可能是这样:

div { position: absolute; width: 200px; height: 60px; } div::before {
content: “”; position: absolute; left: 0; bottom: 0; width: 200px;
height: 2px; background: deeppink; transition: transform .5s; transform:
scaleX(0); transrform-origin:100% 0; } div:hover::before { transform:
scaleX(1); transform-origin:0 0; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
div {
    position: absolute;
    width: 200px;
    height: 60px;
}
div::before {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 200px;
height: 2px;
background: deeppink;
transition: transform .5s;
transform: scaleX(0);
transrform-origin:100% 0;
}
 
div:hover::before {
transform: scaleX(1);
transform-origin:0 0;
}

图片 18

DEMO2

嗯?为什么是要用 transform: scale() 来实现线条的动画?因为它可以配合 transform-origin 实现动画的不同运动方向:

 

用SVG塑造图形

至于狐狸,Sass-SVG被用来为每个身体部位创建复杂的SVG形状。SVG图像可以用作背景图像,更好的是,只要它们是基于64或UTF-8编码的,就可以被内联编写(为了最大限度的浏览器支持)。

不过,SVG代码手写起来非常棘手。我使用Adobe Illustrator来创建初始形状:

图片 19

然后我将每个身体部分保存为SVG图像。SVG代码通过Sass-SVG传输到SCSS样式表。例如,这是狐狸的鼻子:

JavaScript

.fox-nose:before { @ include svg((viewBox: (0 0 168 168))) { // the nose
@ include svg(‘path’, ( fill: $color-nose, d:
‘M83.7,86.7c3.3,0,11.6-3.9,11.6-7.1c0-3.2-9.4-3.2-11.6-3.2c-2.2,0-11.6,0-11.6,3.2
C72.1,82.8,80.4,86.7,83.7,86.7z’ )); // the line connecting the nose to
the mouth @ include svg(‘path’, ( stroke: $color-nose, fill: none, d:
‘M83.7,102.3V86.7’ )); // the mouth @ include svg(‘path’, ( stroke:
$color-nose, fill: none, d:
‘M94.5,104.9c0,0-5.2-2.7-10.8-2.7c-5.6,0-10.8,2.7-10.8,2.7’ )); } }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
.fox-nose:before {
@ include svg((viewBox: (0 0 168 168))) {
    // the nose
   @ include svg(‘path’, (
      fill: $color-nose,
      d: ‘M83.7,86.7c3.3,0,11.6-3.9,11.6-7.1c0-3.2-9.4-3.2-11.6-3.2c-2.2,0-11.6,0-11.6,3.2   C72.1,82.8,80.4,86.7,83.7,86.7z’
    ));
 
    // the line connecting the nose to the mouth
   @ include svg(‘path’, (
      stroke: $color-nose,
      fill: none,
      d: ‘M83.7,102.3V86.7’
    ));
 
    // the mouth
   @ include svg(‘path’, (
      stroke: $color-nose,
      fill: none,
      d: ‘M94.5,104.9c0,0-5.2-2.7-10.8-2.7c-5.6,0-10.8,2.7-10.8,2.7’
    ));
  }
}

这将在url()中生成一个编码的内联SVG字符串,看起来像这样:

.fox-nose:before { background-image:
url(“data:image/svg+xml;,%3Csvg…”); }

1
2
3
.fox-nose:before {
  background-image: url("data:image/svg+xml;,%3Csvg…");
}

由于SVG是一个背景图像,因此它可以被转换和动画化,就像一个HTML元素一样。使用Sass-SVG,Sass
$variables可用于完全控制SVG填充和笔触颜色。

通过内联SVG使狐狸响应起来很简单。viewbox属性值((viewBox:(0 0 168
168)))直接来自SVG文件,但只要保持高/宽比率,那么包含SVG背景图像的元素可以是任意大小。狐狸头部的所有部分都是绝对定位的,具有与.fox-head相同的高度和宽度。

webAssembly.Instance

WebAssembly.Instance实例对象是有状态,可执行的
WebAssembly.Module实例。实例中包含了所有可以被
JavaScript调用的WebAssembly 代码导出的函数。

重要提示:由于大型模块的实例化可能很消耗资源,开发人员只有在绝对需要同步编译时,才使用
Instance() 构造函数;其他情况下,应该使用异步
WebAssembly.instantiate()方法。

图片 20

  • module: 需要被实例化的webAssembly module
  • importObject: 需要导入的变量

transform-origin 实现线条运动方向

transform-origin 让我们可以更改一个元素变形(transform)的原点,transform-origin 属性可以使用一个,两个或三个值来指定,其中每个值都表示一个偏移量。
没有明确定义的偏移将重置为其对应的初始值。

本效果最最最重要的地方就在于这里,我们使用 transform-origin 去改变 transform: scale() 的原点实现线条运动的方向。

  1. 我们给线条设置一个默认的 transform-origin 记为状态1
  2. hover 的时候,设置另外一个不同的 transform-origin, 记为状态2

所以,当然我们 hover
的时候,会读取状态2的transform-origin,从该原点开始放大至 scaleX(1),hover
离开的时候,会读取状态1的transform-origin,从scaleX(1)状态缩小至该原点。

嗯,CSS代码大概是这样:

div { position: absolute; width: 200px; height: 60px; } div::before {
content: “”; position: absolute; left: 0; bottom: 0; width: 200px;
height: 2px; background: deeppink; transition: transform .5s; transform:
scaleX(0); transform-origin: 100% 0; } div:hover::before { transform:
scaleX(1); transform-origin: 0 0; }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
div {
    position: absolute;
    width: 200px;
    height: 60px;
}
div::before {
content: "";
position: absolute;
left: 0;
bottom: 0;
width: 200px;
height: 2px;
background: deeppink;
transition: transform .5s;
transform: scaleX(0);
transform-origin: 100% 0;
}
 
div:hover::before {
transform: scaleX(1);
transform-origin: 0 0;
}

这里,我们巧妙的通过 hover
状态施加了一层新的 transform-origin ,让动画的进入与离开产生了两种不同的效果,两个不同的方向。

如此一来,也就顺利实现了我们想要的效果,撒花:

图片 21

DEMO3

注意,这里使用了 transform-origin 去改变 transform: scale() 的原点实现线条运动的方向,而没有借助诸如 position 位移,transform: translate(),或者
margin 等位置属性去改变线条所在的位置。

所以,有趣的是,线条其实没有产生过任何位移,这里其实也是障眼法,让它看上去,它好像在移动。

 

“Squigglevision”和SVG滤镜

Squigglevision是一种通过摆动形状轮廓来模拟手绘动画的动画技术。这使得像狐狸和哈士奇这样的场景看上去更加动态化和手绘化,即使动物在不动的时候也是如此。

SVG有一个称为的滤镜,可以给任何应用了此滤镜的地方“噪声”。结合滤镜以指定像素在每个过滤器中移动的距离。

<svg xmlns=”” version=”1.1″>
<defs> <filter id=”squiggly-0″> <feturbulence
id=”turbulence” basefrequency=”0.02″ numoctaves=”3″ result=”noise”
seed=”0″/> <fedisplacementmap id=”displacement” in=”SourceGraphic”
in2=”noise” scale=”2″/> </filter> <filter
id=”squiggly-1″> <feturbulence id=”turbulence”
basefrequency=”0.02″ numoctaves=”3″ result=”noise” seed=”1″/>
<fedisplacementmap in=”SourceGraphic” in2=”noise” scale=”3″/>
</filter> <filter id=”squiggly-2″> <feturbulence
id=”turbulence” basefrequency=”0.02″ numoctaves=”3″ result=”noise”
seed=”2″/> <fedisplacementmap in=”SourceGraphic” in2=”noise”
scale=”2″/> </filter> <filter id=”squiggly-3″>
<feturbulence id=”turbulence” basefrequency=”0.02″ numoctaves=”3″
result=”noise” seed=”3″/> <fedisplacementmap in=”SourceGraphic”
in2=”noise” scale=”3″/> </filter> <filter
id=”squiggly-4″> <feturbulence id=”turbulence”
basefrequency=”0.02″ numoctaves=”3″ result=”noise” seed=”4″/>
<fedisplacementmap in=”SourceGraphic” in2=”noise” scale=”1″/>
</filter> </defs> </svg>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<svg xmlns="http://www.w3.org/2000/svg" version="1.1">
  <defs>
    <filter id="squiggly-0">
      <feturbulence id="turbulence" basefrequency="0.02" numoctaves="3" result="noise" seed="0"/>
      <fedisplacementmap id="displacement" in="SourceGraphic" in2="noise" scale="2"/>
    </filter>
    <filter id="squiggly-1">
      <feturbulence id="turbulence" basefrequency="0.02" numoctaves="3" result="noise" seed="1"/>
      <fedisplacementmap in="SourceGraphic" in2="noise" scale="3"/>
    </filter>
    <filter id="squiggly-2">
      <feturbulence id="turbulence" basefrequency="0.02" numoctaves="3" result="noise" seed="2"/>
      <fedisplacementmap in="SourceGraphic" in2="noise" scale="2"/>
    </filter>
    <filter id="squiggly-3">
      <feturbulence id="turbulence" basefrequency="0.02" numoctaves="3" result="noise" seed="3"/>
      <fedisplacementmap in="SourceGraphic" in2="noise" scale="3"/>
    </filter>
    <filter id="squiggly-4">
      <feturbulence id="turbulence" basefrequency="0.02" numoctaves="3" result="noise" seed="4"/>
      <fedisplacementmap in="SourceGraphic" in2="noise" scale="1"/>
    </filter>
  </defs>
</svg>

属性的任何元素。要创建“squigglevision”效果,关键帧动画快速地一次设置一个滤镜

@keyframes squigglevision { 0% { -webkit-filter: url(‘#squiggly-0’);
filter: url(‘#squiggly-0’); } 25% { -webkit-filter:
url(‘#squiggly-1’); filter: url(‘#squiggly-1’); } 50% {
-webkit-filter: url(‘#squiggly-2’); filter: url(‘#squiggly-2’); } 75%
{ -webkit-filter: url(‘#squiggly-3’); filter: url(‘#squiggly-3’); }
100% { -webkit-filter: url(‘#squiggly-4’); filter: url(‘#squiggly-4’);
} }

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@keyframes squigglevision {
  0% {
    -webkit-filter: url(‘#squiggly-0’);
    filter: url(‘#squiggly-0’);
  }
  25% {
    -webkit-filter: url(‘#squiggly-1’);
    filter: url(‘#squiggly-1’);
  }
  50% {
    -webkit-filter: url(‘#squiggly-2’);
    filter: url(‘#squiggly-2’);
  }
  75% {
    -webkit-filter: url(‘#squiggly-3’);
    filter: url(‘#squiggly-3’);
  }
  100% {
    -webkit-filter: url(‘#squiggly-4’);
    filter: url(‘#squiggly-4’);
  }
}

注意:这些SVG滤镜目前在Firefox中似乎不起作用,因此可以将这样的滤镜动画视为一种渐进增强处理。

发表评论

电子邮件地址不会被公开。 必填项已用*标注