在 html 中用加色法混合颜色
本文通过解决一个假想的问题介绍了 css screen 混合模式,并介绍了如何用 svg 滤镜、canvas 2d、canvas webgl 实现相同的效果。
下面的图片演示三种颜色光叠加的效果,请在 html 中实现这种效果。
词语 | 指代 |
---|---|
混合 | blend |
加色 | additive color - 名词 |
特性 | attribute,比如 ,说 id 是元素 a 的特性 |
透明度 | α、alpha |
伪输入图像 | pseudo input image |
着色器 | shader |
着色器程序 | shader program |
xml 应用程序 | XML application |
chrome | google chrome 41 |
firefox | firefox developer edition 40 |
ie | internet explorer 11 |
3 个浏览器 | 上面 3 个版本的浏览器 |
opera 已经基于 webkit 了,所以未测试 opera,若在 chrome 中可用那我就认为在 opera 中也可用。
当然可以用 photoshop 制作图片,html 用
引用该图片,本文不讨论这种方法。
观察重叠部分发现该部分的颜色不仅受自己的影响、还受它下面背景颜色的影响,重叠部分的颜色是自己的颜色和背景颜色混合的结果。换句话说,一个像素绘制出来的颜色等于像素颜色和背景像素颜色的混合,即
C=B(Cb,Cs) ,其中,
C 是绘制的颜色
B 是混合函数
Cb 是背景颜色
Cs 是前景颜色,即像素的颜色
这里面 C 的 r、g、b 颜色分量都是 [0, 1] 的小数而不是 [0, 255] 的整数。显然,对同一个像素来说不同的 B 得到不同的 C 。红绿蓝分别是 rgb(1, 0, 0)
、rgb(0, 1, 0)
、rgb(0, 0, 1)
, B 如果满足 B(Cb,Cs)=min(Cs+Cb,1) 就能合成白色。
重点:不同的 B 得到不同的 C
html 中经常用到下面 3 个方法,
![]()
引用带 alpha 通道的图像它们使用相同的混合函数,叫做 α 混合或简单 α 复合,
αs 是前景透明度, αb 是背景透明度,上面的式子计算混合后的 r、g、b 颜色,混合后的透明度 αo 由公式 αo=αs+αb×(1−αs) 给出。很多时候背景不透明,即 αb 是 1,上面把 1 代入了 αb 。
简单 α 复合 - http://dev.w3.org/fxtf/compositing/#simplealphacompositing
opacity - http://stackoverflow.com/questions/8743482/calculating-opacity-value-mathematically
下面给上面的式子代入几组实际值。设 Cs 是不透明红 rgba(1, 0, 0, 1)
, Cb 是不透明蓝 rgb(0, 0, 1)
,它俩混合的结果不用计算都知道仍然是不透明红,计算过程如下,
r = 1 x 1 + 0 x (1 - 1) = 1
g = 0 x 1 + 0 x (1 - 1) = 0
b = 0 x 1 + 1 x (1 - 1) = 0
红蓝得红,混合失败。另外一组, Cs = rgba(1, 0, 0, 0.5)
, Cb = rgb(0, 0, 1)
,有,
r = 1 x 0.5 + 0 x (1 - 0.5) = 0.5
g = 0 x 0.5 + 0 x (1 - 0.5) = 0
b = 0 x 0.5 + 1 x (1 - 0.5) = 0.5
要想让得到的 rgb(0.5, 0, 0.5)
和 rgb(0, 1, 0)
的绿色混合以得到 rgb(1, 1, 1)
的白色,α 需要满足下面的方程组,
上面的方程组无解,即无论如何设置 α 都无法通过 B 混合 rgb(0.5, 0, 0.5)
和 rgb(0, 1, 0)
得到 rgb(1, 1, 1)
。
回过头来观察式子 Cs×αs+Cb×(1−αs) ,可以看出结果介于 Cs 和 Cb 之间。红绿蓝混合时,白色的红色分量只能通过红色得到,这要求红色的 α 是 1,但 α = 1 造成背景颜色蓝或者绿被忽略,而忽略任何一个分量都无法得到白色。因此这个混合函数不合适。
如果可以自己逐一计算像素的颜色,得出要求的效果自然不在话下。除了自己计算外,如果存在正好能够实现要求效果的固定函数,则调用该函数也可以。
在 html 中处理颜色有 3 种工具,css、svg、canvas。
css 有个模块叫复合与混合,这个模块定义了若干固定函数,其中一个叫 screen,它的 B 是
css 复合与混合 - http://dev.w3.org/fxtf/compositing/
假设 add 是 min(Cs+Cb,1) ,screen 虽然不是 add 但是也可以把红绿蓝合成白色,实现要求的效果。至于 add、screen 或其它混合函数哪个能更精确地反映光线的混合,我也搞不清楚。
通过指定 html 元素的 css 属性 mix-blend-mode: screen
来让元素和其背后的元素以 screen 方式混合。css 目前没办法逐像素计算目标区域的颜色。
本文把 svg 写在 html 内 。svg 是 xml 应用程序,遵循 xml 语法,但是放在 html 中又可以采用部分 html 语法。如果大家按照 xml svg 的知识去看本文的代码可能会有疑问,所以在写 svg 之前先说一下 html 中的 svg。
html 不支持名字空间,忽略 里面由特性定义的名字空间,所以本文的 svg 没有
xmlns="http://www.w3.org/2000/svg"
或者 xmlns:xlink="http://www.w3.org/1999/xlink"
,xlink:href
在 html 中是个普通的特性名,冒号和名字空间无关
没有歧义时可以省略特性值周围的引号
xml 中没有内容的元素比如
也可以写做
,叫做自闭合;html 不存在自闭合,但内嵌的 svg 元素可以使用自闭合
html 中的 svg 元素可以自闭合 - http://www.w3.org/TR/html-markup/syntax.html#svg-mathml
所有没有内容的 xml 元素都叫 empty 元素,可以自闭合;html 不存在 empty 元素,但是定义了一些 void 元素,void 元素不能有内容,只有开始标记没有结束标记。
所有 void 元素是,area, base, br, col, command, embed, hr, img, input, keygen, link, meta, param, source, track, wbr
http://www.w3.org/TR/html-markup/syntax.html#syntax-elements所有 html 元素开始标记的 > 前面可以写一个 /,和不写 / 一样。
解释为
而不是
,
是 void 元素,所以没问题;
解释为,
不是 void 元素,所以可能会出问题,
red = html, green = xhtml
有些元素可以省略结束标记,但不是 void 元素,比如
;有些元素有时候没有内容,但既不是 void 元素也不能省略结束标记,比如 ;有些元素可以省略开始标记。
http://www.w3.org/TR/html5/syntax.html#optional-tags浏览器从网站获取的文件 mimetype = text/html 导致调用 html 解析器。
另外,svg 很多要素都没有浏览器支持;当支持的时候,可能各个浏览器有差异。
有了这些知识下面看 svg。
svg
- svg 里面的元素也是 dom 元素,也可以应用 css 混合。css 混合在 css 部分讲述
- svg 有个规范定义了复合,和 css 混合效果差不多,关键字
comp-op
。我不知道有哪个浏览器支持该规范- svg 滤镜
和
svg 滤镜 - http://www.w3.org/tr/svg11/filters.html
svg 复合 - http://www.w3.org/TR/SVGCompositing/
按其
operator
特性指出的操作组合两个输入图像 i1 、 i2 。当operator=arithmetic
时需要另外的 4 个特性k1
、k2
、k3
、k4
,默认值是 0,并按如下方式分别计算结果像素的 3 个通道,我不清楚它如何处理 α 通道,result=k1×i1×i2+k2×i1+k3×i2+k4⋯(svg.1)
既然知道
mix-blend-mode: screen
的混合函数 B=Cs+Cb−Cs×Cb ,设 Cs 是 i1 , Cb 是 i2 ,有,
result===k1×i1×i2−1×i1×i2−1×Cs×Cb+++k2×i11×i1Cs+++k3×i21×i2Cb++k40所以
可以实现效果。
支持 screen 混合模式,
,所以应该也能实现效果。
canvas
canvas 分为 2d 和 webgl,它里面的形状都是画上去的,由像素组成,不是 dom 元素,无法应用 css 混合;但是 canvas 2d 有个全局复合操作,和 css 混合是同一个概念在两种不同语言中的实现,支持 css 混合的所有固定函数。当然自己计算像素也行。
全局复合操作 - http://dev.w3.org/fxtf/compositing/#canvascompositingandblending
webgl 没有与 css、canvas 2d 完全相同的混合概念,但也有自己的混合函数,解决本文提出的问题不在话下。webgl 有个特点是无论你干什么都需要写着色器代码、写调用编译着色器的函数的代码、写调用连接着色器的函数的代码。
如何运行示例代码
下面是框架代码,后面给出的示例代码需要放在框架代码的
里
<html> <head> <meta charset=utf-8> <style> .sample { display: inline-block; vertical-align: top; width: 200px; } style> <title>additive colortitle> head> <body> body> html>
依次执行下面 3 个步骤,缺一不可,
- 新建一个空 html 文件
- 拷贝框架代码,粘贴到空的 html 文件
- 确保 html 文件编码为 utf8,保存
示例 - css
mix-blend-mode
ie 不认识
mix-blend-mode
和isolation
mix-blend-mode - http://dev.w3.org/fxtf/compositing/#mix-blend-mode
isolation - http://dev.w3.org/fxtf/compositing/#isolation元素的
mix-blend-mode
属性是说,我知道你的颜色,但是在显示的时候不要只显示你的颜色,而是要显示你的颜色和你背景颜色混合后的颜色,至于如何混合,我会通过mix-blend-mode
属性的值指出。因此设计一个容器 div
position: relative
,里面有红绿蓝三个方块 divposition: absolute
,三个方块之间有重叠部分,通过mix-blend-mode: screen
指出重叠部分颜色的计算方法。
<div class=sample> <style> .s1 { height: 180px; isolation: isolate; position: relative; } .s1 > div { height: 100px; mix-blend-mode: screen; position: absolute; width: 100px; } .s1 > div:nth-of-type(1) { background-color: red; left: 50px; top: 20px; } .s1 > div:nth-of-type(2) { background-color: lime; left: 30px; top: 40px; } .s1 > div:nth-of-type(3) { background-color: blue; left: 70px; top: 60px; } style> <div class=s1><div>div><div>div><div>div>div> <h4>mix-blend-mode: screen 不是颜色分量相加h4> div>
上面的代码实现了刚才的设计,并且额外设置了容器 div 的一个属性
isolation: isolate
。元素的isolation: isolate
是说,我的子元素不会和我外面的元素混合。isolation
属性另外一个可能的取值兼默认值是auto
,没有限制、随便混和。容器 div 放在 html 的
里,
默认的颜色是不透明白,假设没有通过
isolation: isolate
限定容器元素的子元素不能与容器外的元素混合,红绿蓝 3 个方块 div 就要和白色以 screen 模式混合。红色rgb(1, 0, 0)
和白色rgb(1, 1, 1)
以 screen 模式 B=Cs+Cb−Cs×Cb 混合的结果是rgb(1, 1, 1)
白色,
r = 1 + 1 - 1 x 1 = 1
g = 0 + 1 - 0 x 1 = 1
b = 0 + 1 - 0 x 1 = 1
绿蓝方块和白色混合也得到白色,结果就是一片白,不是要求的效果,所以设置容器的isolation: isolate
。示例 - svg
和
- svg filter primitive 必须包含在
元素内
是 svg filter primitive
- 所以?
定义一个矩形滤镜区域,默认值是
x=-10%
、y=-10%
、width=120%
、height=120%
,x
和y
的值相对于应用滤镜的元素,x=-10 y=10
以应用滤镜的元素为准向左 10 向下 10。x
、y
、width
、height
的数值的解释由另外一个特性 filterUnits 决定,如果
filterUnits
是默认值objectBoundingBox
x=10
是说 x 是应用滤镜的元素的宽度的 10 倍x=100%
是说 x 是应用滤镜的元素的宽度的 1 倍如果
filterUnits=userSpaceOnUse
x=10
是说 x 是 10 个用户单位,用户单位具体是啥要看包含这个元素的 svg 的宽度或高度用的单位,默认 pxx=100%
含义不变filterUnits - http://www.w3.org/TR/SVG11/filters.html#FilterEffectsRegion
这个连接指向 15.5 Filter effects region,因为指向 filterUnits 的连接打开后其内容只是一个指向 FilterEffectsRegion 的连接如果 svg 宽度单位是 px,高度单位是 cm,用户单位是啥?
这个问题我着实回答不上来。难道 x 对应 svg 的 width,y 对应 svg 的 height?
接受两个图像
i1
和i2
,逐一扫描
i1
和i2
的像素,用 svg.1 产生新像素,放置到滤镜区域相应的位置。
对图像
i1
和i2
应用 screen 混合模式。
in
的默认值是中上一个 filter primitive 的结果;如果自己是第一个,则默认
SourceGraphic
。in=SourceGraphic in2=BackgroundImage
分别使用当前图片和当前图片的背景图片。in - http://www.w3.org/TR/SVG11/filters.html#FilterPrimitiveInAttribute
同样是图片,为啥一个叫 source graphic,一个叫 background image?
http://www.w3.org/TR/SVG11/filters.html#AccessingBackgroundImage
简而言之出于性能考虑伪输入图像
BackgroundImage
是背景的一个快照,要使用BackgroundImage
作in
或in2
的参数必须指定容器元素的enable-background=new
以要求容器存储背景快照供滤镜使用。enable-background
默认值是accumulate
,不存,也无法使用BackgroundImage
。两个不同的单词可能是要强调输入图像和背景图像的这种差异。当然也可能是我想多了,人家就是喜欢出其不意,如之奈何?混合 3 个形状需要应用两次滤镜,出于演示的目的正好一个用
一个用
。
- 创建 3 个分别是红绿蓝的 svg 形状
- 为了使用
BackgroundImage
,把这 3 个形状放到一个组里面,并设置组的enable-background=new
- 放置第 1 个形状
- 把形状 2 放到和形状 1 部分重叠的位置,此时形状 1 可以视为形状 2 的背景,对形状 2 应用滤镜
,重叠的部分就会经过 svg.1 计算
- 形状 3 用
。
<div class=sample> <svg height=180 width=200> <filter id=s2-composite x=0 y=0 width=1 height=1> <feComposite in2=BackgroundImage operator=arithmetic k1=-1 k2=1 k3=1>feComposite> filter> <filter id=s2-blend> <feBlend in2=BackgroundImage mode=screen>feBlend> filter> <g enable-background=new> <rect width=100 height=100 x=50 y=20 fill=red>rect> <rect width=100 height=100 x=30 y=40 fill=lime filter=url(#s2-composite)>rect> <rect width=100 height=100 x=70 y=60 fill=blue filter=url(#s2-blend)>rect> g> svg> <h4>svg,仅限 ie 10+h4> div>
没有指出
的
x
、y
、width
、height
所以它们都取默认值。只有 ie 10+ 支持上面的代码。ie 此刻又迸射出耀眼的光芒,
别的浏览器玩儿蛋去吧!
这是我心里想象的 ie 工作人员心里的想象,请不要以为他们一定是那样想的。
Appendix A: The deprecated enable-background property
http://dev.w3.org/fxtf/filters/#AccessBackgroundImagesvg 最近的风向是不赞成
enable-background
了,enable-background=new
要换成isolation=isolate
以“兼容 css 复合与混合”。大家留意一下,isolation
是 css 属性,在样式表里面指定;svg 发明了个presentation attribute
,这种特性也可以在样式表中以 css 属性的形式指定以兼容 css,而 svg 这个isolation
不是所谓的presentation attribute
,不能在样式表里指定。这还兼容个毛?svg 就是这样,当你拿它和 html 比的时候,一眼看上去都差不多,似乎能很容易混用,实际上有很多出其不意的不一样,烦得要死。无论如何还是要换一下试试。换成
isolation=isolate
后连 ie 都没法读取背景图片了,前面的 css 部分说过 ie 不认识样式表中的isolation
,现在看来 ie 也不认识isolation
特性,3 个浏览器没有能运行的。http://dev.w3.org/fxtf/filters/ 里面的示例 Example of feComposite 就是从 http://www.w3.org/TR/SVG11/filters.html 拷贝的同名示例,只是把enable-background=new
换成了isolation=isolate
,但是紧跟其后的连接 View this example as SVG 指向的 svg 文件里面用的仍然是enable-background=new
。这么看来 ie 是被坑了,但从大的方面看 svg 本身就很坑,废弃
enable-background
一点都不亏。svg 很好玩,但是能别用 xml 语法吗?一般只要 chrome 和 firefox 能用,ie 我常常忽略,前面 css 的示例代码就没管 ie。现在这段代码,由于只有 ie 10+ 支持伪输入图像
BackgroundImage
,chrome 和 firefox 上都运行不了,不能说是达到了要求的效果,最好有在 3 个浏览器上都能运行的 svg 例子。所幸对于这个简单的问题,TIMTOWTDI!下面凑一个能在 3 个浏览器上运行的 svg 解法,说凑是因为它没有把 3 个元素两两混合,而是在滤镜里生成了两个方块,和应用滤镜的那个方块元素混合。在滤镜里生成方块指的是把滤镜矩形设置为单一的颜色然后临时保存,这要用到,
说的是当把图像传递给
时,对图像的每一个像素左乘下面的矩阵以得到新的像素,
a00 a01 a02 a03 a04 a10 a11 a12 a13 a14 a20 a21 a22 a23 a24 a30 a31 a32 a33 a34 0 0 0 0 1 - 最后一行总是它,不写在 values 里
即新像素
(r', g', b', a')
等于给出的矩阵乘以原像素
(r, g, b, a)
,
⎡⎣⎢⎢⎢⎢⎢⎢R′G′B′A′1⎤⎦⎥⎥⎥⎥⎥⎥=⎡⎣⎢⎢⎢⎢⎢⎢a00a10a20a300a01a11a21a310a02a12a22a320a03a13a23a330a04a14a24a341⎤⎦⎥⎥⎥⎥⎥⎥×⎡⎣⎢⎢⎢⎢⎢⎢RGBA1⎤⎦⎥⎥⎥⎥⎥⎥
把
(r, g, b, a, 1)
变成(1, 0, 0, a, 1)
,这是个红色方块。因此可以写下面的代码,
- 定义一个蓝方块
- 对蓝方块应用滤镜
- 滤镜从蓝方块生成一个红方块和一个绿方块,偏移,混合
- 上一步的结果和蓝方块混合,蓝方块在滤镜中通过伪输入图像
SourceGraphic
引用
<div class=sample> <svg height=180 width=200> <filter id=s2-2 x=-1 y=-1 width=2 height=2> <feOffset dx=-20 dy=-40>feOffset> <feColorMatrix result=red type=matrix values=" 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0">feColorMatrix> <feOffset in=SourceGraphic dx=-40 dy=-20>feOffset> <feColorMatrix type=matrix values=" 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0">feColorMatrix> <feBlend mode=screen in2=red>feBlend> <feBlend mode=screen in2=SourceGraphic>feBlend> filter> <rect x=70 y=60 width=100 height=100 fill=blue filter=url(#s2-2)>rect> svg> <h4>svg feBlendh4> div>
也可以做红绿蓝 3 张图片,在滤镜里用
引用,代码类似下面,记得先做 3 张 100px * 100px 的红绿蓝图片放到 html 的同一目录。
总结
svg 滤镜的思路就是
和
,前者自己计算像素,后者调用固定函数。由于 chrome 和 firefox 不支持在滤镜中读取背景图像所以给了两段绕弯的代码,第 2 段代码还依赖 3 张图片。
示例 - canvas 2d
方法 1.
CanvasRenderingContext2D.prototype.globalCompositeOperation
globalCompositeOperation 是 HTML Canvas 2D Context 规范定义在接口 CanvasRenderingContext2D 上的一个特性。chrome 里面访问不到 CanvasRenderingContext2D.prototype.globalCompositeOperation,firefox 和 ie 里面存在该 js 属性,定义了 get 和 set 访问函数。
不能直接在代码里使用
CanvasRenderingContext2D.prototype.globalCompositeOperation
,因为这句代码会调用get
函数,而get
需要通过this
访问实际的画布上下文对象。当然正常情况下也不会那么写,正常情况是先获取某个画布的 2d 上下文,然后访问上下文的属性,var t = theCanvas.getContext("2d"); console.log(t.globalCompositeOperation); // 默认 "source-over"
有了上面的
t
,可以写
Object.getOwnPropertyDescriptor(CanvasRenderingContext2D.prototype, "globalCompositeOperation").get.call(t);
interface CanvasRenderingContext2D
http://www.w3.org/TR/2dcontext/#canvasrenderingcontext2d
- 绘制 红 色方块
- 全局复合模式 = screen
- 绘制 绿 色方块
- 绘制 蓝 色方块
<div class=sample> <canvas class=s3-1 height=180 width=200>canvas> <h4>canvas 2d 全局复合h4> <script> !function () { var canvas = document.querySelector(".s3-1"), cc = canvas.getContext("2d"); cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); cc.globalCompositeOperation = "screen"; cc.fillStyle = "lime"; cc.fillRect(30, 40, 100, 100); cc.fillStyle = "blue"; cc.fillRect(70, 60, 100, 100); }(); script> div>
方法 2.
CanvasRenderingContext2D.prototype.putImageData
- 绘制 红 色方块
- 选定一个部分重叠的方块,使用
CanvasRenderingContext2D.prototype.getImageData
把该部分像素读入内存,对每个像素,和 绿 色应用 min(Cs+Cb,1) ,然后写入画布- 选定一个部分重叠的方块,读取,每个像素和 蓝 色应用 min(Cs+Cb,1) ,写入画布
因为红绿蓝两两混合时每个分量必然一个是 0 一个是 1,所以并没有调用 Math.min 而是直接把分量置 1,或者说置 255。
<div class=sample> <canvas class=s3-2 height=180 width=200>canvas> <h4>在 canvas 中把颜色分量置 1,未使用 min(x + y, 1)h4> <script> !function () { var canvas = document.querySelector(".s3-2"), cc = canvas.getContext("2d"), i, len, idata, arr; cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); for (i = 0, idata = cc.getImageData(30, 40, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 1] = arr[i + 3] = 255; cc.putImageData(idata, 30, 40); for (i = 0, idata = cc.getImageData(70, 60, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 2] = arr[i + 3] = 255; cc.putImageData(idata, 70, 60); }(); script> div>
示例 - canvas webgl
webgl api - https://msdn.microsoft.com/en-us/library/dn621085(v=vs.85).aspx
webgl methods - https://msdn.microsoft.com/en-us/library/dn302341(v=vs.85).aspx
glBlendFunc + glBlendEquation 效果演示 - http://www.andersriggelsen.dk/glblendfunc.php本文假设你对 webgl 一无所知。
本节的目标是在阅读本节内容之后,对 webgl 一无所知的读者能掌握 webgl 的基本思路、写出基本的 webgl 程序。如果不是这个情况,请跟帖指出,我会修正本节内容直至达到前述目标。
绘制方块
设
var gl = theCanvas.getContext("webgl");
,webgl 通过下面两个函数之一进行绘制,
gl.drawArrays(mode, first, count);
gl.drawElements(mode, count, type, offset);
这两个函数差不多,先解释
gl.drawArrays
。在调用gl.drawArrays
之前gl
必须满足下列条件,
- 使用
gl.useProgram
绑定了 1 个着色器程序- 使用
gl.bindBuffer
绑定了 1 个数组,数组里面有内容可用- 使用
gl.enableVertexAttribArray
启用了至少 1 个在顶点着色器里定义的特性- 使用
gl.vertexAttribPointer
描述了启用的特性上面 4 个条件就是发起 1 次
gl.drawArrays
调用所需的代码 + 数据,代码用opengl es
着色器语言glsl
写成,数据在javascript
代码里面提供,并调用 webgl 的 javascript api 建立js 数据
到glsl 代码
的联系。1 个 webgl 程序可以有很多着色器程序,每个着色器程序一定有 1 个顶点着色器和 1 个片段着色器。每绘制 1 个点,webgl 都依次调用顶点着色器和片段着色器。顶点着色器的唯一任务是给全局变量
gl_Position
赋值,它代表 1 个点的位置;片段着色器的唯一任务是给全局变量gl_FragColor
赋值,它代表刚才那个点的颜色。
gl.drawArrays
依次执行下列步骤,
- webgl 一次从数组读取由
gl.vertexAttribPointer
的stride
参数指出的字节,这些字节视为 1 个顶点- 把这么多字节按
gl.vertexAttribPointer
指出的方式拆分后分别赋值给顶点着色器里面用attribute
定义的变量,调用了几次gl.vertexAttribPointer
就要给几个变量赋值- 进入顶点着色器的
main
函数,main
里面一般会使用刚才赋值过的特性- 顶点着色器的
main
结束,进入片段着色器的main
函数- 片段着色器的
main
结束,1 个顶点渲染完毕,从数组读取下一个顶点- 重复上述过程,直至处理了由
count
参数指出的顶点数webgl 实际上读取的是从 javascript 数组拷贝到显卡上的数组
gl.vertexAttribPointer( index, - 特性在和 gl.ARRAY_BUFFER 绑定的缓冲区中的索引 size, - 1 | 2 | 3 | [4],每个特性有几个分量,比如 vec3 有 3 个分量 type, - gl.BYTE | gl.UNSIGNED_BYTE | gl.SHORT | gl.UNSIGNED_SHORT | [gl.FLOAT] normalized, - true,转化到 [-1.0, 1.0] stride, - [0, 255],默认 0,单位字节,必须是 type 的整数倍 offset - 默认 0,单位字节,必须是 type 的整数倍 )
读作:为了给顶点着色器里面定义的第
index
号特性赋值,从数组中取stride
个字节作为一个顶点,从这个顶点的第offset
个字节开始取size
个type
,每个type
依次对应特性的一个分量。假设在顶点着色器里定义了 2 个特性
attribute vec3 position; attribute vec2 resolution; void main() { gl_Position = ???; }
每次进入顶点着色器的时候都希望这俩变量被赋值,以便在顶点着色器的
main
里面使用它们。在 javascript 里面用一个Float32Array
保存顶点,数组形如[x0, y0, z0, w0, h0, x1, y1, z1, w1, h1, ...]
下面的调用
gl.vertexAttribPointer(idPosition, 3, gl.FLOAT, false, 5 * 4, 0); gl.vertexAttribPointer(idResolution, 2, gl.FLOAT, false, 5 * 4, 3 * 4); // 4 是 Float32Array 数组的元素 Float32 的字节数,对应 gl.FLOAT // 5 是说一个顶点有 5 个 Float32,5 * 4 是这个顶点的字节数 // 第 2 个调用里面的 3 是说 resolution 从每个顶点的第 3 个 Float32 开始 // // idPosition 和 idColor 是 gl.getAttribLocation 返回的一个整数, // 代表顶点着色器里面的特性 position 和 resolution。position 和 resolution // 是在顶点着色器里面定义的变量,不能直接在 javascript 里面用,需要通过 // gl.getAttribLocation 建立一个对应关系 // // 如果给 resolution 的 offset 参数传 0 则 resolution 和 position 重叠, // 这没有问题但是数值可能没有意义
让 webgl 这样取值
| 数组中每个顶点的长度是 5 * 4 = 20(stride)个字节 | | x0, y0, z0, w0, h0, x1, y1, z1, w1, h1, ... | | | | | | | 从数组 arr 的第 3 * 4 = 12(offset)个字节开始取 2(size)个 | | | gl.FLOAT(type)组成一个 vec2(arr[3], arr[4]),把这个 vec2 | | | 赋值给顶点着色器特性 resolution | | | 从数组 arr 的第 0(offset)个字节开始取 3(size)个 gl.FLOAT(type)组成 | 一个 vec3(arr[0], arr[1], arr[2]),把这个 vec3 赋值给顶点着色器特性 position
每个顶点的画布分辨率
resolution
都一样,所以一般不这么传递,放在这里只是为了举例。
gl.drawArrays(mode, first, count);
的mode
参数从 webgl 定义的枚举里面取值,分 3 种类型
gl.POINTS
,点。数组中每个顶点代表一个点gl.LINES
、gl.LINE_STRIP
、gl.LINE_LOOP
,直线段。数组中每个顶点代表直线的一个端点或者说顶点,顶点之间的点由 webgl 以线性插值的方式计算出来gl.TRIANGLES
、gl.TRIANGLE_STRIP
、gl.TRIANGLE_FAN
,平面三角形。数组中每个顶点代表三角形的一个顶点,顶点之间的点由 webgl 以线性插值的方式计算出来为了绘制一个方块,调用
gl.drawArrays(gl.TRIANGLE_FAN, offset, 4);
,意思是从当前绑定的数组的第offset
个顶点开始用连续的 4 个顶点组成 1 个三角扇,这 4 个点的位置是事先规划好的,排列如下0 3 1 2
webgl.1
- 4 个点的三角扇包含 2 个三角形,分别是
0 - 1 - 2
和0 - 2 - 3
,三角扇绘制的三角形的第 1 个顶点总是offset
处的那个顶点- 这 2 个三角形共享 1 条边
0 - 2
,两条边的方向相反,第 1 个是2 -> 0
,第 2 个是0 -> 2
,说这样的 2 个三角具有相同的朝向。三角扇两个相邻三角形的朝向一定相同- 如果改变了顶点的顺序,得到的三角扇可能就不是一个方块
有了这些知识下面写一个绘制黑色方块的程序,里面出现了顶点着色器和片段着色器代码,
- ie 只支持
experimental-webgl
- 这里面调用的函数
glProgram
在正式示例中定义,如果要运行需拷贝glProgram
函数上面是注意事项
上面是
gl.drawArrays
,它从用gl.bindBuffer(gl.ARRAY_BUFFER, arr)
绑定的arr
中依次读取每个顶点。gl.drawElements
需要用gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, ids)
额外绑定一个数组作为gl.ARRAY_BUFFER
的索引,这样一来它使用两个数组,arr
保存顶点,ids
保存遍历arr
的顺序。设// vertex 0, vertex 1, vertex 2, vertex 3, ... arr = [ x0, y0, x1, y1, x2, y2, x3, y3, ...] ids = [0, 3, 1, 2]
并且
- 已经调用了两次
gl.bindBuffer
让gl.ARRAY_BUFFER
和gl.ELEMENT_ARRAY_BUFFER
分别对应
arr
和ids
gl.vertexAttribPointer
指出每个顶点是 2 个type
- 索引数组
ids
的元素类型是Uint16
或者说gl.UNSIGNED_SHORT
则
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4)
绘制 4 次顶点,依次是0 - 1 - 2 - 3
gl.drawElements(gl.TRIANGLE_FAN, 4, gl.UNSIGNED_SHORT, 0)
绘制 4 次顶点,它依次读取ids
的每个元素,以元素的值作为arr
的索引去获取顶点,依次绘制arr
的0 - 3 - 1 - 2
我假设读者通过前面的阅读和练习已经理解了
gl.drawArrays
和gl.drawElements
,下面简要介绍设置颜色。前面在片段着色器里硬编码了个颜色,不透明黑,要画 3 个颜色的方块就需要 3 个片段着色器。如果能让片段着色器接受一个 javascript 传入的变量,有点像顶点着色器里面的特性
attribute
,从 javascript 指定颜色,那就可以只写一个片段着色器。出于两个原因,不使用attribute
- 只有顶点着色器可以定义
attribute
,片段着色器不可以- 方块是单色的,不需要像
attribute
那样每个顶点都传一个值着色器程序总共可以定义 3 种变量:
attribute
、uniform
、varying
这里使用
uniform
。varying
用来从顶点着色器往片段着色器传值,当然也可以实现效果。
uniform
的意思是,每次调用gl.drawArrays
绘制一系列的顶点之前,先设置一个在绘制过程中保持不变的值,绘制这些点的过程中,着色器程序可以读取但不能修改该定值。对比attribute
,attribute
对gl.drawArrays
绘制的每 1 个顶点都赋值 1 次;uniform
只在gl.drawArrays
开始前赋值一次。因为
uniform
在 1 次绘制中只赋值 1 次,所以它不从数组里面取值,gl.uniformXxx
用于设置uniform
的值。所以下面的示例中,顶点着色器定义 1 个
attribute
以接受顶点,片段着色器定义 1 个uniform
以接受颜色,调用 3 次gl.drawArrays
以绘制 3 个方块。混合颜色
这里需要把 C=B(Cb,Cs) 换个形式以反映 webgl 的混合方法,换成 C=e(f(Cs),g(Cb)) 。看上去更复杂了,但马上就会发现,它很简单。
e 对应
gl.blendEquation(mode)
,mode
是 3 个枚举值之一
mode e gl.FUNC_ADD
- 默认值e(x,y)=clamp(x+y) gl.FUNC_SUBTRACT
e(x,y)=clamp(x−y) gl.FUNC_REVERSE_SUBTRACT
e(x,y)=clamp(y−x)
这里的 clamp(x) 把 x 限制为 [0, 1],小于 0 的值变成 0,大于 1 的值变成 1。f 和 g 分别对应
gl.blendFunc(sfactor, dfactor)
中的sfactor
和dfactor
,均从下列枚举中取值
factor f gl.ZERO
-dfactor
默认值f(x)=x×0 gl.ONE
-sfactor
默认值f(x)=x×1 gl.SRC_COLOR
f(x)=x×Cs gl.ONE_MINUS_SRC_COLOR
f(x)=x×(1−Cs) gl.DST_COLOR
f(x)=x×Cb gl.ONE_MINUS_DST_COLOR
f(x)=x×(1−Cb) gl.SRC_ALPHA
f(x)=x×αs gl.ONE_MINUS_SRC_ALPHA
f(x)=x×(1−αs) gl.DST_ALPHA
f(x)=x×αb gl.ONE_MINUS_DST_ALPHA
f(x)=x×(1−αb) gl.SRC_ALPHA_SATURATE
f(x)=x×min(αs,αb)
所以,当dfactor
=gl.ONE
、sfactor
取默认值gl.ONE
、mode
取默认值gl.FUNC_ADD
时有
C=e(f(Cs),g(Cb))=clamp(Cs×1+Cb×1)=clamp(Cs+Cb)当 Cs 和 Cb 都是正数时 clamp(Cs+Cb)=min(Cs+Cb,1) ,就是前面用过的 add 混合模式。相应的 js 代码是
gl.blendFunc(gl.ONE, gl.ONE); gl.enable(gl.BLEND); // 必须明显的启用混合
而如果让
sfactor
=gl.ONE_MINUS_DST_COLOR
、dfactor
取默认值gl.ONE
、mode
取默认值gl.FUNC_ADD
,有
C=e(f(Cs),g(Cb))=clamp(Cs×(1−Cb)+Cb)=clamp(Cs+Cb−Cs×Cb)当 Cs 和 Cb 都是正数时 clamp(Cs+Cb−Cs×Cb)=Cs+Cb−Cs×Cb ,就是前面用过的 screen 混合模式。相应的 js 代码是
gl.blendFunc(gl.ONE_MINUS_DST_COLOR, gl.ONE); gl.enable(gl.BLEND); // 必须明显的启用混合
代码
现在的情况是
- 会用
gl.drawArrays(gl.TRIANGLE_FAN, 0, 4);
绘制方块- 知道将要用
uniform
给片段着色器传递一个代表颜色的变量- 知道如何设置混合
下面看具体的代码
<div class=sample> <canvas class=s4 height=180 width=200>canvas> <h4>webgl 混合h4> <script> !function () { var canvas = document.querySelector(".s4"), ch = canvas.height, cw = canvas.width, cc = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"), svs = "attribute vec2 position;" + "uniform vec2 resolution;" + "void main() {" + " vec2 p = position / resolution * 2.0 - 1.0;" + " gl_Position = vec4(p * vec2(1, -1), 0, 1);" + "}", sfs = "precision mediump float;" + "uniform vec3 color;" + "void main() { gl_FragColor = vec4(color, 1); }", rects = [ 50, 20, 50, 120, 150, 120, 150, 20, // red 30, 40, 30, 140, 130, 140, 130, 40, // lime 70, 60, 70, 160, 170, 160, 170, 60 // blue ], buffer = cc.createBuffer(), attrs = { position: 0, }, unifs = { color: 0, resolution: 0 }, program = glProgram(cc, sfs, svs, attrs, unifs); cc.useProgram(program); cc.uniform2f(unifs.resolution, cw, ch); cc.bindBuffer(cc.ARRAY_BUFFER, buffer); cc.vertexAttribPointer(attrs.position, 2, cc.UNSIGNED_BYTE, false, 0, 0); cc.bufferData(cc.ARRAY_BUFFER, new Uint8Array(rects), cc.STATIC_DRAW); //cc.blendFunc(cc.ONE, cc.ONE); cc.blendFunc(cc.ONE_MINUS_DST_COLOR, cc.ONE); cc.enable(cc.BLEND); draw(cc, 0, [1, 0, 0]); draw(cc, 4, [0, 1, 0]); draw(cc, 8, [0, 0, 1]); function draw(gl, offset, color) { gl.uniform3fv(unifs.color, color); gl.drawArrays(gl.TRIANGLE_FAN, offset, 4); } }(); function glProgram(gl, sfs, svs, attrs, unifs) { var prop, i, program = glLink(gl, glCompile(gl, sfs, gl.FRAGMENT_SHADER), glCompile(gl, svs, gl.VERTEX_SHADER)); for (prop in attrs) { i = gl.getAttribLocation(program, prop); attrs[prop] = i; gl.enableVertexAttribArray(i); } for (prop in unifs) unifs[prop] = gl.getUniformLocation(program, prop); return program; function glCompile(gl, source, type) { var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; } function glLink(gl, fs, vs) { var program = gl.createProgram(); gl.attachShader(program, fs); gl.attachShader(program, vs); gl.linkProgram(program); return program; } } script> div>
要点
uniform
不像attribute
那样要调用gl.enableVertexAttribArray
,使用gl.getUniformLocation
获取uniform
在 javascript 中的索引后就能用了js 中的顶点数组使用了大于 1 的整数坐标,这个整数坐标传入顶点着色器后,顶点着色器要根据当前的画布尺寸换算出相应的小数,然后才给
gl_Position
赋值代码包含 add 和 screen 混合模式,就一句代码,add 被注释掉了
全部代码
<html> <head> <meta charset=utf-8> <style> .sample { display: inline-block; vertical-align: top; width: 200px; } style> <title>additive colortitle> head> <body> <div class=sample> <style> .s1 { height: 180px; isolation: isolate; position: relative; } .s1 > div { height: 100px; mix-blend-mode: screen; position: absolute; width: 100px; } .s1 > div:nth-of-type(1) { background-color: red; left: 50px; top: 20px; } .s1 > div:nth-of-type(2) { background-color: lime; left: 30px; top: 40px; } .s1 > div:nth-of-type(3) { background-color: blue; left: 70px; top: 60px; } style> <div class=s1><div>div><div>div><div>div>div> <h4>mix-blend-mode: screen 不是颜色分量相加h4> div> <div class=sample> <svg height=180 width=200> <filter id=s2-composite x=0 y=0 width=1 height=1> <feComposite in2=BackgroundImage operator=arithmetic k1=-1 k2=1 k3=1>feComposite> filter> <filter id=s2-blend> <feBlend in2=BackgroundImage mode=screen>feBlend> filter> <g enable-background=new> <rect width=100 height=100 x=50 y=20 fill=red>rect> <rect width=100 height=100 x=30 y=40 fill=lime filter=url(#s2-composite)>rect> <rect width=100 height=100 x=70 y=60 fill=blue filter=url(#s2-blend)>rect> g> svg> <h4>svg,仅限 ie 10+h4> div> <div class=sample> <svg height=180 width=200> <filter id=s2-2 x=-1 y=-1 width=2 height=2> <feOffset dx=-20 dy=-40>feOffset> <feColorMatrix result=red type=matrix values=" 0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 1 0">feColorMatrix> <feOffset in=SourceGraphic dx=-40 dy=-20>feOffset> <feColorMatrix type=matrix values=" 0 0 0 0 0 0 0 0 0 1 0 0 0 0 0 0 0 0 1 0">feColorMatrix> <feBlend mode=screen in2=red>feBlend> <feBlend mode=screen in2=SourceGraphic>feBlend> filter> <rect x=70 y=60 width=100 height=100 fill=blue filter=url(#s2-2)>rect> svg> <h4>svg feBlendh4> div> <div class=sample> <canvas class=s3-1 height=180 width=200>canvas> <h4>canvas 2d 全局复合h4> <script> !function () { var canvas = document.querySelector(".s3-1"), cc = canvas.getContext("2d"); cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); cc.globalCompositeOperation = "screen"; cc.fillStyle = "lime"; cc.fillRect(30, 40, 100, 100); cc.fillStyle = "blue"; cc.fillRect(70, 60, 100, 100); }(); script> div> <div class=sample> <canvas class=s3-2 height=180 width=200>canvas> <h4>在 canvas 中把颜色分量置 1,未使用 min(x + y, 1)h4> <script> !function () { var canvas = document.querySelector(".s3-2"), cc = canvas.getContext("2d"), i, len, idata, arr; cc.fillStyle = "red"; cc.fillRect(50, 20, 100, 100); for (i = 0, idata = cc.getImageData(30, 40, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 1] = arr[i + 3] = 255; cc.putImageData(idata, 30, 40); for (i = 0, idata = cc.getImageData(70, 60, 100, 100), arr = idata.data, len = arr.length; i < len; i += 4) arr[i + 2] = arr[i + 3] = 255; cc.putImageData(idata, 70, 60); }(); script> div> <div class=sample> <canvas class=s4 height=180 width=200>canvas> <h4>webgl 混合h4> <script> !function () { var canvas = document.querySelector(".s4"), ch = canvas.height, cw = canvas.width, cc = canvas.getContext("webgl") || canvas.getContext("experimental-webgl"), svs = "attribute vec2 position;" + "uniform vec2 resolution;" + "void main() {" + " vec2 p = position / resolution * 2.0 - 1.0;" + " gl_Position = vec4(p * vec2(1, -1), 0, 1);" + "}", sfs = "precision mediump float;" + "uniform vec3 color;" + "void main() { gl_FragColor = vec4(color, 1); }", rects = [ 50, 20, 50, 120, 150, 120, 150, 20, // red 30, 40, 30, 140, 130, 140, 130, 40, // lime 70, 60, 70, 160, 170, 160, 170, 60 // blue ], buffer = cc.createBuffer(), attrs = { position: 0, }, unifs = { color: 0, resolution: 0 }, program = glProgram(cc, sfs, svs, attrs, unifs); cc.useProgram(program); cc.uniform2f(unifs.resolution, cw, ch); cc.bindBuffer(cc.ARRAY_BUFFER, buffer); cc.vertexAttribPointer(attrs.position, 2, cc.UNSIGNED_BYTE, false, 0, 0); cc.bufferData(cc.ARRAY_BUFFER, new Uint8Array(rects), cc.STATIC_DRAW); //cc.blendFunc(cc.ONE, cc.ONE); cc.blendFunc(cc.ONE_MINUS_DST_COLOR, cc.ONE); cc.enable(cc.BLEND); draw(cc, 0, [1, 0, 0]); draw(cc, 4, [0, 1, 0]); draw(cc, 8, [0, 0, 1]); function draw(gl, offset, color) { gl.uniform3fv(unifs.color, color); gl.drawArrays(gl.TRIANGLE_FAN, offset, 4); } }(); function glProgram(gl, sfs, svs, attrs, unifs) { var prop, i, program = glLink(gl, glCompile(gl, sfs, gl.FRAGMENT_SHADER), glCompile(gl, svs, gl.VERTEX_SHADER)); for (prop in attrs) { i = gl.getAttribLocation(program, prop); attrs[prop] = i; gl.enableVertexAttribArray(i); } for (prop in unifs) unifs[prop] = gl.getUniformLocation(program, prop); return program; function glCompile(gl, source, type) { var shader = gl.createShader(type); gl.shaderSource(shader, source); gl.compileShader(shader); return shader; } function glLink(gl, fs, vs) { var program = gl.createProgram(); gl.attachShader(program, fs); gl.attachShader(program, vs); gl.linkProgram(program); return program; } } script> div> body> html>
参考
复合与混合级别 1
http://www.w3.org/TR/compositing/
http://dev.w3.org/fxtf/compositing/
http://dev.w3.org/fxtf/compositing-1/svg 1.1 滤镜
http://www.w3.org/TR/SVG11/filters.html
http://www.w3.org/TR/SVG/filters.html滤镜效果模块级别 1
http://www.w3.org/TR/filter-effects-1/
http://dev.w3.org/fxtf/filters/svg 复合
http://www.w3.org/TR/SVGCompositing/
http://dev.w3.org/SVG/modules/compositing/master/SVGCompositingPrimer.html混合模式
http://en.wikipedia.org/wiki/Blend_modes欢迎使用Markdown编辑器写博客
本Markdown编辑器使用StackEdit修改而来,用它写博客,将会带来全新的体验哦:
- Markdown和扩展Markdown简洁的语法
- 代码块高亮
- 图片链接和图片上传
- LaTex数学公式
- UML序列图和流程图
- 离线写博客
- 导入导出Markdown文件
- 丰富的快捷键
快捷键
- 加粗
Ctrl + B
- 斜体
Ctrl + I
- 引用
Ctrl + Q
- 插入链接
Ctrl + L
- 插入代码
Ctrl + K
- 插入图片
Ctrl + G
- 提升标题
Ctrl + H
- 有序列表
Ctrl + O
- 无序列表
Ctrl + U
- 横线
Ctrl + R
- 撤销
Ctrl + Z
- 重做
Ctrl + Y
Markdown及扩展
Markdown 是一种轻量级标记语言,它允许人们使用易读易写的纯文本格式编写文档,然后转换成格式丰富的HTML页面。 —— [ 维基百科 ]
使用简单的符号标识不同的标题,将某些文字标记为粗体或者斜体,创建一个链接等,详细语法参考帮助?。
本编辑器支持 Markdown Extra , 扩展了很多好用的功能。具体请参考Github.
表格
Markdown Extra 表格语法:
项目 价格 Computer $1600 Phone $12 Pipe $1 可以使用冒号来定义对齐方式:
项目 价格 数量 Computer 1600 元 5 Phone 12 元 12 Pipe 1 元 234 定义列表
- Markdown Extra 定义列表语法:
- 项目1
- 项目2
- 定义 A
- 定义 B
- 项目3
- 定义 C
定义 D
定义D内容
代码块
代码块语法遵循标准markdown代码,例如:
@requires_authorization def somefunc(param1='', param2=0): '''A docstring''' if param1 > param2: # interesting print 'Greater' return (param2 - param1 + 1) or None class SomeClass: pass >>> message = '''interpreter ... prompt'''
脚注
生成一个脚注1.
目录
用
[TOC]
来生成目录:
- 概要
- 问题
- 约定
- 分析
- 不可行的方法
- 可行的方法
- css
- html 中的 svg
- svg
- canvas
- 如何运行示例代码
- 示例 - css mix-blend-mode
- 示例 - svg feComposite 和 feBlend
- 总结
- 示例 - canvas 2d
- 示例 - canvas webgl
- 绘制方块
- 混合颜色
- 代码
- 要点
- 全部代码
- 参考
- 欢迎使用Markdown编辑器写博客
- 快捷键
- Markdown及扩展
- 表格
- 定义列表
- 代码块
- 脚注
- 目录
- 数学公式
- UML 图
- 离线写博客
- 浏览器兼容
数学公式
使用MathJax渲染LaTex 数学公式,详见math.stackexchange.com.
- 行内公式,数学公式为: Γ(n)=(n−1)!∀n∈N 。
- 块级公式:
x=−b±b2−4ac−−−−−−−√2a更多LaTex语法请参考 这儿.
UML 图:
可以渲染序列图:
Created with Raphaël 2.1.0 张三 张三 李四 李四 嘿,小四儿, 写博客了没? 李四愣了一下,说: 忙得吐血,哪有时间写。或者流程图:
Created with Raphaël 2.1.0 开始 我的操作 确认? 结束 yes no
- 关于 序列图 语法,参考 这儿,
- 关于 流程图 语法,参考 这儿.
离线写博客
即使用户在没有网络的情况下,也可以通过本编辑器离线写博客(直接在曾经使用过的浏览器中输入write.blog.csdn.net/mdeditor即可。Markdown编辑器使用浏览器离线存储将内容保存在本地。
用户写博客的过程中,内容实时保存在浏览器缓存中,在用户关闭浏览器或者其它异常情况下,内容不会丢失。用户再次打开浏览器时,会显示上次用户正在编辑的没有发表的内容。
博客发表后,本地缓存将被删除。
用户可以选择 把正在写的博客保存到服务器草稿箱,即使换浏览器或者清除缓存,内容也不会丢失。
注意:虽然浏览器存储大部分时候都比较可靠,但为了您的数据安全,在联网后,请务必及时发表或者保存到服务器草稿箱。
浏览器兼容
- 目前,本编辑器对Chrome浏览器支持最为完整。建议大家使用较新版本的Chrome。
- IE9以下不支持
- IE9,10,11存在以下问题
- 不支持离线功能
- IE9不支持文件导入导出
- IE10不支持拖拽文件导入
- 这里是 脚注 的 内容. ↩