|
|
$
移动到行尾,a;<Esc>
完成一行分号的输入;($a;<Esc>)j
移动到下一行,重复步骤1再来一遍,完成第二行;(j$a;<Esc>)好累,还不如鼠标点呢,vim的哲学就是Don’t repeat yourself,我们要想办法减少一些重复。
在vim里,.
命令是会重复上次修改,我们要构造可重复的修改,于是方法为:
$
移动到行尾,a;<Esc>
完成一行分号的输入;($a;<Esc>)j
移动到下一行,然后$.
,完成第二行;(j$.)可以看到.
命令非常有用,尽量让可以重复的操作变成可以用.
来重复的操作。
回过头来,什么样的才是“修改”呢,x
会删除光标下的字符;dd
删除一行;>G
从当前行到文档末尾缩进,都算得上修改,它们都是在普通模式中执行的命令。 插入模式的修改肯定也是修改啦,不过如何记录的呢?就是从进入插入模式的那一刻起(例如输入i
),直到返回普通模式为止(也就是输入<Esc>或<Ctrl + [ >>,Vim会记录每一个按键操作。
复合命令A
相当于$a
在这里更显得有用了,因为它是可重复的修改:
A
到行尾,;<Esc>
完成一行分号的输入;(A;<Esc>)j
移动到下一行,然后.
,完成第二行;(j.)认识.
命令的魅力,尽量使操作可重复。
<Ctrl + v>
(如果macvim或者gvim则是<Ctrl + q>)进入列块可视模式,jj$
先下移动两行然后跳到行尾;A;
进入插入模式输入分号;这个方法里用了可视模式里列块选择,<Ctrl + v>是进入列块选择,通过移动建立选取后,使用A
和I
进入插入模式编辑文本,然后使用<Esc>退出插入模式,同时结束列块模式,这时插入的文本就会扩散到选区选中的行了。
有意思的是,可以进入列块选择后复制,然后把它贴出来,试试咯。
如果你多输入了一个字符怎么办呢?gv
可以重新选择刚才的选区,再调整一下选中你想删除的字符,删除它。gv
适用于可视模式哦。
:x,ynormal A;
这个方法里用到了命令行模式,在命令行模式中执行的命令又被称为Ex命令。有时使用Ex命令,能比普通模式命令更快地完成同样的工作。
其中:
是进入命令行模式,x,y
就是开始行号x和结束行号y,两者中间加上逗号表示范围;除了行号,其实我们还可以使用正则,比如删除含aotu字符的行到含有labs的行,则可以使用:/aotu/ , /labs/ d
,可以用-
来表示匹配行的前一行,+
表示匹配行的下一行,那么删除匹配行的下一行则应该是:/aotu/+ d
。
normal
就是表示执行正常(noramal)模式下的命令,后面的A;
就很好理解了,在行尾插入分号。如果是m(ove)
移动行,d(elet)
删除行,co(py)
或t
复制行,是不用输入noraml
的。如:1,5 co 100
复制1~5行并把它们粘贴到100行以下。
.
代表当前行;:.,/pattern/ d
会删除当前行到包含pattern的行。
$
代表文件的最后一行;:5, . m $
会移动第5行到当前行移动到文件末尾。
%
代表文件的每一行,等同于1,$
。全文替换到时候常常:%s/foo/bar/g
。
也许有人问,如果这个例子里的行是不联系的,怎么办呢?
你可以使用:x,y g/pattern/normal A;
或者:x,y v/pattern/normal A;
这里的g
就是globally,整个的意思就是x到y行之间匹配pattern的行normal模式执行A;
,而v
则是来自于conVerse的v,所以整个的意思就是x到y行之间不匹配pattern的行normal模式执行A;
。
qa
,然后A;<Esc>
,按q
;j
到第二行上@a
;j
到第三行上@@
。宏记录就是说vim会记录你所操作的序列,然后你可以在记录完成后的任意时间(normal模式下)一键播放录制的操作序列(宏)。
上面的例子,在普通模式中qa
中的q
就是进入宏模式,a
就是这个宏的名字叫做a,宏的名字可以是26个字母中的某一个,大小写都可以,然后可以看到vim状态栏上显示“recording @a”,这时你的所有操作都会被记录。直到你返回会普通模式,然后按q
。
@a
就是播放名字为a的宏,而@@
就是重复播放刚刚播放过的宏。
其实上面为了演示@@
而没有让他们可重复起来,可重复应该是在第1步qaA;<Esc>jq
,然后在第2步2@qa
就直接添加了第2和第3行的分号了。
上面只是大概的四种方法,要细分可能还有一些方法。
最后话说VS code 的vim插件挺好用的,更新也很快。
参考资料:《vim实用技巧》
]]>'1234567'.match(/\d{1,3}/g)
的结果。/^\d{3}/
。/[^abc]/
。["123", "456", "7"]
。/\b(?!th)\w+\b/
。/^(?=.*\d).{4,8}$/
。首先.{4,8} 表示与包含 4-8 个字符的字符串匹配;然后.*表示单个字符(除换行符 \n 外)零次或多次,且后面跟着一个数字,注意(?=)只匹配一个位置。/[\u4e00-\u9fa5]/
。当然,可能答案不唯一,不必较真啦~ 主要目的是回忆熟悉一下语法~
如果还不了解正则,可以前往正则表达式理论篇 了解哇~
要想在复杂性和完整性之间取得平衡,一个重要因素是要了解将要搜索的文本。
好的正则表达式:
有时候处理各种极端情况会降低成本/收益的比例。所以某些情况下,不完全依赖正则表达式完成全部工作,比如某些字段用子表达式()括起来,让内存记忆下来,然后再用其他程序来验证。
不过本文还是从学习正则的角度出发,全部依赖正则表达式来写的哇~~
正则表达式:/^\$[0-9]+(\.[0-9][0-9])?$/
。
分为四部分:
^\$
以美元符号开头。[0-9]+
至少包含一个数字。(\.[0-9][0-9])?
由一个点和两位数组成,匹配0次或1次,因为可能是整数或者是小数。$
最后的$表示以数字结尾的。缺点:不能匹配$1,000
方法一:分类逻辑为第一个数字(0、1、2),可以分为三部分:上午 00点到09点(0可选);白天10到19点;晚上20到23点。
因此有三个多选分支,得到的结果为:
|
|
还可以优化一下,合并前面的两个多选分支,得到:
|
|
方法二:分类逻辑为第二个数字,可以分为两部分:[0-3]和[4-9]。为什么这么分?看看下面这个图就知道了,[0-3]多了一行(以2为第一个数字):
因此有两个多选分支,结果为:
分钟数比较简单,第一个数范围在0-5之间,第二个数在0-9之间,因此得到分钟数为:
|
|
小时部分用(?:)包起来,起到一个分组的作用,且不保存匹配项;
冒号、分钟数拼起来;
最后加上一个分界\b
表示单词的开始或结束,得到最终的结果:
|
|
|
|
其实这个结果不能说完全正确,首先你要明白这个正则用在什么地方,比如是数据验证或者
复杂的字符串搜寻替换。
情景一:填写表单中的字符串必须为24小时制的时间,那么可能第一个\b
需要改成^,第二个\b
改成$
。
情景二:用于复杂的字符串搜寻替换时,可能也会匹配这样子的字符串如’跑步用时19:50’,明显的,’19:50’表示19分50秒,而不是表示24小时制的时间19点50分。
IP地址的规则:点号分开的四个字段,每个字段在0-255之间。
如果一个字段是一个数或两个数,肯定是在0-255的范围内的;
如果三位数,那么以0或者1开头的三位数也是合法的,即000-199。
从上面的陈述中我们就可以得到三个多选分支:
|
|
我们稍微合并一下这三个多选分支,得到:
|
|
我们再来看以2开头的三位数:
第二位数小于5的时候,第三位数范围[0-9]都可以;第二位数等于5的时候,第三位数范围[0-5] ,因此得到两个多选分支:
|
|
前两步合并起来,得到一个字段0-255的表示方法:
|
|
四个字段合并起来,IP地址正则如下:
|
|
点号要转义一下,^和$需要加上,否则可能匹配52123.3.22.993,因为其中的123.3.22.99是符合的。(?:)起到分组的作用,且不保存匹配项。
一些测试结果:
|
|
虽然0.0.0.0是合法的,但它是非法的IP地址,使用正则的否定顺序环视功能(零宽负向先行断言),可加上(?!0+.0+.0+.0+$) :
|
|
/*
和*/
之间的css注释。描述:起始分隔符和结束分隔符都是"
,且正文中容许出现转义之后的引号\"
。
简单情况分析:
举例:匹配类似 I "start \"x3\" end" U
文本的 "start \"x3\" end"
引文字符串,注意\"
属于转义引号。
"
。[^"]
表示不是引号的其他任意字符。"
前面有反斜线\,且被反斜线\转义,则也属于正文。例如start\"
引号的前面有一个反斜线,那么这个引号也属于正文。即(?<=\)”表示匹配一个引号,它的前面有一个\
,注意正则的反斜线也要用\
来转义一下,因为\是特殊字符。用非捕获分组(?:)
将[^"]|(?<=\\)"
括起来,给个量词*
,表示匹配正文0次或多次。
因此可以写出正则表达式: /"(?:[^"]|(?<=\\)")*"/
注意:ES7才支持逆序环视(?<=)
验证正则:/"(?:[^"]|(?<=\\)")*"/
|
|
为什么第2个才是对的呢?我们看一下返回的input属性就了解了:
验证正则:/"(?:[^"]|(?<=\\)")*"/
|
|
引号”前面有反斜线\,但是这个反斜线不是转义引号的,那么引号就不应该属于正文,而是属于结束分隔符。
什么情况反斜线\不转义引号呢?
这个反斜线\本身就是被转义的情况。
上面的结果按照预期结果应该返回 [""start\"x3\\"]
,但是现在多了end"
。
因此验证这个正则表达式不正确。
也就是说,正文中可出现转义的字符,因此得出正则\\.
,注意第一个\
表示转义第二个\
,点表示匹配除换行符 \n 之外的任何单个字符),例如可以匹配\+
或者\\
。而且转义的字符已经包含了\"
的情况,因此正则(?<=\\)"
可以不用写了,且替换成\\.
。
因此改正后的正则:/"(?:\\.|[^"])*"/
你可能注意到了,我把[^”]和\.的位置调换一下,后面的验证3会讲到为什么要这么做。
验证正则:/"(?:\\.|[^"])*"/
和 /"(?:[^"]|\\.)*"/
|
|
[^"]
和\\.
的位置调换后,结果与期望不符合。那是因为[^"]
匹配start \
后,遇到紧接着的"
不匹配,交给后面的多选分支\\.
,也不匹配,又刚好结束分隔符是"
,导致匹配成功,结束匹配。
因此两个正则之间 正确的正则是 /"(?:\\.|[^"])*"/
验证:/"(?:\\.|[^"])*"/
|
|
上面的字符串 "start\"x3\"
其实是没有结束分隔符的,但是还是匹配了。那是因为正则[^"]
和\\.
一起作用,导致匹配到了文本U末尾,后续想找结束分隔符的时候,结果却找不到,所以只能回溯文本去找结束分隔符,最后找到了 x3\
后面的引号,匹配成功,结束匹配。
回溯会导致不期望的结果,由于是卡在多选分支上出错的,因此猜测多选分支|
匹配内容出现重叠。
你想想,如果符合正文的反斜线,不是以[^"]
方式匹配,而是以\\.
的方式匹配,那就不会把好好的\"
拆开来匹配了。
综上所述,一定要让反斜线是以\\.
的方式匹配,字符串里的反斜杆不能以[^"]
方式匹配。
因此将[^"]
改成[^\\"]
。这样子就可以确保正确识别正文特殊的\"
和结束分隔符"
了。
注意:很多字符在[]都会失去本来的意义,但是反斜杠字符 \
仍为转义字符。若要匹配反斜杠字符,请使用两个反斜杠 \\
。
改正的正则:/"(?:\\.|[^\\"])*"/
验证:/"(?:\\.|[^\\"])*"/
|
|
为了优化,我们可以把[^\\"]
放在前面,因为普通字符的匹配可能性更大。
注意:优化正则提高效率最需要考虑的问题:改动是否会影响匹配。只有在排序与匹配成功无关时才不会影响准确性,才能重新安排多选分支的顺序。
优化后的正则:/"(?:[^\\"]|\\.)*"/
经历了容许引文字符串中出现转义引号的例子分析,瞬间觉得这个容易了许多。
描述与要求:匹配类似<input name=123 value=">" >
的HTML标签,起始分隔符是<
,结束分隔符是>
,且HTML 标签属性值中可以出现>
。
起始分隔符和结束分隔符都是明确的,我们来分类一下正文。
可能你会当心单双引号引用文本,会像“容许引文字符串中出现转义引号”那么复杂。幸好是HTML Tag的属性值中不允许出现转义引号,因为平常的转义符号\
变成了普通字符。
根据三种情况,分别写出三个正则:
"[^"]*"
'[^']*'
[^'">]
好了,用多选分支连起来"[^"]*"|'[^']*'|[^'">]
,再用非捕获分组(?:)将多选分支括起来,如(?:"[^"]*"|'[^']*'|[^'">])
,用*
表示匹配任意次,最后前后加上开始结束分隔符,搞定:
/<(?:"[^"]*"|'[^']*'|[^'">])*>/
验证:
|
|
看到没有,几乎每个正则都包含多选分支,只要你懂得将数据分类,离成功就不远了。哈哈哈哈哈。
讲真,做前端越久,我们就越容易被思维所束缚。比如,应该没几个人会相信用1K代码能够写出一个游戏,而且还是3d的游戏。
在看这段代码之前我们不得不提到一个一年一度的比赛:js1k。每年,主办方会提出一个比赛主题,参赛者们必须围绕这个主题,用1024个字节以内的JS代码做一个参赛作品。比赛的要求有以下几个:
Create a fancy pancy JavaScript demo (用JS做出一个华丽的demo)
Submissions may be up to 1024 bytes (最多1024字节)
Externals are strictly forbidden (禁止引用外部资源)
Must work current generation browsers (必须能在现代浏览器中运行)
Minification and hacks allowed (允许代码压缩或者hack)
另外,作为基础,demo运行的环境中内置了一部分变量供调用:
window.a
是一个canvas元素
window.b
是document.body
window.c
是a元素对应的2D/3D上下文
window.d
是document对象
是不是还挺贴心?只需要用一个字母就可以调用原本一长串代码才能拿到的对象喔。
除了这些,比赛还有一些小规则,可以查看比赛的 规则页 ,里面有详细描述,这里就不再多说。
今年比赛的主题是 Let’s get eleMental!,我们今天要看的 Firewatch 就是其中第三位的作品(第一名作品是 Romanesco 2.0 3D分形展示,有强烈的不明觉厉感,建议前往围观;第二名 Voxeling 是一个3D像素Demo)。
游戏通过键盘上下左右+空格键操作。在开始游戏后,面前会有一颗烟花炸开。紧接着烟花下的树开始着火。玩家的任务就是按下空格,用水枪将火扑灭。在一定时间后,树上的火还会点燃周围的树。如果不慎走神,很有可能就救不回这片森林了喔。
粗略地讲,这份源码总共就分为两个部分:
下面是创建对象部分的代码:
|
|
为了减少代码占用的空间,作者大量使用了单字母的变量名与属性名,比如entities.c
,entities.x
等等,代码可读性非常差。
浏览一遍,我们是根本看不出上面这些变量是什么含义了。
但是,我们能够确定的是,这段代码初始化了playerA
,playerX
,playerZ
变量,然后把所有的对象设定好属性后都存入了entities
数组中,包括树干,树底下的水果,还有树干等等。没办法,只能先跳过这部分了。
这是代码中主管用户交互的代码:
|
|
蛤?就这么短?
…
真是这么短。
上面这段代码,看着更像是定义了onkeydown跟onkeyup俩函数,后面理应还有类似于addEventListener之类的语句将这个函数绑定给某个元素某个事件的代码。因为身为前端,我们早已习惯绑定事件的那几种套路:
|
|
可是任凭老司机怎么 Ctrl+f 搜索代码,也并没有找到所谓的绑定事件的语句。
事实上,作者在定义这两个函数的时候,并没有使用var关键字。意味着,这后面定义的变量,是直接挂在window下的。那么凭空定义的这两个函数,就分别会变成 window.onkeydown
和 window.onkeyup
。没错,这就是我们极度不推崇的绑定回调的方式了。
回调函数的内容,是在用户按下按键的时候将burn中下标为 keyCode - 32
的属性设定为 keydown
或者 keyup
的第六个字母,也就是 w
或者 undefined
。这个做法十分精妙,下面举例说明。
假设我们按下了空格键(keyCode=32),执行 onkeydown
函数,burn[0] = 'w'
;而放手的时候,则执行 onkeyup
函数, burn[0] = undefined
。这样一来,在事件循环里面只需要判断burn[0]
是true
还是false
,就可以知道空格键按下了没有。同理,burn[5]
,burn[6]
,burn[7]
,burn[8]
分别代表了左,上,右,下键的状态。
可是,印象中burn是个函数呀,entities[30].p = burn = function (e, f) {...}
,怎么就用来表示按键状态了呢?目的只能有一个,省下一个声明变量的语句。
为了省下多几个字符,程序员真是什么事都做得出来啊…
终于到了事件循环部分了。这部分代码占了源码大概一半的篇幅。折叠后,代码结构大概是这样的:
结构大概清晰了:首先是玩家的移动,接着是水枪的处理,紧接着是背景天空森林地板的渲染。
每个事件循环一开始,先是移动玩家。
理解了用户交互部分的内容,这里就很好理解了。burn[5]与burn[7]分管左右两键,那么playerA
很明显就是用户的朝向。而burn[6]与burn[8]分管前后,在经过换算后,我们很快也就能知道playerX
与playerZ
分别是玩家在x-z平面上的坐标。别忘了这游戏可是3D的,所以并不用x-y坐标系。
接下来是水枪的代码:
|
|
只要空格被按下,就会有新的对象被插入entities
数组中。不过既然是水枪的代码,我们很容易可以想到,这里插进去的对象正是水流。
水流浇到火上是可以灭火的,所以下面这段带有一堆abs的代码,十有八九是用来判断水流跟火焰的位置关系的。
|
|
后面可以证明我们的猜测并没有错。另外,这段代码最后的&&符号后面的代码,就是灭火的处理函数了。
遍历数组,我们一般是用forEach
,而作者在这个例子中全都是用some
来做的。我猜作者并没有别的用意,纯粹是因为长度的原因。所以我觉得这里使用map
应该更好。
接下来是一些零散的处理。
|
|
首先是平移了画布,其次是遍历对象数组,执行对象的p函数。这样一来就很清晰了。对象的属性p,就是在每个事件循环中处理对象的函数。
|
|
接下来,作者巧妙地用循环 + hsla渲染了一个带渐变色的天空。
|
|
这段代码过滤掉了对象数组中h属性不大于0的对象。
回想一下,我们前面看到的每种对象的p函数,都带着h–,而p函数在每个事件循环都会执行。那么这个h属性表示的就是对象的寿命了。这个事件循环的间隔是33ms,也就是30h相当于1秒。
|
|
接下来的这部分,前面的画背景与后面的画地板还是跟前面一样的套路,分别使用循环来绘制带渐变色的森林与地板。可是中间这段是什么鬼?
根据注释说明,这个部分是将Z属性设置为对象到屏幕的距离,随后做了一次从近到远排序。
至于对象到屏幕的距离如何计算,这里直接画图说明,就不再赘述。
|
|
接下来的这段代码就是这个游戏的灵魂所在了。
这是一个遍历entities数组的操作。以 ||
符号分割,这个操作可以分为三个部分:
!f.v && f.Z > 160
f.Z < 8
Math.abs(...) < 160 && (...)
语句1跟2只要有一个成立,语句3就不会被执行。语句1跟2也很好理解,主要是判断对象跟屏幕之间的距离是不是处在(8, 160)的区间内,如果不是就直接跳过该对象。
这里有一个例外情况,在距离大于160的情况下,如果对象带有v属性则还是可以继续后面的判断的。纵观整个代码,带有v属性的只有前面提到的烟雾以及烟花。这就说明烟雾与烟花就算是在160距离开外,依然是可以看到的,事实上也的确如此。
语句3被 &&
符号分割为了两部分:
Math.abs(e = (f.x - playerX) * Math.cos(playerA) * 160 / f.Z - (f.z - playerZ) * Math.sin(playerA) * 160 / f.Z) < 160
(c.fillStyle='...',c.fillRect(...))
语句1中的比较,转换为代数式,经过简单的代数变换后,可以变为:
dx*cos(α) - dz*sin(α) < f.Z
结合刚刚的图,这个语句意义就是指,线段a需要小于f.Z。而从图上我们可以知道,线段a就是对象投影在屏幕上后与玩家的距离。这样一来,视角90°以外的对象就不会被渲染了。
语句2就是渲染对象的语句了。看到这里的fillStyle
函数我们才明白,前面所定义的f.c
属性,其实就是定义了对象颜色的hsl值。另外,从fillRect
函数的传参情况来看,也很容易看出f.y
指的就是对象的y坐标,f.s
表示对象的宽高,而f.S
则是在绘制树干的时候作为对象的高度来使用。
现在我们对这段代码有大概的理解了,是时候回头看一看了。
首先是创建对象的部分,此处开了两重循环,种下了256课树,每棵树分别有一根树干,12个对象组成的叶子,另外地上有6个果实。
接下来,第30棵树被恶意纵了火(将f.p
设置为burn
函数),第一棵着火的树还会发出一个烟花作为信号。而由于每一帧最后都有s--
语句,这个s
被作为一个计数器,让着火的树每16帧发出一个烟圈。
过后,就是熟悉的与用户交互的环节了。这部分已经十分清晰,就不再赘述。
这段代码虽然短,却是麻雀虽小五脏俱全。实现了游戏的基本功能不说,有一些小细节也是让我大吃一惊。比如树着火时冒出的烟,在普通情况下是深灰色的,而在被水枪浇到的时候,会变成浅灰色;又比如寿命的设定,使树叶在着火之后一段时间会被烧完。
在编程思想上,作者也是很有见地。使用entities数组存储所有对象的信息,给每种对象一个变换函数,这本身就有粒子的思想在其中。此外,setInterval
的使用,则是再正常不过的事件循环机制的实现。
编程手法就不用多说了。比如各种利用some
函数代替forEach
来省字数,又或者是利用burn[e.keyCode - 32] = e.type[5]
来判断按键的状态……作者是老司机,这一点是没跑了。
这段代码构思巧妙,思路行云流水…类似的溢美之词说再多也没用。更关键的是,我们干前端这一行的,绝对不只是jQ,选择器,或者node小工具。偶尔看一看别人的代码,还是能够学到很多意想不到的知识的。
2016年即将过去,回顾总结项目的时候,发现日常工作中一些值得我们思考的地方,这次选了印象比较深刻的京东云项目,希望本文能对读者有所帮助,文多图少,一目百行的客官请轻虐。
由于项目的紧迫性,项目进度基本处于多线并行追赶的状态,产品、视觉、前端三个线边输出边交接,产品和视觉会把重要的框架性的页面先输出给到前端,这样一来,前端得到的交互文档、视觉稿基本是阶段性的,并没有完整的项目逻辑和项目页面可以参考来评估项目的构建方案,项目的公用模块、公用组件、组件管理方式等全局性的内容难以确定,类似这些项目中各种不能确定的信息,我称之为『变数』。『变数』会给构建页面过程带来很多不可预测的问题,这些问题可能会令一部分工作推倒或翻工。
面对这种时间线短、各线并行、存在『变数』的项目,主动获取项目信息尤其重要,个人觉得比较重要的有以下几个:项目整体信息结构、整体的时间线规划、视觉稿的具体输出日期、视觉交接 Deadline、前端开发的周期、前端交接 Deadline、项目各线对接负责人。
这次项目属于改版类,从旧版大概了解到站点频道划分、频道之间逻辑关系、业务模块的引用情况等信息,这次改版产品的逻辑变化不大,主要是视觉的优化。虽然前期视觉给到我们的视觉稿只有站点首页,频道的首页和频道的内页都还在并行进行中,但从旧版站点逻辑和产品交互侧得到的信息以及已出的视觉稿可以确定站点哪些部分可以作为公用组件去制作,也可以大致确定项目的资源目录分布。
对于旧站点常见的组件而在已出视觉稿未体现到的,如侧导航弹浮层,找到产品和交互进一步了解后,得知会在频道首页和内页会用到,亦可以确认作为公用组件制作,并可以提前为这些组件制作评估工作量并选择最佳的实现方案。
整体的时间线规划、视觉稿的具体输出日期、视觉交接 Deadline、前端开发的周期以及前端交接 Deadline,这些信息都是项目时间维度上的信息,这类信息的确认,可以让我们更准确评估工作量(在时间线内是否可以完成所有工作),从而在项目紧急性方面做好更合理的人力投入。
我们接到新项目,一般都会考虑以上的因素,主动去获取项目的信息,这些信息都是显而易见的,但有些信息略显隐蔽。
拿到设计稿的时候,发现视觉设计师只提供了一份 1280 分辨率的宽屏版,国内分辨率使用占比名列前 5 的 1024 分辨率并没有兼容,带着疑问找到视觉确认,得知首页会有 1280 分辨率和 1024 分辨率两个版本,适配 1024 分辨率版本会迟一点给到。
对于这种看似不是问题的问题而前期又可以确认的问题,我是一定要去确认的!这种问题在前期看上去并不会成为项目推进的阻力,但是说不定会在项目后期爆发,对项目造成吨暴击伤害。
记得刚入行的时候就亲身经历过联调阶段调整分辨率兼容问题,产品说『我的笔记本电脑首页出现横向滚动条了』,然后去排查了一下,发现产品用了 1024 分辨率,而视觉给到前端的视觉稿只兼容到 1280 分辨率,当时缺少实际项目经验,就默默按照 1280 去做了。当产品找到视觉,视觉回答『立项的时候文档没有写到分辨率兼容情况呀,所以当时只做了宽屏1280了』,最后产品『还是兼容一下吧,做一个窄版适配吧』,再然后视觉和前端就只能『……』。
这种问题两条线人员很容易懵B『O嘴』,而且最终还是需要重新调整适配 1024 分辨率,视觉和前端两条线都需要额外的工时,而这些工时并不在前期规划内,所以最有可能会令到视觉或前端或两者都加班,如果加班在可控的时间内完成的话还算是亡羊补牢,但如果加班后还是影响到项目进展造成项目 delay 的话,那就不是影响视觉和前端两条工作线工作效率这么简单了,这是一种潜在的『变数』,应该如何避免这种『变数』的出现,非常值得我们去思考。
如果前端可以从立项就开始参与和产品、交互、视觉、开发各线大前期沟通的话,每个岗位的同事都可以就各自的专业去衡量评估项目,是不是能更早地去发现一些潜在的『变数』呢?而去执行实现的话是否需要在流程上做出改动?如果因为团队的合作模式不能实现,作为前端,我们是不是应该更主动去找到各线的同事进行沟通?
当然,这仅仅是针对此情况而去考虑的东西,其实我想提出的是,面对『项目变数』我们需要做的就是尽可能及时掌握更多关于项目的任何信息去深入了解项目,对项目未来发展作出预见性的判断从而掌握主动权,而不是静静地等待进入前端流程。项目可见度越高,准备越充分,项目主动性越强,只有 100% 的项目信息确认,才能 Kill the 『潜变数』Tens of Thousands Times
。
首页的设计稿包含了很多单色图标,图标复用性很强,同一个图标出现在不同的模块,而且大小又不一样,又需要多态并且兼容 Retina 高清屏。
站在前端角度考虑,PC平台的解决方案,第一时间肯定会想到 iconfont 图标。iconfont 图标已不是什么新鲜的技术,不少同事都在不少项目中都有应用到,可是大家在遇到同一问题使用相同技术的时候,处理方法亦有不同。有的同事可能会自己制作 iconfont 图标;有的同事可能会让设计师提供图标 SVG 文件,利用线上的工具自动生成 iconfont 图标;有的同事可能会把 iconfont 的生成工具介绍给视觉设计师,让视觉设计师生成 iconfont 图标。
站在视觉角度考虑,当然想图标在所以设备都可以高清呈现。不同的设计师经验不一样,有些可能会提供 SVG 文件,有些可能会提供一套1X,2X图,有些甚至会把 3X 图也一起给到前端
当项目同时由多人维护的时候,就可能会出现同一个项目同样的的技术方案出现不一样的协作方式,一旦人员交叉交接的时候,就有意思了。从协作效率上来看是存在一定问题的,因此很有必要让前端和视觉设计师统一一套解决 iconfont 图标的方案。
如何实行? 个人觉得,最重要的是让大家对问题达成共识,也就是:解决这个问题于工作有何收益?
首先需要站在视觉方角度让他们知道问题痛点,再提出解决方案并希望得到他们的协作,下面我以对话的形式将当时我和视觉沟通的对话内容简单叙述一下(前端:FE,视觉:VD):
FE:啊宏,这些单色图标数量挺多的呀,复用率又高,很多还有双态,同一个图标还有不一样的尺寸,全部做成图片的话数量很多哇,即使全部合并成 sprite 图,图片的 K 数也会很大呢。最要命的是图标后续不知道还会有多少种色态会扩展,首页还要求适配高清屏呢,这工作量不是一般的小呀。
VD:确实啊,高清屏适配每一个图标我都要给一个 2X 图你,10个图标我就得给你 20 个,如果有双态的话,就得给你40个,这次首页纯色图标就有50个。。。
FE:嗯,会出人命的,主要这些图标都是纯色图标,都做成图片不值得,用 iconfont 图标可以解决,全矢量,还可以用样式控制尺寸大小和颜色,高清屏、尺寸、配色都不是问题呀,省力省时间呢!
VD:666,那这个 iconfont 图标要怎么个搞法啊?我需要做些什么呢?
FE:只需要把图标做成 SVG 文件提供给我就 OK 了呀,网上有 SVG 转 iconfont 图标的服务哈
VD:没问题呀,如果能解决刚才说的问题
FE:是啊是啊!主要是这样处理的话可以节省大家的时间,效率成倍提高的啊,不过生成 iconfont 图标后有些图标会在 windows 显示出问题,有问题的图标需要你用 Ai 再处理一下哟,没问题吧?
VD:没问题啊,总比切一堆图好吧,哈哈~~
FE:好的,那么我们约定好,以后这一类纯色图标我们都统一用 iconfont 图标处理,由视觉这边提供图标的 SVG 文件,然后前端负责生成 iconfont 图标,我会在组内推一下这个处理 iconfont 的流程,麻烦你在视觉组内也推一下这个方法哈,都统一这么处理,看OK么?
VD:好的没问题~~ 6666 ~~~
FE:6666~~
项目最后一共投入了5个人力完成后期的构建,成功统一使用了这个 iconfont 图片处理流程,避免了一大波 icon 的蹂躏:
痛点定位和效率收益是这次协作成功推动的关键点,个人觉得要成功推动一个方案实施,『痛点』及『收益点』必定是核心点。以上仅仅是一个简单的方案推动例子,真正操作起来也不会太困难,但关键是我们需要从每次的工作收益中沉淀点『好西』出来就很好了。
好了,以上就是我在京东云改版项目中对工作流程以及上下线协作中感受比较深的一些思考和总结。其实问题都很常见,也有很多更好的解决方案,但是要将这些问题和解决方案存在的价值转化出来,还是需要我们做完每一次的项目后认真去总结沉淀,最后,做好业务无非几个字,想得周全,助得快乐,踩得深入,跳得出来。
]]>MDN 中的介绍:
animation-delay CSS 属性定义动画于何时开始,即从动画应用在元素上到动画开始的这段时间的长度。
该属性值默认为 0s,可为正值,也可为负值。
由于 css3 动画没有时间轴,animation-delay
最常见的是用于将动画与其他动画的执行时机错开,将动画落到不同的时间点,形成动画时间轴。
形成的时间轴如下图所示:
css3 animation 亦可实现一些 js 的效果,例如利用 animation-delay
可以实现一个简单的轮播。以下是一个三屏轮播的例子。
See the Pen KNvRxZ by Yetty (@Yetty) on CodePen.
多个元素使用相同的动画效果时,将动画执行时机依次错开,可形成整齐有序的序列动画效果。
See the Pen listAni by Yetty (@Yetty) on CodePen.
以笔者开发的京东2017海外招聘项目为例,第二屏的菜单和第三屏的时间轴的进退场动画都运用了序列动画。下图展示第三屏时间轴的进场效果,有兴趣的同学亦可扫码观看完整案例。
animation-delay
可为负值。负值会让动画从它的动画序列中某位置立即开始。 巧用这个负值,可以解决实际开发中的一些问题。
如若上述的序列动画要进行无限循环,单纯将 animation-iteration-count
设置为 infinite
,动画开始时会有延迟。此时,将 animation-delay
设置为负值,提前动画开始执行的时机,当用户看到动画时,动画便已经处于进行中的状态。
See the Pen listAniInfinite by Yetty (@Yetty) on CodePen.
将 animation-play-state
设置为 paused
,animation-delay
设置成不同的负值,可以查看动画在不同帧时的状态,便于进行动画调试。
|
|
See the Pen listAniPaused by Yetty (@Yetty) on CodePen.
MDN 中的介绍:
animation-fill-mode 这个 CSS 属性用来指定在动画执行之前和之后如何给动画的目标应用样式。
animation-fill-mode
应该算是 animation
属性里比较难上手的一个,但它的作用却很大。
“动画结束后,突然跳回第一帧!” 很多刚接触 css3 动画的同学,都是在这个场景下,接触了 animation-fill-mode
属性。将 animation-fill-mode
设置为 forwards
,动画执行结束后保持最后一帧的样式。
See the Pen MbmvQL by Yetty (@Yetty) on CodePen.
开发动画时,我们都是先根据视觉稿做好构建,再来给元素加动画的。如上文所述,可通过 animation-delay
来延迟的动画的执行。而在执行前,元素往往需要先隐藏(translate
定位到视窗外 / opacity
设置为 0 / scale
设置为 0 等)。若将隐藏元素的样式直接应用到元素上,一来不利于构建,二来对于不支持动画的浏览器来说,只会呈现一片空白。此时,animation-fill-mode
的 backwards
属性值便派上用场。
对于 backwards
的解释,笔者见过不少文章的说法都有不妥之处,认为 backwards
与 forwards
相反,表示动画执行结束后保持第一帧的样式。实则不然,我们看下 w3c 的解释:
backwards:在 animation-delay 所指定的一段时间内,在动画显示之前,应用开始属性值(在第一个关键帧中定义)。
换句话说,backwards
作用的是 animation-delay
的时间段,应用第一个关键帧的样式。
See the Pen YpVxpw by Yetty (@Yetty) on CodePen.
当然,动画的第一帧和最后一帧的计算还受 animation-direction
和 animation-iteration-count
的影响,MDN 中有详细解释:
forwards
animation-direction animation-iteration-count last keyframe encountered normal even or odd 100% or to reverse even or odd 0% or from alternate even 0% or from alternate odd 100% or to alternate-reverse even 100% or to alternate-reverse odd 0% or from backwards
animation-direction first relevant keyframe normal or alternate 0% or from reverse or alternate-reverse 100% or to
既然上表中涉及了 animation-direction
属性,那我们就顺着来研究一下它。
MDN 中的介绍:
animation-direction CSS 属性指示动画是否反向播放。
动画元素有进场动画,往往也会需要退场动画。比较常见的做法,退场时使用与进场动画反向的动画。animation-direction
的 reverse
属性值可简单实现反向动画。
先看MDN 中的介绍:
reverse:反向运行动画,每周期结束动画由尾到头运行。
|
|
|
|
See the Pen YpQqKZ by Yetty (@Yetty) on CodePen.
当然,上述例子为了演示方便,只是简单做了只有两帧的动画,这种效果用 transition
同样可以实现。
MDN 中的介绍:
animation-play-state CSS 属性定义一个动画是否运行或者暂停。
在做翻页 h5 时,需要对动画的播放进行控制。只有当用户进入当前屏时,动画才开始播放。通常我们会给当前屏加上一个 acitve
类,用来给元素添加动画:
或者如上文“进/退场动画复用”中的例子,分别用 on
和 off
控制进/退场动画。这都是常见的思路。
如果是不需要重复触发的动画,用 animation-play-state
同样可以实现动画的控制。动画属性直接添加到元素上, animation-play-state
默认设置为 paused
,当进入当前屏时,将 animation-play-state
设置为 running
即可。
See the Pen vymWwE by Yetty (@Yetty) on CodePen.
在前文介绍 animation-delay
时,提到了一个轮播的例子,当用户 hover
时,轮播动画应该暂停,用 animation-play-state
属性便可轻松实现交互:
MDN 中的介绍:
CSS animation-timing-function 属性定义 CSS 动画在每一动画周期中执行的节奏。
关于 animation-timing-function
,有一个特别需要注意的点,MDN 中有强调:
对于关键帧动画来说,timing function 作用于一个关键帧周期而非整个动画周期,即从关键帧开始开始,到关键帧结束结束。
也就是说,animation-timing-function
是作用于 @keyframes
中设置的两个关键帧之间的,这一点在该属性值为 steps()
时可明显感知。
animation-timing-function
最让人感到惊(beng)艳(kui)的莫过于 steps()
属性值。利用 steps()
,可以轻松实现逐帧动画(又称“精灵动画”),从而告别不可控的 gif 时代。
关于逐帧动画,笔者之前在凹凸实验室平台已经发布过相关文章介绍,此处不再赘述,有兴趣的同学可前往围观:《CSS3逐帧动画》。
参考文章:
]]>Welikesmall 是一个互联网品牌宣传代理,这是我见过的最喜欢使用 SVG 做动效的网页设计团队。事实上,越来越多的网页动效达人选择在 SVG 的疆土上开辟动效的土壤,即便 SMIL 寿终正寝,事实上这反而将 SVG 动效推向了一个新的世界:CSS3 Animation + SVG。
(SMIL is dead! Long live SMIL! A Guide to Alternatives to SMIL Features)
还记得我在久远的《以电影之眼看 CSS3 动画》中说道:“CSS3 动画简直拥有了整个世界!”那么带上 SVG 的 CSS3 动画则已突破天际向着宇宙级的可能性前进(感觉给自己挖了一个无比巨大的坑,网页动画界可不敢再出新技术了[扶额])。
CSS 与 SVG 的打通无疑将 html 代码的可读性又推上一个台阶,我们可以通过 CSS 控制 SVG 图形的尺寸、填色、边框色、过渡、移动变幻等相当实用的各种属性,除此之外,将图形分解的动画在这种条件下也变得相当简单。
本文将讲到三个动效例子:
动效来源:WLS-Adobe
即将聊到的 SVG 标签:
<path>
<g>
<symbol>
<defs>
<use>
<clipPath>
<mask>
以及属性:
viewBox
preserveAspectRatio
fill
stroke
stroke-dasharray
stroke-dashoffset
d
clip-path
mask
要做出这样的效果,第一步是将图形画出来。徒手敲代码这种事还是留给图形工具来做,但是,为了更好地控制与制作动效,咱至少要做到读懂 SVG 代码。
SVG 的基本格式是使用 <svg>
标签对代码进行包裹,可直接将代码段插入 html 中,也可以保存成 svg 文件之后使用 img
、object
进行引用。
|
|
由于交互动效所需,这里仅介绍直接使用 svg
标签的情况。
|
|
这是箭头的代码段,使用了最简单的线条进行绘制。可以看到其中包裹了许多坐标样的属性值。有坐标就意味着有坐标系。
SVG 的坐标系存在三个概念:视窗、视窗坐标系、用户坐标系。视窗坐标系与用户坐标系属于 SVG 的两种坐标系统,默认情况下这两个坐标系的点是一一对应的。与 web 其他坐标系相同,原点位于视窗的左上角,x 轴水平向右,y 轴垂直向下。
(图片来源:MDN-SVG Tutorial-Positions)
SVG 的位置、大小与文档流中的块级元素相同,都可由 CSS 进行控制。
视窗即为在页面中 SVG 设定的尺寸可见部分,默认情况下 SVG 超出隐藏。
SVG 能通过 viewBox 属性就完成图形的位移与缩放。
viewBox属性值的格式为(x0,y0,u_width,u_height),每个值之间用逗号或者空格隔开,它们共同确定了视窗显示的区域:视窗左上角坐标设为(x0,y0)、视窗的宽设为 u_width,高为 u_height;这个变换对整个视窗都起作用。
下图展示了当 viewBox 尺寸与 SVG 尺寸相同、放大一倍、缩小一倍时的表现:
一句话总结,就是用户坐标系需要以某种方式铺满整个视窗。默认的方式是以最短边为准铺满,也就是类似 background-size 中的 cover 值。通过 preserveAspectRatio
属性你可以控制用户坐标系的展开方式与位置,完美满足你的各种需求。
preserveAspectRatio 是一個以對齊為主,然後再選擇要自動填滿還是咖掉的屬性。——引用来源《SVG 研究之路 (23) - 理解 viewport 與 viewbox》
属性的语法如下:preserveAspectRatio="[defer] <align> [<meetOrSlice>]"
注意3个参数之间需要使用空格隔开。
defer
:可选参数,只对image
元素有效,如果image
元素中preserveAspectRatio
属性的值以defer
开头,则意味着image
元素使用引用图片的缩放比例,如果被引用的图片没有缩放比例,则忽略defer
。所有其他的元素都忽略这个字符串。meetOrSlice:可选参数,可以去下列值:
meet
- 默认值,统一缩放图形,让图形全部显示在 viewport 中。slice
- 统一缩放图形,让图形充满 viewport,超出的部分被剪裁掉。
align:必选参数。由两个名词组成。
這兩個名詞分別代表 viewbox 與 viewport 的 x 方向對齊模式,以及 y 方向的對齊模式,換句話說,可以想成:「水平置中 + 垂直靠上對齊」的這種感覺,不過在這個 align 的表現手法倒是很抽象,可以用下方的表格看出端倪:
也因此我們要做一個「水平置中 + 垂直靠上對齊」的 viewbox 設定,就必須寫成:xMidYMin,做一個「水平靠右對齊 + 垂直靠下對齊」的 viewbox 設定,就必須寫成:xMaxYMax,不過這裡有個細節請特別注意,「Y」是大寫呀!真是不知道為什麼會這樣設計,我想或許跟命名規則有關吧!
下图诠释了各种填充的效果:
(图片来源:7 Coordinate Systems, Transformations and Units)
在这一层面处理好图形的展示之后,剩下的所有变换,无论是 translate、rotate 还是 opacity,我们都可以全权交给 CSS 来处理,并且可以将图形细化到形状或者路径的层面进行变换。
然而实际情况是,刚才的那段代码,放进codepen之后是什么也看不见的,原因就在于这个路径的绘制既没有填充颜色也没有描边。
fill
fill
属性用于给形状填充颜色。
|
|
填充色的透明度通过 fill-opacity
设置。
fill-rule
用于设置填充方式,算法较为抽象,除了inherit
这个取值,还可取以下两种值:
nonzero
:这个值采用的算法是:从需要判定的点向任意方向发射线,然后计算图形与线段交点的处的走向;计算结果从0开始,每有一个交点处的线段是从左到右的,就加1;每有一个交点处的线段是从右到左的,就减1;这样计算完所有交点后,如果这个计算的结果不等于0,则该点在图形内,需要填充;如果该值等于0,则在图形外,不需要填充。看下面的示例:
evenodd
:这个值采用的算法是:从需要判定的点向任意方向发射线,然后计算图形与线段交点的个数,个数为奇数则改点在图形内,需要填充;个数为偶数则点在图形外,不需要填充。看下图的示例:
然而我们发现,我们的箭头即使填充了颜色,还是什么也看不见,问题就出在我们绘制的时候使用了没有面积的 line
标签。这个时候,就需要出动描边了。
stroke
这个 stroke
可得大书特书,因为光是这个 stroke
就能搞定80%的描线动效。
直接通过 stroke
设置描边色,我们就能立刻看到刚才的箭头了。通过 stroke-width
则可以对描边的粗细进行修改。
|
|
stroke-dasharray
(敲黑板)王牌属性出现辣!
这个属性的属性值是1到 n 个数字,多个数字由逗号隔开,CSS 中的定义则由空格分开,每个数字定义了实线段的长度,分别是按照绘制、不绘制这个顺序循环下去。
下面是设置了1个、2个、3个数字时虚线的描绘情况对比:
stroke-dashoffset
(敲黑板)这个也是重点属性!
当我们将描边虚实设置成实线部分与图形描边长度相同时,我们是看不到空白段的部分的。这时形状的描边就像完全描绘出来了一样。这时我们使用这个属性,将虚线开始的位置稍微做一下移动,无论是往前移还是往后移,我们都能看到图形描边出现了一段空白,当这个移动形成一个连续的动作时,描线动效就这么不经意的出现了(蓦然回首)。
|
|
再对头部做个延时处理,修改一下虚线移动的方向,动效看起来会更顺眼一些。这个时候,SVG 可以分路径编辑的优势就体现出来了。对每个 line
添加一个类,我们就能对每条路径进行差异化处理(Codepen)。
|
|
|
|
了解了这两个重点属性,动效剩下的重担,就落在了 dasharray 与 dashoffset 值的计算上了。这个步骤或许没有什么捷径,简单的直线、弧线之类的或许还能口算口算,其余的不规则图形也就只有多试这条傻路可走,如果你是图形高手就当我没说。
另外三个描边属性:stroke-linecap
、stroke-linejoin
、stroke-miterlimit
由于暂时用不上惨遭抛弃,具体可参考MDN-SVG Tutorial-Fills and Strokes,stroke-miterlimit
详解SVG 研究之路(16)- Stroke-miterlimit。
箭头的绘制只用到了路径中最简单的直线路径 line
,SVG 中还有矩形 rect
、圆形 circle
、椭圆 ellipse
、折线 polyline
、多边形 polygon
以及万能的路径 path
。之所以将一些规整的图形单独出标签,是为了代码的可读性更强些,毕竟 SVG 的可读性已经没那么强了……
规整图形的属性较好理解(具体可参考MDN-SVG Tutorial-Path),这里深入讲解一下如何阅读路径 path
的代码。
这组指令的参数代表的是绝对坐标。假设当前画笔所在的位置为(x0,y0),则下面的绝对坐标指令代表的含义如下所示:
移动画笔指令
M
,画直线指令:L
,H
,V
,闭合指令Z
都比较简单;下面重点看看绘制曲线的几个指令。
A
rx ry x-axis-rotation large-arc-flag sweep-flag x y用圆弧连接2个点比较复杂,情况也很多,所以这个命令有7个参数,分别控制曲线的的各个属性。下面解释一下数值的含义:
rx,ry 是弧所在的椭圆的半长轴、半短轴长度,rx 为 x 轴上的轴长,ry 为 y 轴上的周长。
x-axis-rotation 是此段弧的顺时针旋转角度,负数代表逆时针转动的角度。
large-arc-flag 两个值:1
或0
。1
表示大角度弧线,0
代表小角度弧线。
sweep-flag 两个值:1
或0
。1
代表从起点到终点弧线绕中心顺时针方向,0
代表逆时针方向。
x,y 是弧终端坐标。
为了更好的理解圆弧的绘制,我们来试试手动画一下 MDN 上的范例:
|
|
首先是 M
和 L
指令:
然后是 A
指令的绘制,在这一步可以看到 large-arc-flag(大小弧)与 sweep-flag(弧度方向)值的影响。
在本例中,弧度标记值为0
,意味着选择小弧;弧度方向标记值为1
,意味着选择起点到终点为顺时针方向的那条弧(别眨眼):
接下来我们省略掉 L
指令的绘制,来看看下一个圆弧。这个圆弧的旋转角度(x-axis-rotation)发生了变化,体会一下差异:
看了这么久,是不是挺纳闷这么难看的东西为什么一定要读懂?其实也不是强求各位看官能成为脑补 SVG 图形的天才,只是大概读懂这些难看的数字,在做动画的时候才会心里有底手上有劲点,至少大概知道这条东西画出来是什么样,而后再针对它写写动效。所以,我们继续看看图形界的万金油——贝塞尔曲线吧~!
……贝塞尔曲线被广泛地在计算机图形中用来为平滑曲线建立模型。贝塞尔曲线是矢量图形文件和相应软件(如 PostScript、PDF 等)能够处理的唯一曲线,用于光滑地近似其他曲线。二次和三次贝塞尔曲线最为常用。
引用来源:维基百科——贝塞尔曲线——应用
维基上有详细的贝塞尔曲线绘制公式与动图展示,这里就不做展开。
path
中的贝塞尔曲线指令共有四个:C
,S
,Q
,T
。SVG 只提供了最高阶到三次的贝塞尔曲线绘制指令,事实上大部分绘图软件也是如此。
C
x1 y1, x2 y2, x y (或者 c
dx1, dy1, dx2, dy2, dx dy)三次贝塞尔曲线有两个控制点,就是(x1,y1)和(x2,y2),最后面(x,y)代表曲线的终点。
这个时候还是上动图比较省心。以下面的代码段为例:
|
|
绘制过程如下:
(手残,顺滑绘制过程请还是参考维基君。)
借助 PS 中的钢笔工具根据辅助线能迅速画出路径,可以免去那抽象的计算过程。
S
x2 y2, x y (或者 s
dx2 dy2, dx dy)很多时候,曲线不止一个弧,为了平滑过渡,第二个曲线的控制点常常是第一个曲线控制点在曲线另外一边的映射点。这个时候可以使用这个简化版本。
这里要注意的是,如果
S
指令前面没有其他的S
指令或C
指令,这个时候会认为两个控制点是一样的,退化成二次贝塞尔曲线的样子;如果S
指令是用在另外一个S
指令或者C
指令后面,这个时候后面这个S
指令的第一个控制点会默认设置为前面的这个曲线的第二个控制点的一个映射点。——《突袭 HTML5 之 SVG 2D 入门2 - 图形绘制》
这里重点讲解一下 S
指令中每个点对应的位置。同样借用 MDN 上的示例:
|
|
Q
x1 y1, x y (或者 q
dx1 dy1, dx dy)经历了三次贝塞尔曲线的洗礼,二次贝塞尔曲线看起来真是亲切。
|
|
注:PS 中的钢笔工具绘制二次贝塞尔曲线只能通过三次贝塞尔曲线进行模拟,或许二次贝塞尔曲线最准确的绘制方法就是通过代码了吧。这里有一个可视化 Canvas 绘制贝塞尔曲线的网站——Canvas Quadratic Curve Example,实现方式比 SVG 还复杂[抠鼻]。
T
x y(或者 t
dx dy)与 S
指令类似,为了更顺滑的多弧曲线,T
指令直接指定曲线终点,控制点自动计算。
同时,如果 T
指令只在上一个指令为 Q
或者 T
指令的情况下有效,否则当作 L
指令执行。
终于把贝塞尔讲完了……
偷偷用一个箭头把 SVG 的填色、描边、路径都给讲完了,然而,SVG 能用到的还不止这些。开玩笑,Web 界的猪——浑身都是宝——可不是吹的。
首先,我们观察一下这个播放键的结构的实现方法(Codepen)(注:为了能直观地看到效果,我将 .play-icon-blend 的填充与描边改为了黑色,原例子中为白色):
|
|
|
|
这里顺带用了一下下 CSS3 的滤镜 mix-blend-mode
(SVG 也有滤镜功能,这里不做介绍,感兴趣的可以移步《突袭 HTML5 之 SVG 2D 入门10 - 滤镜》)。这里用到的值 exclusion
的效果,是在叠加区域只显示亮色,下面是使用了同样滤镜的图片与正常图片的对比图,感受一下:
图片来源:CSS-Tricks-mix-blend-mode
可以看到代码里还出现了一些了不得的标签—— <defs>
、<use>
。接下来,我们就来了解一下它们。
首先我们知道,通过中间圆圈的缩放,再加上外围圆圈与中心三角的叠加效果,完成了这个 hover 效果。也就意味着,圆圈在这里用到两次。这个时候就可以使用 SVG 里路径的重用与引用功能。
三种集合标签:<g>
、<symbol>
、<defs>
,都是用于将零散的图形组合成一个整体。区别在于:
<g>
:组合标签。添加 id
属性来作为引用的钩子,可在 <g>
标签上设置这组元素的相关属性(填色、描边等等)。<symbol>
:模板标签。与 <g>
标签一样,通过 id
进行引用。不同点在于,symbol
元素本身不会被渲染;symbol
元素拥有属性 viewBox
和 preserveAspectRatio
,这些允许 symbol
缩放图形。<defs>
:定义标签。不仅仅是图形对象的合集,还可以是渐变效果、蒙版、滤镜等等,设置好 id
,在对应的属性(例如渐变就是 fill
、蒙版就是 mask
、滤镜就是 filter
)中引用即可,引用格式为“url(#id)
”。具体例子参看《SVG 研究之路 (18) - 再談 defs》。更详细的区别见《突袭 HTML5 之 SVG 2D 入门7 - 重用与引用》。
以上三种集合的引用统一使用 <use>
标签。xlink:href
属性指定引用的 id
。
use
元素的作用过程就相当于把被引用的对象深拷贝一份到独立的非公开的 DOM 树中;这棵树的父节点是use
元素。虽然是非公开的DOM节点,但是本质上还是 DOM 节点,所以被引用对象的所有属性值、动画、事件、 CSS 的相关设置等都会拷贝多来并都还是会起作用,而且这些节点也会继承use
元素和use
祖先的相关属性(注意引用元素是深拷贝,这些拷贝过来的元素与原来的元素已经无关系了,所以这里不会继承被引用元素祖先节点的属性),如果这些节点本身有相关(CSS)属性,还会覆盖继承来的属性,这些与普通的DOM节点是一致的,所以对use元素使用“visibility:hidden
”时要小心,并不一定会起作用。但是由于这部分节点是非公开的,在 DOM 操作中,也只能看到use
元素,所以也只能操作到use
元素。
在 SVG Sprite 中,<use>
的使用比较猖狂(《拥抱 Web 设计新趋势:SVG Sprites 实践应用》,同时也提到了 SVG 的兼容情况),而当 SVG 图形代码与引用部分分离开时,想针对图形中的某一部分进行处理就会显得特别麻烦(只能看到 use
结点),这个时候,打开 shadow DOM 的显示,包你一览无余(具体操作方法见《神奇的 Shadow DOM》)。
打开了 shadow DOM 显示的 use
标签
下面就来看一个非图形引用的例子。在前面我们知道了,如果要描边动效,那修改 stroke-dashoffset
就可以达到效果。然而这种方法本身就是利用了虚线的 hack,如果我们想要做一个虚线的描线动效呢?比如:
这个时候 stroke-dasharray
与 stroke-offset
的合作是无法完成的,因为他俩动起来本身就是虚线在移动。所以我们需要换个思路,描线动画还是那个描线动画,只是虚线的绘制需要使用另一个 hack —— 蒙版。
SVG 中的蒙版有两种——剪裁cliping <clipPath>
与遮罩mask <mask>
,都需要在 <defs>
中定义,然后通过对应的属性进行引用。
注:以上代码为了直观体现两者的使用方法,已剔除其余不相干代码,不可直接运行。
剪裁与遮罩的区别在于,剪裁是按照定义的形状界限分明地进行图像的展示与隐藏:
而遮罩相较于剪裁,多了渐变显示图像的功能,只要在 <mask>
中包裹渐变的定义即可。遮罩的展示策略是:
越黑越透明,越白越不透明,而遮色片(注:即遮罩)只有黑到白的灰階分布,所以如果作為遮色片的顏色是灰階以外的顏色,都會被轉換為灰階。——引用来源《SVG 研究之路 (9) - Clipping and Masking》
因此遮罩的功能其实是包含剪裁的,当遮罩使用的是纯黑的图像时,功能等同于剪裁。
虚线的描线动效结合剪裁或者遮罩即可以完成(Codepen):
|
|
|
|
注意到上方使用了遮罩的集合里多了一个方形图像,是因为遮罩对于图形尺寸的要求更加严苛,line
在它的眼里不是东西,不提供任何效果支持,但是一旦加个方形垫背,line
就被接受了[翻白眼]。所以涉及到切割的蒙版,请尽量使用 clipPath
。
写到这里,阿婆主气数已尽,SVG 是个深坑,这里也只能借着三个例子扯扯若干特性,等下回心情好了,阿婆主再拎几个出来说说(也是任性,人的 SVG 笔记都是一个属性一篇的)。下面我们来给这篇凌乱的文章做个梳理:
<path>
<g>
<symbol>
<defs>
<use>
<clipPath>
<mask>
viewBox
preserveAspectRatio
fill
stroke
stroke-dasharray
stroke-dashoffset
d
clip-path
mask
stroke-dasharray
、stroke-dashoffset
<defs>
、<use>
<clipPath>
、<mask>
、clip-path
、mask
、stroke-dasharray
、stroke-dashoffset
文中引用到的资料(前方高能预警):
「FIGlet」是基于ASCII
字符组成的字符画。它的名字由来,其实有几个固定的名称,几种比较主要的称呼分别是:「ASCII TEXT」、「ASCII TEXT Banner」、「ASCII Art」、「ASCII Decorator」和「FIGlet」。然而「FIGlet」这个词应该是最标准的用法,因为维基百科上有相关介绍 ( https://en.wikipedia.org/wiki/FIGlet )。「FIGlet」原作者是Glenn Chappell、Ian Chai,源于 Frank、Ian 和 Glen当初在写邮件签名的时候加上去的一些字体艺术。 ( 參考:http://www.figlet.org/figlet_history.html)
「FIGlet」在1991年春天,仅仅是一个只有170行用C语言编写的小程序,当时叫做newban
,作者认为它是一个可以长期使用于互联网中的程序,然后用带“新”的英文不太合适,于是后来称之为FIGlet 1.0
。当时的版本只有一个字体并且仅仅包含了一些小写字母。
于是1993年的时候,决定重写「FIGlet」,花了7个月的时间整理了13个字体以及相关文档,因此FIGlet 2.0
正式版也正式诞生。
当时「FIGlet」像暴风一样席卷互联网,每周都有新的字体涌现,不少人自愿把figlet这个功能接入到non-Unix
当中,当然还有一些bug的报告。因为这些从而使得「FIGlet」更加国际化,所以在FIGlet 2.1
中加入了支持非ASCII字符,还有从右到左打印等功能。直至今天最新版本是2.2.5,可以从官方网站找到相关代码的下载地址。
接下来就是介绍生成这样的字符图形的几种方式:
除了字体,其实也有不少图片转为ASCII的工具,有兴趣的可以参考一下 ASCII Art,或者是这个网站:http://picascii.com/,都是通过程序帮我们实现。可以看一下picascii.com这个网站转出来的效果
目前一些比较流行的编辑器如Sublime
、VSCODE
、Atom
等都有相关的插件帮助我们生成字符图形,例如在VSCODE中在插件管理,搜索“VSC Figlet”
并安装
然后在MAC下通过快捷键command+shift+p
调出令命面板,输入查找“VSC Figlet”
然后输入根据提示,输入您想要转换的字符
选择一种字体效果
最终效果如下
作为一名高逼格的前端工程师,想必看一下Node.js里面有没有相关的模块,于是去npm上搜了一把,结果更新频率比较高的,有这个名叫figlet的npm包
|
|
简单示例
|
|
输出
|
|
也可以直接安装命令行
|
|
这里输入凹凸实验室出品-前端自动化流程构建工具「Athena」的名称看看
|
|
输出效果
效果是不是跟官方配图很像。
如果想直接做一个类似的在线转换工具,可以参考一下网页版本http://patorjk.com/software/taag/,里面主要用到figlet.js
。
|
|
看到这里,这绝对不是一篇软文,相信你至少能从文章中学习到一点小知识。
如果你还不知道轻氧是什么鬼,那末可以先移步这里:轻氧 - 2016年末最新款互联网专业资讯 APP,或者直接关注「凹凸实验室」的公众号以获取更多轻氧APP的最新讯息。
在轻氧上一个版本(响尾蛇)推出的时候,我们在实验室的公众号做了一次小范围的推广,总用户数虽然谈不上惊喜,但也超出了一开始的期望,且每日俱增。
朋友们所给的评论和意见,不管褒贬我们都有记录,而其中呼声最高的安卓版本,我们已优先列入开发计划,技术选型上我们决定尝试使用阿里的weex,支持国货,人人有责。
「看看资讯,玩玩牌」,我们其实就是想给「轻氧」加一个神秘好玩的功能,类似远古时代的「占卜」,通过用户主动给予它的一些交互引出一些未知的功能,这个版本叫「塔罗牌」,打开APP,相信你们就知道怎么玩。
实在不会玩的童鞋可以看下玩法流程:
APP将玩法最终呈现给用户的是一张张卡片UI,所以我们把每次推送给用户的活动称为「卡牌」。每个卡牌具有不一样的玩法功能和营销意义。
卡牌的出现可以增加一些有趣的逻辑,例如带频道限制的卡牌 - 适用于在不同频道摇出不同的卡片;带时间段限制的卡片 - 可以在不同的时间段、节日摇出不一样的卡片。。。
而至于卡牌内容的设计,嘿嘿,我们可以很乖巧,也可以很邪恶,可以很文艺清新,也可以风骚YD,可以很地方随意,也可以很官方严肃,总之我们努力满足各方要求,不管你是宅男还是欲女!
在1.4.0版本中,我们只准备了有限的3张卡牌,大伙儿先提前尝尝鲜吧。
成熟性感与完美,需要多给我们一点调教的时间。
目前仅提供ios版本
基于 Weex 的 Android 版与网页版正在紧急开发中,敬请期待。
官网地址:
https://app.aotu.io
扫二维码:
问题反馈:
希望在这个信息爆炸的时代,「轻氧」能够帮助你聚焦更优质的资讯内容。
也让你更加专注于用心被创造出来的文章,和其创造者。
正则表达式
regular expression
缩写 regexp 、regex 、egrep。
正则表达式包括普通字符(例如,a 到 z 之间的字母)和特殊字符(称为“元字符”)。
若要匹配这些特殊字符,必须首先转义字符,即,在字符前面加反斜杠字符 \
**。
例如,若要搜索 “+”文本字符,可使用表达式 \+
。
但是大多数 特殊字符 在中括号表达式内出现时失去本来的意义,并恢复为普通字符。
|
|
替换文本中的$字符有特殊含义:
如:
|
|
非全局检索:如果没有找到任何匹配的文本返回null;否则数组的第一个元素是匹配的字符串,剩下的是小括号中的子表达式,即a[n]中存放的是$n的内容。非全局检索返回三个属性:length 属性;index 属性声明的是匹配文本的第一个字符的位置;input 属性则存放的是被检索的字符串 string。
全局检索:设置标志g则返回所有匹配子字符串,即不提供与子表达式相关的信息。没有 index 属性或 input 属性。
非全局检索:与String.macth()非全局检索相同,返回一个数组或null。
全局检索:尽管是全局匹配的正则表达式,但是exec方法只对指定的字符串进行一次匹配。但是可以反复调用来实现全局检索。在 RegExpObject 的lastIndex 属性指定的字符处开始检索字符串;匹配后,将更新lastIndex为匹配文本的最后一个字符的下一个位置;再也找不到匹配的文本时,将返回null,并把 lastIndex 属性重置为 0。
如:
RegExpObject.test()
参数:字符串。
返回:true或false。
RegExpObject.toString()
返回:字符串
|
指示在两个或多个项之间进行选择。类似js中的或,又称分支条件。/
正则表达式模式的开始或结尾。\
反斜杠字符,用来转义。-
连字符 当且仅当在字符组[]的内部表示一个范围,比如[A-Z]就是表示范围从A到Z;如果需要在字符组里面表示普通字符-,放在字符组的开头或者尾部即可。
.
匹配除换行符 \n 之外的任何单个字符。\d
等价[0-9],匹配0到9字符。\D
等价[^0-9],与\d
相反。\w
与以下任意字符匹配:A-Z、a-z、0-9 和下划线,等价于 [A-Za-z0-9]。\W
与\w相反,即 [^A-Za-z0-9]
显示限定符位于大括号 {} 中,并包含指示出现次数上下限的数值;*+?
这三个字符属于单字符限定符:
{n}
正好匹配 n 次。{n,}
至少匹配 n 次。{n,m}
匹配至少 n 次,至多 m 次。*
等价{0,}+
等价{1,}?
等价{0,1}
注意:
*
和+
:javascript默认是贪婪匹配,也就是说匹配重复字符是尽可能多地匹配。?
:当进行非贪婪匹配,只需要在待匹配的字符后面跟随一个?
即可。
|
|
^
匹配开始的位置。将 ^
用作括号[]
表达式中的第一个字符,则会对字符集求反。$
匹配结尾的位置。\b
与一个字边界匹配,如er\b 与“never”中的“er”匹配,但与“verb”中的“er”不匹配。\B
非边界字匹配。
中括号[]
字符组;标记括号表达式的开始和结尾,起到的作用是匹配这个或者匹配那个。[...]
匹配方括号内任意字符。很多字符在[]
都会失去本来的意义:[^...]
匹配不在方括号内的任意字符;[?.]
匹配普通的问号和点号。
注意:反斜杠字符 \
在[]
中仍为转义字符。若要匹配反斜杠字符,请使用两个反斜杠 \\
。
另外不要滥用字符组这个失去意义的特性,比如不要使用[.]
来代替\:
转义点号,因为需要付出处理字符组的代价。
大括号{}
标记限定符表达式的开始和结尾。
小括号()
标记子表达式的开始和结尾,主要作用是分组,对内容进行区分。
(模式)
可以记住和这个模式匹配的匹配项(捕获分组)。不要滥用括号,如果不需要保存子表达式,可使用非捕获型括号(?:)
来进行性能优化。(?:模式)
与模式 匹配,但不保存匹配项(非捕获分组)。(?=模式)
零宽正向先行断言,要求匹配与模式 匹配的搜索字符串。 找到一个匹配项后,将在匹配文本之前开始搜索下一个匹配项;但不会保存匹配项。(?!模式)
零宽负向先行断言,要求匹配与模式 不匹配的搜索字符串。 找到一个匹配项后,将在匹配文本之前开始搜索下一个匹配项;但不会保存匹配项。
有点晕?
好,换个说法。。。
先行断言(?=模式)
:x只有在y前面才匹配,必须写成/x(?=y)/
。 解释:找一个x,那个x的后面有y。
先行否定断言(?!模式)
: x只有不在y前面才匹配,必须写成/x(?!y)/
。 解释:找一个x,那个x的后面没有y。
稳住,又来了两个断言,来自ES7提案:
后行断言(?<=模式)
:与”先行断言”相反, x只有在y后面才匹配,必须写成/(?<=y)x/
。解释:找一个x,那个x的前面要有y。
后行否定断言(?<!模式)
: 与”先行否定断言“相反,x只有不在y后面才匹配,必须写成/(?<!y)x/
。 解释:找一个x,那个x的前面没有y。
可以看出,后行断言先匹配/(?<=y)x/的x,然后再回到左边,匹配y的部分,即先右后左”的执行顺序。
零宽负向先行断言的例子:
|
|
\n
表示引用字符,与第n个子表达式第一次匹配的字符相匹配。反向引用的例子,给MikeMike字符后加个单引号:
|
|
\s
任何空白字符。即[ \f\n\r\t\v]
\S
任何非空白字符。\t
Tab 字符(\u0009)。\n
换行符(\u000A)\v
垂直制表符(\u000B)。\f
换页符(\u000C)\r
回车符(\u000D)。
注意:\n
和\r
一起使用,即 /[\r\n]/g
来匹配换行,因为unix扩展的系统以\n
标志结尾,window以\r\n
标志结尾。
\cx
匹配 x 指示的控制字符,要求x 的值必须在 A-Z 或 a-z 范围内。\xn
匹配n,n 是一个十六进制转义码,两位数长。\un
匹配 n,其中n 是以四位十六进制数表示的 Unicode 字符。
\nm
或 \n 先尝试反向引用,不可则再尝试标识为一个八进制转义码。\nml
当n 是八进制数字 (0-3),m 和 l 是八进制数字 (0-7) 时,匹配八进制转义码 nml。
i
执行不区分大小写的匹配。g
执行一个全局匹配,简而言之,即找到所有的匹配,而不是在找到第一个之后就停止。m
多行匹配模式,^匹配一行的开头和字符串的开头,$匹配行的结束和字符串的结束。ES6新增u和y修饰符:
u
修饰符含义为“Unicode模式”,用来正确处理大于\uFFFF的Unicode字符。也就是说,会正确处理四个字节的UTF-16编码。
|
|
y
修饰符与g修饰符都是全局匹配,不同之处在于:lastIndex属性指定每次搜索的开始位置,g
修饰符从这个位置开始向后搜索,直到发现匹配为止;但是y
修饰符要求必须在lastIndex指定的位置发现匹配,即y
修饰符确保匹配必须从剩余的第一个位置开始,这也是“粘连”的涵义。
|
|
\
转义符(), (?:), (?=), []
括号和中括号*、+、?、{n}、{n,}、{n,m}
限定符^、$、\
定位点和序列|
替换JS 是 NFA 引擎。
NFA 引擎的特点:
|
|
举个贪婪与回溯结合的例子:
|
|
匹配顺序如图所示:
MDN
w3school
http://es6.ruanyifeng.com/#docs/regex
http://imweb.io/topic/56e804ef1a5f05dc50643106
http://www.cnblogs.com/deerchao/archive/2006/08/24/zhengzhe30fengzhongjiaocheng.html
http://www.cnblogs.com/hustskyking/p/how-regular-expressions-work.html
XCEL 基于 Electron 和 Vue 2.0 进行开发,充分利用 Electron 多进程任务处理等功能,使其拥有高性能、跨平台(windows 7+、Mac 和 Linux)的特性。
落地页:https://xcel.aotu.io/ ✨✨✨
项目地址:https://github.com/o2team/xcel ✨✨✨
用户研究的定量研究和轻量级数据处理中,均需对数据进行清洗处理,用以剔除异常数据,保证数据结果的信度和效度。目前因调研数据和轻量级数据的多变性,对轻量级数据清洗往往采取人工清洗,缺少统一、标准的清洗流程,但对于调研和轻量级的数据往往是需要保证数据稳定性的,因此,在对数据进行清洗的时候最好有可以标准化的清洗方式。
结合用研组的需求,我们利用 Electron 和 Vue 的特性对该工具进行开发。
纸上得来终觉浅,绝知此事要躬行
如果对某项技术比较熟悉可略读/跳过。
Electron 是一个能让你通过 JavaScript、HTML 和 CSS 构建桌面应用的框架。这些应用能打包到 Mac、Windows 和 Linux 电脑上运行,当然它们也能上架到 Mac 和 Windows 的 app stores。
通常来说,桌面应用都需要用每个操作系统对应的原生语言进行开发。这意味着需要拥有 3 个团队为这个应用编写 3 个相应的版本。Electron 则允许你通过 web 语言编写一次即可。
Electron 结合了 Chromium、Node.js 和用于调用操作系统本地功能的 API(如打开文件窗口、通知、图标等)。
基于 Electron 的开发,就好像开发一个网页一样,而且能够无缝地 使用 Node。或者说:就好像构建一个 Node app,并通过 HTML 和 CSS 构建界面。另外,你只需为一个浏览器(最新的 Chrome)进行设计(即无需考虑兼容性)。
Electron 有两个种进程:『主进程』和『渲染进程』。有些模块只能工作在其中一个进程上,而有些则能工作在两个进程上。主进程更多地充当幕后角色,而渲染进程则是应用的每个窗口。
PS:可通过任务管理器(PC)/活动监视器(Mac)查看进程的相关信息。
dialog
模块拥有所有原生 dialog 的 API,如打开文件、保存文件和弹窗。主进程,通常是一个命名为 main.js
的文件,该文件是每个 Electron 应用的入口。它控制了应用的生命周期(从打开到关闭)。它能调用原生元素和创建新的(多个)渲染进程,而且整个 Node API 是内置其中的。
渲染进程是应用的一个浏览器窗口。与主进程不同,它能存在多个(注:一个 Electron 应用只能有一个主进程)并且是相互独立的。它们也能是隐藏的。它通常被命名为 index.html
。它们就像典型的 HTML 文件,但在 Electron 中,它们能获取完整的 Node API 特性。因此,这也是它与其它浏览器不同的地方。
在 Chrome(或其它浏览器)中的每个标签页(tab) 和其内的页面,就好比 Electron 中的一个单独渲染进程。如果你关闭所有标签页,Chrome 依然存在,这好比 Electron 的主进程,而且你能打开一个新的窗口或关闭这个应用。
注:一般情况下,在 Chrome 浏览器中,一个标签页(tab)中的页面(即除了浏览器本身部分,如搜索框、工具栏等)就是一个渲染进程。
尽管主进程和渲染进程都有各自的任务,但它们之间也有需要协同完成的任务。因此它们之间需要通讯。IPC就为此而生,它提供了进程间的通讯。但它只能在主进程与渲染进程之间传递信息。
Electron 应用就像 Node 应用,它也依赖一个 package.json
文件。该文件定义了哪个文件作为主进程,并因此让 Electron 知道从何启动你的应用。然后主进程能创建渲染进程,并能使用 IPC 让两者间进行消息传递。
至此,Electron 的基础部分介绍完毕。该部分是基于我之前翻译的一篇文章《Essential Electron》,译文可点击 这里。
目前,该工具应用了 Vue、Vuex、Vuex-router。在工具基本定型阶段,由 1.x 升级到了 2.0 (Vuex 暂未升级)。
对于我来说:
Vue 1.x -> Vue 2.0 的版本迁移用 vue-migration-helper 即可分析出大部分需要更改的地方。
网上已经有很多关于 Vue 的信息了。至此,Vue 部分介绍完毕。
该库支持各种电子表格格式的解析和生成。它由纯 JavaScript 实现,适用于前端和 Node。详情>>
支持读入的格式有:
支持写的格式有:
只要能提供读(解析)和写,剩下的就是靠 JavaScript 处理解析出来的数据(JSON)了。目前该库提供了 sheet_to_json
方法,该方法能将读入的 Excel 数据转为 JSON 格式。由于导出时需要提供特定的 JSON 格式,因此这部分需要我们自己实现。
更多关于 Excel 在 JavaScript 中处理的知识可关注:凹凸实验室的《Node读写Excel文件探究实践》。但该文章存在两处问题(均在 js-xlsx 实战的导出表格部分):
String.fromCharCode(65+j)
生成,但列大于 26 时就会出现问题。这个问题会在后面章节中给出解决方案;原来的:
改为:
实践是检验真理的唯一标准
在理解上述知识的前提下,下面就谈谈一些在实践中总结出来的技巧、难点和重点。
Excel 单元格采用 table
展示。在 Excel 中,被选中的单元格会高亮相应的『行』和『列』,以提醒用户。在该应用中也有做相应处理,横向高亮采用 tr:hover
实现,而纵向呢?这里所采用的一个技巧是:
假设 HTML 结构如下:
CSS 代码如下:
如图:
分割线可以通过 ::after/::before
伪类元素实现一条直线,然后通过 transform:rotate();
旋转特定角度实现。但这种实现的一个问题是:由于宽度是不定的,因此需要通过 JavaScript 运算才能得到准确的对角分割线。
因此,这里可以通过 CSS 线性渐变 linear-gradient(to top right, transparent, transparent calc(50% - .5px), #d3d6db calc(50% - .5px), #d3d6db calc(50% + .5px), transparent calc(50% + .5px))
实现。无论宽高如何变,依然妥妥地自适应。
26列
时会产生问题(如:第 27
列,String.fromCharCode(65+26)
得到的是 [
,而不是 AA
)。因此,这需要通过『十进制和26进制转换』算法来实现。
|
|
|
|
Electron 为 File 对象额外增了 path 属性,该属性可得到文件在文件系统上的真实路径。因此,你可以利用 Node 为所欲为😈。应用场景有:拖拽文件后,通过 Node 提供的 File API 读取文件等。
Electron 应用在 MacOS 中默认不支持『复制』『粘贴』等常见编辑功能,因此需要为 MacOS 显式地设置复制粘贴等编辑功能的菜单栏,并为此设置相应的快捷键。
|
|
Electron 的一个缺点是:即使你的应用是一个简单的时钟,但它也不得不包含完整的基础设施(如 Chromium、Node 等)。因此,一般情况,打包后的程序至少会达到几十兆(根据系统类型进行浮动)。当你的应用越复杂,就越可以忽略这部分了。
众所周知,页面的渲染难免会导致『白屏』,而且这里采用了 Vue 框架,情况就更加糟糕了。另外,Electron 应用也避免不了『先打开浏览器,再渲染页面』的步骤。下面提供几种方法来减轻这种情况,以让程序更贴近原生应用。
对于第一点,若程序的背景不是纯白(#fff)的,那么可指定窗口的背景颜色与其一致,以避免突变。
对于第二点,由于 Electron 本质是一个浏览器,需要加载非网页部分的资源。因此,我们可以先隐藏窗口。
等到渲染进程开始渲染页面的那一刻,在 ready-to-show
的回调函数中显示窗口。
对于第三点,我并没有实现,原因如下:
其实现方式,可参考《4 must-know tips for building cross platform Electron apps》。
在渲染进程中调用原本专属于主进程中的 API (如弹框)的方式有两种:
remote 模块:该模块提供了一种在渲染进程(网页)和主进程之间进行进程间通讯(IPC)的简便途径。
|
|
如果 Electron 应用没有了自动更新的功能,那么意味着用户想体验你新开发的功能或用上修复 Bug 后的新版本,只能靠自己主动地去官网下载,这无疑是糟糕的体验。Electron 提供的 autoUpdater 模块可实现自动更新功能,该模块提供了第三方框架 Squirrel 的接口,但 Electron 目前只内置了 Squirrel.Mac,且它与 Squirrel.Windows(需要额外引入)的处理方式也不一致(在客户端与服务器端两方面),因此如果刚接触该模块,会发现处理起来相对比较繁琐。具体可以参考我的一篇译文《Electron 自动更新的完整教程(Windows 和 OSX)》。
目前 Electron 的 autoUpdater 模块不支持 Linux 系统。
另外,XCel 目前并没有采用 autoUpdater 模块实现自动更新功能,而是利用 Electron 的 DownloadItem 模块实现。而服务器端则采用 Nuts。
至此,CSS、JavaScript 和 Electron 相关的知识和技巧 部分阐述完毕。
下面谈谈『性能优化』,这部分涉及到运行效率和内存占用量。
注:以下内容均基于 Excel 样例文件(数据量为:1913 行 x 180 列)得出的结论。
Vue 一直标榜着自己性能优异,但当数据量上升到一定量级时(如 1913 x 180 ≈ 34 万个数据单元),会出现严重的性能问题(不做相应优化的前提下)。
如直接通过列表渲染 v-for
渲染数据时,会导致程序卡死。
答:通过查阅相关资料可得(猜测), v-for
是通过一条条数据在构建后插入 DOM 的,这对于数据量较大时,无疑会造成严重的性能问题。
当时,我想到了两种解决思路:
最终,我选择了第二条,理由是:
将原本繁重的 DOM 操作转移到了 JavaScript 的拼接字符串后,性能得到了很大提升(不会导致程序卡死而渲染不出视图)。这种实现原理难道不就是 Vue、React 等框架解决的问题之一吗?只不过框架考虑的场景更广,有些地方需要我们自己根据实际情况进行优化而已。
在浏览器当中,JavaScript 的运算在现代的引擎中非常快,但 DOM 本身是非常缓慢的东西。当你调用原生 DOM API 的时候,浏览器需要在 JavaScript 引擎的语境下去接触原生的 DOM 的实现,这个过程有相当的性能损耗。所以,本质的考量是,要把耗费时间的操作尽量放在纯粹的计算中去做,保证最后计算出来的需要实际接触真实 DOM 的操作是最少的。 —— 《Vue 2.0——渐进式前端解决方案》
当然,由于 JavaScript 天生单线程,即使执行数速度再快,也会导致页面有短暂的时间拒绝用户的输入。此处可通过 Web Worker 或其它方式解决,这也将是我们后续讲到的问题。
也有网友提供了优化大量列表的方法:https://clusterize.js.org/。 但在这里我并没有采用此方式。
插入 DOM 后,又会出现了另外一个问题:滚动会很卡。猜想这是渲染问题,毕竟 34 万个单元格同时存在于界面中。
添加 transform: translate3d(0, 0, 0) / translateZ(0)
属性启动 GPU 渲染,即可解决这个渲染性能问题。再次感叹该属性的强大。🐂
后来,考虑到用户并不需要查看全部数据,只需展示部分数据让用户进行参考即可。我们对此只渲染前 30/50 行数据。这样即可提升用户体验,也能进一步优化性能(又是纯属臆测)。
另外,由于自己学艺不精和粗心大意,忘记在生产环境关闭 Vuex 的『严格模式』。
Vuex 的严格模式要在生产中关闭,否则会对 state 树进行一个深观察 (deep watch),产生不必要的性能损耗。也许在数据量少时,不会注意到这个问题。
我当时的情况是:导入 Excel 数据后,再进行交互(涉及 Vuex 的读写操作),则需要等几秒才会响应,而直接通过纯 DOM 监听的事件则无此问题。由此,判断出是 Vuex 问题。
|
|
前面说道,JavaScript 天生单线程,即使再快,对于需要处理数据量较大的情况,也会出现拒绝响应的问题。因此需要 Web Worker 或类似的方案去解决。
在这里我不选择 Web worker 的原因有如下几点:
Electron 作者在 2014.11.7 在《state of web worker support?》 issue 中回复了以下这一段:
Node integration doesn’t work in web workers, and there is no plan to do. Workers in Chromium are implemented by starting a new thread, and Node is not thread safe. Back in past we had tried to add node integration to web workers in Atom, but it crashed too easily so we gave up on it.
因此,我们最终采用了创建一个新的渲染进程 background process
进行处理数据。由 Electron 章节可知,每个 Electron 渲染进程是独立的,因此它们不会互相影响。但这也带来了一个问题:它们不能相互通讯?
错!下面有 3 种方式进行通讯:
background process
是 B,那么 A 先将 Excel 数据传递到主进程,然后主进程再转发到 B。B 处理完后再原路返回,具体如下图。当然,也可以将数据存储在主进程中,然后在多个渲染进程中使用 remote 模块来访问它。该工具采用了第三种方式的第一种情况:
1、主页面渲染进程 A 的代码如下:
2、作为中转站的主进程的代码如下:
3、处理繁重数据的 background process
渲染进程 B 的代码如下:
至此,我们将『读取文件』、『过滤数据』和『导出文件』三大耗时的数据操作均转移到了 background process
中处理。
这里,我们只创建了一个 background process
,如果想要做得更极致,我们可以新建『CPU 线程数- 1 』 个的 background process
同时对数据进行处理,然后在主进程对处理后数据进行拼接,最后再将拼接后的数据返回到主页面的渲染进程。这样就可以充分榨干 CPU 了。当然,在此我不会进行这个优化。
不要为了优化而优化,否则得不偿失。 —— 某网友
解决了执行效率和渲染的问题,发现也存在内存占用量过大的问题。当时猜测是以下几个原因:
background process
处理。在通讯传递数据的过程中,由于不是共享内存(因为 IPC 是基于 Socket 的),导致出现多份数据副本(在写该篇文章时才有了这相对确切的答案)。null
,然后等待 GC 回收。由于 Chromium 采用多进程架构,因此会涉及到进程间通信问题。Browser 进程在启动 Render 进程的过程中会建立一个以 UNIX Socket 为基础的 IPC 通道。有了 IPC 通道之后,接下来 Browser 进程与 Render 进程就以消息的形式进行通信。我们将这种消息称为 IPC 消息,以区别于线程消息循环中的消息。
——《Chromium的IPC消息发送、接收和分发机制分析》
定义:为了易于理解,以下『Excel 数据』均指 Excel 的全部有效单元格转为 JSON 格式后的数据。
最容易处理的无疑是第三点,手动将不再需要的变量及时设置为 null
。但这效果并不明显。
后来,通过系统的『活动监视器』对该工具的每阶段(打开时、导入文件时、筛选时和导出时)进行粗略的内存分析,得到以下报告(之前分析的、未作修改):
—————- S:报告分割线 —————-
经观察,主要耗内存的是页面进程。下面通过截图说明:PID 15243
是主进程PID 15246
是页面渲染进程PID 15248
是 background 渲染进程
a、首次启动程序时(第 4 行是主进程;第 1 行是页面渲染进程;第 3 行是 background 渲染进程 )
b、导入文件(第 5 行是主进程;第 2 行是页面渲染进程;第 4 行是 background 渲染进程 )
c、筛选数据(第 4 行是主进程;第 1 行是页面渲染进程;第 3 行是 background 渲染进程 )
由于 JS 目前不具有主动回收资源的功能,所以只能主动将对象设置为 null
,然后等待 GC 回收。
因此,经过一段时间等待后,内存占用如下:
d、一段时间后(第 4 行是主进程;第 1 行是页面渲染进程;第 3 行是 background 渲染进程 )
由上述可得,页面渲染进程由于页面元素和 Vue 等 UI 相关资源是固定的,占用内存较大且不能回收。主进程占用资源也不能得到很好释放,暂时不知道原因,而 background 渲染进程则较好地释放资源。
—————- E:报告分割线 —————-
根据报告,初步得出的结论是 Vue 和通讯时占用资源较大。
根据该工具的实际应用场景:由于 Excel 数据只在『导入』和『过滤后』两个阶段需要展示,而且展示的只是通过 JavaScript 拼接的 HTML 字符串构成的 DOM 而已。因此将表格数据放置在 Vuex 中,有点滥用资源的嫌疑。
另外,在 background process
中也有存有一份 Excel 数据副本。因此,索性只在 background process
存储一份 Excel 数据,然后每当数据变化时,通过 IPC 让 background process
返回拼接好的 HTML 字符串即可。这样一来,内存占有量立刻下降许多。而且这也是一个一举多得的优化:
background process
,页面的渲染进程进一步减少耗时的操作;其实,这也有点像 Vuex 的『全局单例模式管理』,一份数据就好。
当然,对于 Excel 的基本信息,如行列数、SheetName、标题组等均依然保存在 Vuex。
优化后的内存占有量如下图。与上述报告的第三张图相比(同一阶段),内存占有量下降了 44.419%:
另外,对于不需要响应的数据,可通过 Object.freeze()
冻结起来。这也是一种优化手段。但该工具目前并没有应用到。
至此,优化部分也阐述完毕了!
该工具目前是开源的,欢迎大家使用或推荐给用研组等有需要的人。
你们的反馈(可提交 issues / pull request)能让这个工具在使用和功能上不断完善。
最后,感谢 LV 的产品规划、界面设计和优化上的强力支持。全文完!
]]>中文版叫『代码大全』,两届SoftwareJolt Award震撼大奖得主,一本完整的软件构建手册,涵盖了软件构建过程中的所有细节。
谷歌度娘搜关键字「程序员必读书本」的结果统计,这本书是最多人推荐的,近年宣称为「最值得程序员阅读的首本书,也是建议程序员推荐给身边小伙伴的首本书」。
想读的朋友可以到我东东家购买,
中文版叫『程序员修炼之道-从小工到专家』。
原版略贵但绝对值的一读,它充满了关于如何改进程序员本身和代码本身的实用建议。
想读的朋友依然可以到我东东家购买,原版有每满100减30
的活动。
有一个好的中文名叫『人月神话』。
引用东东家的一段广告语吧:
图灵奖得主,IBM 360系统之父,作者Brooks颠覆了项目管理领域,长久不衰传奇著作!软件开发人员、软件项目经理、系统分析师等IT从业者必藏之软工圣经,畅销40年!赠国内实战体验精华册
然后摘其中的两句经典:
生一个孩子总是需要九个月的时间,不管安排多少个女性。
一个煎蛋,承诺在两分钟内完成,但如果两分钟后还是没有准备好,那么客户有两种选择——等待或吃半熟品,软件客户也只能这样选择。
正从标题中所说的那样,这是一本关于软件工程的散文集,文辞优美。唯一的缺点就是引用了年迈的古老技术,但是,这并不影响这本书的魅力。
是时候入手一本经典书籍了:
中文名叫『计算机程序的构造和解释』,目测应该是一本计算机科学的科普读物。
京东购买地址:
看着封面的妹子就想读的一本书,中文名叫『Head Frirst 设计模式』。
看上去最不像技术的编程书籍!
每个页面都包含涂鸦、图片以及其他一些吸引眼球的东西。
可能给人的印象是一本阅读起来很轻松的书,但事实上它会讨论编程的一些核心主题 —— 设计模式。
设计模式就是编程世界里的各种抽象的定理,而这本书有把它们画出来、具体化了的感觉。
畅销十年,累计印刷30余次,荣获2005年第十五届Jolt通用类图书震撼大奖!买买买!
中文名叫『算法导论』,这是今天介绍的几本书里面唯一一本关于「算法」的。
超过50万人阅读的算法圣经!算法标准教材,国内外1000余所高校采用!
京东购买地址:
最后一本是关于程序员职业素养的书,中文名为『程序员的职业素养』。
该书探讨了一些程序员经常忽视的主题。
你可能并不总是同意作者的观点,但它提供了良好的精神食粮。这可能并非你所期望的,但可能正是你所需要的。
京东购买地址:
Webpack做了什么
一句话简单来解释就是处理模块依赖,并将它们合并成可用的静态资源。
为什么选Webpack
模块打包工具有很多,Webpack的特点是它依赖的模块可以是js文件,也可以是css文件,只要配置对应的webpack-loader(加载器),.coffee、.sass、.jade等等任意的静态资源文件都可以被引用,并解析。
例如:我在项目中使用Vue框架,在配置官方提供的loader后,就可以直接在js中依赖.vue后缀的单文件组件了。
安装
安装Webpack,推荐只安装在当前项目中作为依赖
npm install webpack –save
添加一个配置文件 webpack.config.js
module.exports = {
entry: "./entry.js",
output: {
path: __dirname,
filename: "bundle.js"
},
module: {
loaders: [
{ test: /\.css$/, loader: "style!css" }
]
}
};
执行webpack命令
如果全局安装了Webpack的话,那么直接在当前项目执行webpack命令就可以依赖上述webpack.config.js文件中的配置,分析entry.js中的依赖,打包输出bundle.js
我使用npm scripts来启动任务,在package.json中添加:
{
...
"scripts": {
"build": "NODE_ENV=production webpack --watch"
}
...
}
执行npm run build。其中–watch参数表示持续的监听文件变化进行打包。
配置多个入口文件
module.exports = {
entry: {
entry1_bundle: "./entry1.js",
entry2_bundle: "./entry2.js"
},
output: {
path: __dirname,
filename: "[name].js"
},
module: {
loaders: [
{ test: /\.css$/, loader: "style!css" }
]
}
};
在这个配置文件中有两个入口文件,输出的时候[name]会被替换为入口中配置的entry1_bundle和entry2_bundle
使用glob方式配置
var path = require('path'),
glob = require('glob') //需安装glob模块依赖
function getEntries (globPath) {
var files = glob.sync(globPath);
var _entries = {}, entry, dirname, basename;
for (var i = 0; i < files.length; i++) {
entry = files[i];
dirname = path.dirname(entry);
basename = path.basename(entry, '.js');
_entries[path.join(dirname, basename)] = './' + entry;
}
return _entries;
}
执行getEntries(‘*.js’)就会遍历到目录下全部的js文件做为入口文件配置。
目前我有用到三个插件:CommonsChunkPlugin,UglifyJsPlugin,以及一个我自己定义的插件
module.exports = {
// plugins 字段传入一个数组,里面是实例化后的各种插件
plugins: [new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: 3
}),
new webpack.optimize.UglifyJsPlugin([options]),
...
],
entry: {
entry1_bundle: "./entry1.js",
entry2_bundle: "./entry2.js"
},
...
};
提取公用资源
为了便于使用缓存,我通过CommonsChunkPlugin这个插件将公用部分提取出来。
上述配置会自动的将被3个及以上入口文件引用的资源提取出来到一个新的文件vendor.js中。我们通常不希望公用的内容发生不预知的变化,这样配置就可以将希望提取出来的内容显性的配置在config文件中:
entry: {
vendor: ["vue", "other-lib"],
...
}
new CommonsChunkPlugin({
name: "vendor",
// 将minChunks设置为无穷大,就不会有不期望的内容进入vendor了
minChunks: Infinity,
})
只在生产环境下启用UglifyJs插件
var plugins = [new webpack.optimize.CommonsChunkPlugin([options])]
// npm scripts 配置的参数可以用上了
if(process.env.NODE_ENV == 'production'){
plugins.push(new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}))
}
关于UglifyJs的使用就不介绍了,参考UglifyJS2。
自定义插件
Webpack提供的插件已足够使用,不过针对不同的业务,我们可能需要定制一些功能,例如我所定制的功能就是在编译资源的同时生成一份用于上传到服务器的md5版本号配置文件。
来一个简单的小栗子,如何开始写一个Webpack插件:
var chunkCombo = function(){};
chunkCombo.prototype.apply = function(compiler, callback){
compiler.plugin("emit", function(compilation, callback){
compilation.chunks.map(function(chunk, key){
var filename = chunk.name + '.shtml';
var content = chunk.hash.slice(0,8);
// 生成一个对应的新文件存储md5值
compilation.assets[filename] = {
source: function() {
return content;
},
size: function() {
return Buffer.byteLength(content, 'utf8')
}
};
})
callback();
});
}
随着项目的深度定制和优化,我们可能需要开发更多的插件。
有了无所不能的加载器,Webpack可以处理任何类型的静态文件
module.exports = {
entry: {
entry1_bundle: "./entry1.js",
entry2_bundle: "./entry2.js"
},
output: {
path: __dirname,
filename: "[name].js"
},
module: {
loaders: [
{ test: /\.vue$/, loader: 'vue-loader' },
{ test: /\.css$/, loader: 'style-loader!css-loader' },
{ test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"},
//加载器之间用!连接,-loader可以省略不写
{ test: /\.scss$/, loader: 'style!css!sass?sourceMap'},
{ test: /\.(png|jpg)$/, loader: 'url-loader?limit=8192'}
]
}
};
加载器使用前记得先通过npm安装对应的模块,并将依赖添加到package.json文件中,例如:
npm install vue-loader --save
webpack-dev-server是一个轻量的node.js Express服务,通过Socket.IO来实时的通知客户端Webpack编译状态。
安装webpack-dev-server模块,此处不再重复,直接看配置文件:
module.exports = {
entry: {
entry1_bundle: "./entry1.js",
entry2_bundle: "./entry2.js"
},
output: {
path: __dirname,
filename: "bundle.js"
},
...
devServer: {
// serve 的根目录
contentBase: _contentBase,
port: 9000,
// iframe模式和inline模式可选
inline: true,
...
}
};
在package.json中添加:
{
...
"scripts": {
"dev": "NODE_ENV=dev webpack-dev-server"
}
...
}
执行npm run dev 命令后,服务就启动了。访问http://localhost:9000,就可以看到你的应用了。
定制Express路由
在inline模式下,需要手动的将用于更新的的脚本引入到页面中:
module.exports = {
entry: {
entry1_bundle: "./entry1.js",
entry2_bundle: "./entry2.js"
},
output: {
path: __dirname,
filename: "bundle.js"
},
...
devServer: {
// serve 的根目录
contentBase: _contentBase,
port: 9000,
// iframe模式和inline模式可选
inline: true,
setup: function(app) {
app.use(function(req, res, next) {
//...
return next();
});
app.get(['*.shtml','*.html'], function(req, res, next) {
//...
//将实时更新的脚本引入到页面中
res.end('<script src="http://localhost:9000/webpack-dev-server.js"></script>')
})
}
}
};
关于Express路由的使用,参考Express Routing
在我的项目中,我希望HTML页面在开发环境下和服务器环境下保持一致,因此我在devServer中配置了对HTML页面的解析。
以上,希望我的Webpack项目配置能对你解决相关问题的时候有所帮助和启发。
官人你没有走错地方,「WeUI」正式开始支持小程序啦,这是新鲜火辣的、微信官方设计团队为微信小程序量身设计的「WeUI-WXSS」。
WeUI 是一套同微信原生视觉体验一致的基础样式库,由微信官方设计团队为微信内网页和微信小程序量身设计,令用户的使用感知更加统一。包含button
、cell
、dialog
、 progress
、 toast
、article
、actionsheet
、icon
等各式元素。
用微信web开发者工具打开dist目录
dist/example/
下的组件dist/style/weui.wxss
,或者单独引用dist/style/widget
下的组件的wxss
The MIT License(http://opensource.org/licenses/MIT)
请自由地享受和参与开源
如果你有好的意见或建议,欢迎给我们提issue或pull request,为提升微信web体验贡献力量。
]]>买买买
,我们凹凸实验室「阿尔法APP突击队」在抖腿码码码
。轻氧 是 凹凸实验室 一不留神上架了的一款互联网技术资讯APP,它囊括了众多知名互联网公司、团队及网站的资讯,让你能一口气把业界最优质的文章读完,只要你愿意。
其实市面上互联网技术资讯这块领域已经有非常多优秀的产品了,例如开发者头条,又如后来居上者 掘金,类似产品之间的竞争异常剧烈,举步唯艰,再做一款类似内容的APP的意义到底有多少,而成功的概率又有多少?
我们曾经纠结过这些问题,但后来觉得这些纠结有点傻~ 做轻氧的『初心』本来就不是超越,而是在积累和探索。
从业务的层面来看,是为团队积累和探索一款APP从零到设计到上架的整个流程,为下一次(如果有的话)APP研发需求做好流程和技术上的准备。
从专业技术的层面来看,我们是在努力拓宽自己的专业范畴,要知道凹凸这个团队的前身是前端团队,相对匮乏原生应用开发经验,如果团队技术的努力方向是多终端技术体系,是全栈,那末轻氧APP则是印证我们朝这个方向发展、具备全栈开发综合能力的结果。
我们像是在练武,轻氧是我们自创的一门渐进式的武学,谁都无法断言它的厉害与否,因为主创们在不断的努力摸索和改进。
我们没有忘记O2的口号是:Open Oriented,所以「轻氧」年后将会开源,并配套一本总结整个APP项目从零到上架的书籍,记录着轻氧从无到有的套路和心法。希望能帮助到那些想了解和掌握Swift编程、Sketch设计、以及数据库架构及设计相关技能的同学们。
授之以鱼不如授之以渔,为「轻氧」存在的最大意义。
源码届时将托管在github.com/o2team/app,欢迎抢先点赞订阅更新。
「轻氧」适合什么样的你呢?
如果你是辣么专情的:
程序员、设计师、产品经理、运营、用研。
但又不失滥情的闷骚:
喜欢搞搞数码搞机、喜欢新鲜应用、脑袋发热准备创业。
那么,这款资讯 App 就是专门为你设计的。
我们为互联网人士准备了多个专业资讯频道,将优质资讯分类呈现。
你可以根据需要选择你喜欢的频道来定制APP的内容。
目前已开通 11 个资讯频道 (一些好玩的频道正在筹备中):
总之,「轻氧」旨在帮你严肃地提升学术、视界的同时,也可以让你开个小差去猎奇酷玩,嘿嘿。
关于APP的内容源,除了凹凸实验室原创资讯外,
我们精心选出了一些知名的 UED 和互联网站点,基于其简易信息聚合协议(RSS),将优质的互联网内容包装和分发,聚集到一个平台。
我们搬运但不盗窃,如果你喜欢,你仍然可以把「轻氧」当成是一款RSS订阅器,尽管我们不止于订阅辣么简单,后续我们APP做开源分享的时候再做深入介绍。
经过一段时间的耕耘,目前已有超过 4k 篇资讯,来自于 40+ 资讯来源:
未来,我们还将在保证高质量文章的基础上,聚合更多的来源。
就像其它资讯平台一样,「轻氧」的每篇文章也被打上了标签。
用户可以根据标签或者来源,能筛选出自己喜欢的内容,定制个性化的时间线。
与一般的资讯 APP 不同的是,这是一款「技术驱动」的APP,它的诞生源于「工科男」心底纯粹的技术欲望,尽管主创之一是兽医出身。
于是两三个技术工程师包揽了从产品构思、基于 Flinto 的原型交互、基于 Sketch 的视觉设计(除了LOGO为一个视觉美眉支持外)、基于 Swift3.x 的编码实现,当然还有 LeanCloud 数据服务的应用,Linux 服务器运维,以及应用上架部署的所有工作。
这肯定不是一款完美的产品,但我们在不停的迭代完善。
为了进一步提升APP的整体体验、提升我们工程师在跨专业领域的专业度,下一个大版本计划会邀请专业的交互设计师、视觉设计师参与进来。
在「轻氧」APP里面,我们无所不能为,实验了一些我们YY觉得酷的体验,例如阅读进度记录、卡片频道。
尽管新奇不一定是最合适的,但还是期待能给使用「轻氧」的你带来不一样的资讯阅读体验。
目前仅提供ios版本
基于 React Native 的 Android 版与网页版正在紧急开发中,敬请期待。
官网地址:
https://app.aotu.io
扫二维码:
问题反馈:
希望在这个信息爆炸的时代,「轻氧」能够帮助你聚焦更优质的资讯内容。
也让你更加专注于用心被创造出来的文章,和其创造者。
font-weight的属性值有100、200、300、400、500、600、700、800、900和normal、bold、lighter、bolder,它们的区别是?
另外,在实际开发中,我们应该使用数值表达还是文字表达呢?
根据W3C Fonts节章的规范标准,可知:
font-weight可取值:100~900和normal、bold、bolder、lighter。
如果字体使用九阶有序数值100~900来划分其字重(字体的粗细度),那么样式指定的font-weight属性值与字体的字重则一一对应。并且normal等价于400,bold等价于700。
但实际上,我们一般遇到的字体很多时候都是使用一些通用的词描述划分其字重,如下所示。
常见的字重数值大致对应的字重描述词语:
为什么说大致对应呢?在有些字库下是有差异的,比如在Adobe Typekit字库中对字重描述的划分列表中,它列出Heavy指的是800而不是900。另外,在我们日常使用的Photoshop和Sketch里面,Ultra Light是100,而Thin是200。
并且,字体所拥有的字重的数量实际上很少存在满足有9个字重刚好跟100~900的CSS字重一一对应的情况,通常字体拥有的字重数量为4至6个。
不必担心,起码400和700对应的字重至少是每种字体必备的,譬如常见的 Arial、Helvetica、Georgia等等,只有400(normal)和700(bold)。
bolder、lighter表示其字重值是基于从其父元素继承而来的字重计算所得的,与normal、bold所代表的字重并无关系。
其值通常是根据下表计算而得的:
继承值(Inherited value) | bolder所代表的字重 | lighter所代表的字重 |
---|---|---|
100 | 400 | 100 |
200 | 400 | 100 |
300 | 400 | 100 |
400 | 700 | 100 |
500 | 700 | 100 |
600 | 900 | 400 |
700 | 900 | 400 |
800 | 900 | 700 |
900 | 900 | 700 |
在上面我们已经提到,在很多情况下,字体并不是以九阶数值来划分的,并且其含有的字重数量是不一的,通常情况下为4-6个。
此时,就会出现样式指定的字重数值在字体中找不到直接对应的字重,那浏览器是如何解决的呢?
Bingo!
那就是要靠字体匹配算法来解决。其中关于font-weight部分是这么提及到的:
讲人话就是:
如果指定的font-weight数值,即所需的字重,能够在字体中找到对应的字重,那么就匹配为该对应的字重。否则,使用下面的规则来查找所需的字重并渲染:
感谢来自@何洋的补充:
大多数浏览器已实现font-synthesis属性,使用该属性可以控制在font-weight没有相匹配的font typeface时,会模拟计算出合适的渲染字重(与其应有的typeface有些差异),从而忽略Font Matching Algorithm。
参考文章:
下面我们通过官方例子和实际测试来好好理解这个匹配算法规则。
W3C规范标准中给出这么一个例子:
注解:灰色标记的是字体中缺少的字重,而黑色则是字体拥有的字重。
基于匹配算法规则,看图理解所得:
Figure 15.图指的是
字体库内直接匹配的字重 | 填空值(即通过算法间接匹配所得字重) |
---|---|
400 | 300、200、100、500 |
700 | 600 |
900 | 800 |
拿font-weight: 300;
来说,字体中没有可以直接匹配的字重,那么300小于400,则根据第一条规则,先降序查找匹配,但是都没有可匹配的200、100,那么升序查找为400,结果可匹配。
Figure 16.图指的是
字体库内直接映射的字重 | 填空值 |
---|---|
300 | 200、100、400、500 |
600 | 700、800、900 |
这里需要注意的是,填空值500表现的是300的字重,而不是600的字重。
为什么呢?根据规则,500优先匹配400的字重,但是此处找不到400的字重,则执行规则中第一条所需字重小于400的情况。
其余的,我就不多解释了,大家可以根据结果检查自己是否理解到位。
根据Google Fonts API - Droid Sans提供的Droid Sans字体,我们可以知道该字体拥有两种字重。
根据字体匹配算法规则,我们可以预测其字重匹配应该如下表所示:
字体库内直接映射的字重 | 填空值 |
---|---|
400 | 300、200、100、500 |
700 | 600、800、900 |
也就是100、200、300、500会表现为跟400同一种字重,600、800、900会表现为跟700同一种字重。
利用Google Fonts提供的Droid Sans,我们进行了实例测试-(DroidSans.html)来验证。
结果如下图,证明我们的预测结果正确,该字体匹配算法规则运行有效。
根据以上的研究,可以总结出三点:
通常情况下,一个特定的字体仅会包含少数的可用字重。若所指定的字重不存在直接匹配,则会通过字体匹配算法规则匹配使用邻近的可用字重。这也就是为什么我们有时候使用特定字重时没有“生效”,看起来跟其它字重差不多的原因所在。
在实际中,最为常用的字重是normal和bold。我个人认为400、700是等效于normal、bold的,无论哪一种表示方法都没有关系,主要是个人习惯问题。
但是,推荐使用数值替代lighter、bolder,因为这涉及到继承计算的问题,用数值的话则会更为清晰明了。
]]>参考资料:
W3C-字体
W3C-字体匹配算法
最近被分配到移动端开发组,支持某活动的页面页面制作。这算是我第一次真正接触移动端页面制作,下面就谈谈个人总结和思考。
开会大体讲解、讨论与排期 -> 交互设计 -> 视觉设计 -> 页面页面制作 -> 前端开发 -> 测试
每个步骤环环相扣,每个职位都需要和其前后的人沟通协调。
测试遇到问题则会反馈到相应环节负责人。
当然,涉及的职位也不仅于此,还有法务同事审核内容是否符合当前法规等等。
前端开发离不开构建工具,除了敲代码,其余都交给构建工具(如组件开发、CSS 兼容处理、图片 Base64、图片雪碧图和压缩处理等)。
在 Athena 中,文件层级结构如下:项目 project -> 模块 module(具体每个活动) -> 页面 page -> 部件 widget。
举例: 某项目 -> X、Y 活动 -> 预热页和高潮页 -> 头部、弹框等 widget。一般文件目录如下:
刚开始接触时,存在这样的一个疑惑:什么是 widget,一个不可复用的页面头部可以作为 widget 吗?
答:我最初的想法是:“错误地把 widget 当成 component,component 一直被强调的 特性之一是可复用性。对于不可复用的部分就不应该抽出为一个widget了?”其实对于一个相对独立的功能,我们就可把它抽出来。这无疑会增强程序的可维护性。
对于一个项目,一般一个模块由一个人负责。但考虑到每个模块间可能存在(或未来存在)可复用的 widget,需要规范命名以形成命名空间,防止冲突(具体会在下面的规范-命名中阐述)。
Component 与 Widget 的区别
Component 是更加广义抽象的概念,而Widget是更加具体现实的概念。所以Component的范围要比Widget大得多,通常 Component 是由多个 Widget 组成。
举个例子,可能不是很恰当,希望帮助你的理解,比如家是由床,柜子等多个 Component 组成,柜子是由多个抽屉 Widget 组成的。
而 Component 和 Widget 的目的都是为了模块化开发。
其实,在这里并没有对 widget 和 component 做这么细的区分。
正如上面讨论的,一个页面由多个 widget 组成。因此,一个页面看起来如下:
widget 一般存在可复用性。但如何控制细粒度呢?分得越细代码就越简洁,但工作量和维护难度可能会上升,因此需要权衡你当时的情况。
由于一个项目中,一个模块由某一个人负责,但模块之间的 widget 存在或未来存在可复用的可能(而且开发可能会为你的页面添加已有的组件,如页面会嵌在某 APP 内,该 APP 已有现成的一些提示框)。因此,需要命名空间将其它们进行区分以防止冲突。由于 CSS 不存在命名空间,因此只能通过类似 BEM 的方式(具体根据团队的规范),如:app_market_header
、app_market_list_item
。app_market
是模块(即某个活动)的标识,在该项目下,它是唯一的。
另外,还有一点:类名是否要按照 html 层级关系层层添加呢?如:
对于 app_market_header_icon
,尽管在 header 中,但 icon 并不只属于 header,而属于整个模块(活动),那么我们就可以改为 app_market_icon
。
老司机 Code review 后,讲了以下内容:
反面教材:
存在的问题是:嵌套层级越深,类名就越长。
较好的解决方案:
这是基于『姓名』原理进行优化的,举例:app_market_answer_item
是姓名(库日天),那么它的子元素只需继承它的『姓』(库姆斯) app_market_answer_itop
,而不是它的姓名(库日天姆斯) app_market_answer_item_top
。每当类名达到三到四个单词长时,就要考虑简化名字。
进一步优化,app_market 可以看成是『复姓』,有时为了书写便利,可以以两个单词的首字母结合形成一个新的『新姓』- 『am』。当然,追求便利的副作用是牺牲了代码的可读性。如果你负责的项目或页面没有太大的二次维护或者交叉维护的可能性,推荐做此简化。
BTW:此简化后的『姓』可以在代码中稍加注释说明,如下代码所示:
|
|
至少加一个类名,任何时候都尽量要『针对类名书写样式,而不是针对元素书写样式』,除非你能预判元素是末级元素。
因此对于以下 CSS:
可优化成:
移动端采用 rem 布局方式。通过动态修改 html 的 font-size 实现自适应。
REM 布局有两种实现方式:CSS 媒介查询和 JavaScript 动态修改。由于 JavaScript 更为灵活,因此现在更多地采用此方式。
凹凸的实现方式是:在 head
标签末加入以下代码
从以上代码可得出以下信息:
zoom
为 1
有人说 rem 布局是 vw
和 vh
的替换方案,当 vw
和 vh
成熟时,两者可能会各司其职吧。
vw 的兼容性:在安卓 4.3 及以下是不支持的。
由于 rem 布局是相对于视口宽度,因此任何需要根据屏幕大小进行变化的元素(width、height、position 等)都可以用 rem 单位。
但 rem 也有它的缺点——不精细(在下一节阐述),其实这涉及到了浏览器渲染引擎的处理。因此,对于需要精细处理的地方(如通过 CSS 实现的 icon),可以用 px 等绝对单位,然后再通过 transform: scale() 方法等比缩放。
那 font-size
是否也要用 rem 单位呢? 这也是我曾经纠结的地方。如果不等比缩放,对不起设计师,而且对于小屏幕,一些元素内的字体会换行或溢出。当然这可以通过 CSS3 媒介查询解决这种状况。
字体不采用 rem 的好处是:在大屏手机下,能显示更多字体。
看到 网易新闻 和 聚划算 的字体大小都采用 rem 单位,我就不纠结了。当然,也有其它网站是采用绝对单位的,两者没有绝对的对与错,取决于你的实际情况。
由于 rem 布局是基于某一设备实现的(目前一般采用 iPhone6),对于 375 倍数宽的设备无疑会拥有最佳的显示效果。而对于非 375 倍数宽的设备,zoom 就可能是拥有除不尽的小数,根元素的字体大小也相应会有小数。而浏览器对小数的处理方式不一致,导致该居中的地方没完全居中,但你又不能为此设置特定样式(如 margin-top: *px;),因为浏览器多如牛毛,这个浏览器微调居中了,而原本居中的浏览器变得不居中了。
对于图标 icon,rem 的不精细导致通过多个元素(伪元素)组合而成的 icon 会形成错位/偏差。因此,在这种情况下,需要权衡是否需要使用 CSS 实现了。
SASS 无疑增强了原本声明式的 CSS,为 CSS 注入了可编程等能力。在这次项目,算是我第一次使用 SASS,由于构建工具和基础库的完善,只需通过查看/模仿已有项目的 SASS 用法,就能快速上手。后续还是要系统地学习,以更合理地使用 SASS。
使用 SASS 的最大问题是:层级嵌套过深,这也是对 SASS 理解不深入的原因。可以关注一下转译后的 CSS。
这次项目的 APP 采用手机自带浏览器内核,而这些浏览器内核依赖于系统版本等因素。另外,国产机也会对这些内核进行定制和修改。特别是华为、OPPO。
下面列出我所遇到的兼容性问题(不列具体机型,因为这些兼容性处理终会过时,不必死记硬背,遇到了能解决就好(要求基础扎实)):
auto = img.Height * (screen.Width/img.Width)
】,导致图片未显示)。另外,需要注意的是:透明的色标在iOS 默认是黑色的,即 transparent 等于 rgba(0,0,0,0)。因此即使是完全透明的色标,也要指定颜色。否则后果如下:根节点 html font-size 渲染错误:在华为、魅族的某设备上(手Q),会出现一个非常奇葩的渲染 Bug,同一个网页,“扫一扫”打开 html 的 font-size 正常,直接点击链接会出现渲染出来的 html font-size 会比设置得值大(如:设置25.8,渲染出来是 29),因此导致整体变大,且布局错乱。
我的方法是:为 html font-size 重新设置大小:渲染字体大小 - (渲染与正常差值)
|
|
有网友提供这个方法 <meta name="wap-font-scale" content="no">
,经测试不可行。此方法是针对 UC 浏览器的。
上面主要列出了对使用有影响的兼容性问题,有些由于浏览器渲染引擎导致的问题(不影响使用),若无法通过 transform、z-index 等解决,也许只能通过 JavaScript 解决或进行取舍了。
图片占位元素:对于宽高比例固定的坑位(如商品列表项),通过将图片放置在占位元素中,可避免图片加载时引起的页面抖动和图片尺寸不一致而导致的页面布局错乱。代码实现:
.img_placeholder {
position: relative;
height: 0;
overflow: hidden;
padding-top: placeholder 的高/宽%; // padding-top/bottom: 百分比; 是基于父元素的宽度
img {
width: 100%;
height: auto;
position: absolute;
left: 0;
top: 0;
}
}
1px:在 retina 屏幕下,1 CSS像素是用 4 个物理像素表示,为了在该屏幕下显示更精细,通过为 ::after 应用以下代码(以上边框为例):
div {
position: relative;
&::after {
content: '';
position: absolute;
z-index: 1;
pointer-events: none;
background: $borderColor;
height: 1px;left: 0;right: 0;top: 0;
@media only screen and (-webkit-min-device-pixel-ratio:2) {
&{
-webkit-transform: scaleY(0.5);
-webkit-transform-origin: 50% 0%;
}
}
}
}
根据元素个数应用特定样式:
/* one item */
li:first-child:nth-last-child(1) {
width: 100%;
}
/* two items */
li:first-child:nth-last-child(2),
li:first-child:nth-last-child(2) ~ li {
width: 50%;
}
/* three items */
li:first-child:nth-last-child(3),
li:first-child:nth-last-child(3) ~ li {
width: 33.3333%;
}
/* four items */
li:first-child:nth-last-child(4),
li:first-child:nth-last-child(4) ~ li {
width: 25%;
}
应用样例有:根据元素个数自适应标签样式。
而对于反方向标签,可先首先对整体 transform: scale(-1),然后再对字体 transform: scale(-1) 恢复从左向右的方向。效果如下:
background-image: radial-gradient(rem(189/2) 100%, circle, transparent 0, transparent 3px, #fa2c66 3px);
,其中 3px 是孔的半径。另外,卡券的上下部分是线性渐变的,因此可以在上下部分分别通过伪类元素添加 background-image: linear-gradient(to top, #fa2e67 0, #fb5584 100%);
,当然,要从离外上/下边界 3px 的地方开始。虽然这不能完美地从最边界开始,但效果还是可以的。但由于径向渐变的兼容性问题,我最终还是用图片替换了这种实现。🙄display: inline;
,但还会存在一个问题是:padding 只会出现在多行文本的首和尾,对于需要为每行文本的首尾都需要相同的 padding,可以参考这篇文章:《multi-line-padded-text》 。该文章提供了多种实现方式,根据具体情况选择一种即可。另外,对于每行的间距,可通过设置 line-height 和 padding-top/bottom 实现,其中 line-height 要大于(字体高度+padding-top/bottom)。由于工具的成熟,我不需要考虑构建工具的搭建。
由于发布方式的成熟,页面制作和开发能更好地分离,页面制作者负责输出 HTML、CSS,开发负责 copy html 代码和引入 CSS 页面片。CSS 页面片由页面制作者更新发布,开发无需关心。这达到了互不干扰、多线程并行的效果。
成熟的基础设施让我们免除了非代码相关的烦恼,但这也让我担心:假如有一天我脱离了这些基础设施,我该如何保持高效。
CSS 页面片
JS 页面片
Combo Handler是Yahoo!开发的一个Apache模块,它实现了开发人员简单方便地通过URL来合并JavaScript和CSS文件,从而大大减少文件请求数。 http://www.cnblogs.com/zhengyun_ustc/archive/2012/07/18/combo.html
这就是我的第一次…🙈 学习很多,完!
以上仅是我个人完成某项目页面制作的思考和总结,不小心暴露了团队下限。🌚
]]>回答: 或许,可以聊聊 APNG。
APNG(Animated Portable Network Graphics)顾名思义是基于 PNG 格式扩展的一种动画格式,增加了对动画图像的支持,同时加入了 24 位图像和 8 位 Alpha 透明度的支持,这意味着动画将拥有更好的质量,其诞生的目的是为了替代老旧的 GIF 格式,但它目前并没有获得 PNG 组织官方的认可。
MNG
在 APNG 之前它还有一个老冤家叫 MNG(Multiple-image Network Graphics)即多图像网络图形,1996 年 6 月提出 PNF(Portable Network Frame)草案,同年8月更名为 MNG ,2001 年 1 月 31 日发布 MNG 规范 1.0 版本,MNG 是出自 PNG 开发组之手,但由于结构复杂的 MNG 程序库,使用过程会占用大量的资源,早期只有较少的浏览器支持,Chrome、IE、Opera、Safari 则从未支持过。
APNG
2004 年,由 Mozilla 公司两位 Mozilla 程序员 Stuart Parmenter 和 Vladimir Vukićević 共同设计出 APNG,他们希望 Mozilla 社区能使用它,但提案未能通过。
libpng程序库
2006 年,Google Summer of Code 活动中,加拿大圣力嘉学院的学生为 libpng 程序库加入了对 APNG 支持,此后开发者再次推荐给 Mozilla 社区,不过仍然遭到拒绝。
首次支持
2007 年 3 月 23 日,Mozilla 后知后觉,在 Mozilla Firefox 3.0 中 首次支持 APNG 格式。
标准化申请
2007 年 4 月 20 日,Mozilla 希望 APNG 能成为官方标准,因此 PNG 组织发起投票,最终以8:10的票数否决了 APNG 进了官方标准,因为 PNG 组织决心继续推广 MNG,但这不并影响 Mozilla 继续支持 APNG。
开头讲 APNG 时提到,APNG 的出现就是为了替代 GIF,诞生于 1987 年的 GIF 为什么能存活 29 年之久?
主要有四个原因:
1、图片质量
GIF
APNG
如果你使用的是非 Firefox、Safari 浏览器,那 APNG 格式的图片会向下兼容显示为静态图,你可以更换 Firefox、Safari 浏览器或者在 Chrome 浏览器安装 APNG Extension for Google Chrome 扩展来兼容,通过两者对比能总结出以下区别:
GIF:
APNG:
2、图片体积
如果你使用的浏览器不支持WebP,下面对比的 WebP 格式的图片将无法显示。
GIF = 43 920 bytes | APNG = 34 210 bytes |
WebP = 41 064 bytes | Lossy WebP = 73 774 bytes |
GIF = 43 132 bytes | APNG = 30 823 bytes |
WebP = 55 968 bytes | Lossy WebP = 114 518 bytes |
GIF = 200 700 bytes | APNG = 168 411 bytes |
WebP = 424 752 bytes | Lossy WebP = 394 118 bytes |
从几组 GIF、APNG、WebP 的对比中可以发现,无论在纯色的图片或是多彩的图片,大部分情况下 APNG 依旧能比 GIF、WebP 以及有损的 WebP 的体积小。
APNG 是基于 PNG 格式扩展的,首先需要了解一个简单的 PNG 文件组成结构:
PNG Signature | IHDR | IDAT | IEND |
PNG 由 4 部分组成,首先以 PNG Signature(PNG签名块)开头,紧接着一个 IHDR(图像头部块),然后是一个或多个的 IDAT(图像数据块),最终以 IEND(图像结束块)结尾。
APNG 规范引入了三个新大块,分别是:acTL(动画控制块)、fcTL(帧控制块)、fdAT(帧数据块),下图是三个独立的 PNG 文件组成 APNG 的示意图。
从图中可以发现第一帧与后面两帧不同,那是因为第一帧 APNG 文件存储的为一个正常的 PNG 数据块,对于不支持 APNG 的浏览器或软件,只会显示 APNG 文件的第一帧,忽略后面附加的动画块,这也是为什么 APNG 能向下兼容 PNG 的原因。
假设使用一个 4 帧图片合成 APNG
APNG 会通过算法计算帧之间的差异,只存储帧之前的差异,而不是存储全帧。
通过 TweakPNG 软件观察 IDAT 图像数据块和 fdAT 帧数据块的大小,可以明显的看出来存储全帧与差异帧的区别,使得 APNG 文件大小有显著的减少。
主要的原因是缺乏浏览器的支持,从 Can I use 查询可知 Firefox 从 3 到 49 版本自始自终支持着,Opera 早期只有三个版本支持过(10.1、11.5、12.1),后续版本则取消了对 APNG 的支持,而 Chrome、IE、Edge 则从未支持过 APNG,Chrome 和 Opera 都在推广自家的 WebP,而微软则一直是个不合群的家伙。
但是,重要的一点是 2014 年 9 月 17 号 Apple 向用户推送了 iOS 8,这意味着 Safari 8 新增了对 APNG 的支持,这能有效的推动 APNG 的发展,至少在移动端。
既然存在兼容问题,那就需要通过判断应用场景。
|
|
方法与 WebP 检测相似,同样是加载一张 1x1 像素大小的 Base64 编码图片,不同在于 WebP 加载完成后是判断图片宽高是否大于 1,而 APNG 则是将其绘制到画布中,通过 getImageData() 方法去获取该图片的像素数据,主要是获取 data[3] 的 Alpha 透明通道(值的范围:0 - 255),当返回 0(0代表透明的)时则表示支持 APNG,返回 255(255 代表完全可见的)则表示不支持 APNG。
当然,目前也有用于兼容的库:apng-canvas
使用该库需要以下条件支持:
|
|
|
|
在了解 APNG 后,是不是心痒痒想制作 APNG 呢?在制作工具方面,APNG 已经不像早期那样工具匮乏了,APNG Software 网站上有大量的制作工具,有客户端版本(大部分只支持 Widnows)也有命令行版本,可以非常轻松的制作 APNG,比如下面这款软件。
Windows客户端 - APNG Assembler
Mac客户端 - APNGb
功能说明:
这里演示图分别是 Windows 版本和 Mac 版本,功能基本一致,将序列帧图片拖拽到指定位置,设置一些基本的参数即可生成 APNG 图,Mac 版本比 Windows 版本多出一个将 APNG 图片 Disassembly(分解)功能,可分解为多个 PNG 图片。
在最后,如果你认为 APNG 是值得被支持、被推广的,请为它投上一票和点 Stars(需科学上网)。
APNG support in Chrome : issue 1171、issue 437662
APNG support in IE : Votes
感谢你的阅读。
Animated PNG demos
GIF vs APNG vs WebP
Inter-frame Optimization in APNG
davidmz/apng-canvas - Github
GIF - Wikipedia
APNG - Wikipedia
APNG Specification
Can I use - APNG
Portable Network Graphics - Wikipedia
Multiple-image Network Graphics - Wikipedia
APNG Software
今年的 AdobeMax 大会于美国时间 10.31-11.04 在加州第二大城市圣地亚哥举办。AdobeMax大会内容包括发布了Adobe Creative Cloud旗下一系列软件的新版本、实验性项目展示、从业人员交流等。今年的大会有什么有(hei)趣(ke)的(ji)发布了呢?让小编带你去逛一圈看看吧。
首先介绍前端er每天都要接触到 Photoshop ,顺便赞一下 mac 版的安装速度,不到2min就搞定。PS2017 做了如下更新:
…………
大部分都和前端开发都不太沾边,小编很艰难的发现了几个沾边的:
顺便说一下 mac 下的 PS2017 的窗口用 sizeup 的快捷键调整也不会像之前那样不正确了,半屏、全屏切换正常。
今年早些时候发布了预览版,仅支持 mac 。大会上新发布了新的 Beta 版,并且支持了 windows 。
XD 是一款矢量化图形设计、web & 移动 app 原型制作软件——嗯,据说就是死磕 Sketch 。特点是多种内置UI组件、各种酷炫效果、手机端实时预览效果等等。
Ch(Beta) 这是 Adobe 的一个新产品,可通过电脑摄像头来追踪用户的动作和面部表情,并将其实时应用在虚拟2D动画角色上,软件会自动完成动作之间的形变动画计算,无需软件使用者操心。大会上介绍这个软件的时候居然有一段是直播辛普森动画的,非常好玩。
小编并且亲自安装了 Ch 并体验了一遍内置的样例(根本停不下来),发现这个软件真的非常神奇。没了解过的同学强烈介意看一下这个视频了解一下其用途:点此观看
这个软件的大概工作流程是这样的:
Ch里新建 Puppet(new Puppet in Photoshop)——打开 PS 编辑角色(默认打开一个结构化图层的角色样例)——编辑后保存——返回 Ch 把 Puppet 拖入场景舞台——点击右上角的“set rest post”——点击场景面板的红色“录制”按钮,然后就到你尽情表演的时候啦……
以下是小编用样例录的一段“表演”:
所以以后直播网站里是不是要加上一个“二次元直播”频道?
Adobe推出的智能云计算服务,Sensei并非一款单独的软件产品,而是一个可以应用于Adobe旗下各款产品的底层人工智能工具(如 Photoshop、Illustrator)。
该服务具体的内容是让软件明白某张照片、某张照片的一部分、某段视频以及某段文本描述的真实含义,帮助人们把一些固定、重复性的操作变得自动化和简单化。有了海量的分析数据,会让用户处理一些图像、视频、音频变得非常简单和智能,以前需要调整一堆参数才能实现的效果以后只需要几个简单的鼠标操作。下面将要提到的 ConceptCanvas、SkeyReplace 技术就是一个具体的应用方向。
以下介绍的技术还没正式面向用户,Adobe 每年都会在大会上推出一些实验性技术,相当一部分在接下来的几年里会推出面向大众的产品,比如XD就是2015年的实验性项目 Project Comet。
先上一张图:
这是大会现场的一个截图,左边是PS界面,右边是一列是搜索结果。
可以看出这个搜索技术利用简单的图像关键字、构图、颜色、物体位置等信息进行素材搜索,素材来源由于版权原因估计短时间内只支持Adobe Stock。感觉这种搜索方式比常见的搜索引擎以图搜图更加容易被设计师接受,因为实在太简单直观了,有点佩服Adobe工程师的脑洞。
要更直观的感受可以看一下这个演示视频:观看地址
很希望搜索引擎搜图也可以加入类似的技术,在海量图片的网络找图我们是不是可以更便捷、发挥更多想象力?
这是一个神奇的声音编辑软件,可以根据你录制好的一段音频(长度大约20分钟),系统会学习并模仿你的语气。然后输入你想说的文字,就可以根据文字内容输出一段你的声音的音频,听上去就像你真的说过这段话。
问题来了,以后声音造假和图像造假一样成本真的很低?你不想在朋友圈晒一段你和周杰伦的通话录音?
这是VoCo现场演示的视频,点此观看
从照片提取矢量数据的一种技术,如提取轮廓并调整、删除等,这是Illustrator目前正在开发的一部分功能。
即照片天空智能替换技术,以后婚纱照的真的一点也不怕坏天气?
以上就是AdobeMax大会要介绍的主要内容,还有一些设计专业性很强的技术如Stylit、ColorChameleon、Wetbrush等,有兴趣的可以去下面的参考资料看看。
border-image 边框图片,顾名思义: 指定边框使用的图片。
尽管经常使用 border-image(-webkit-border-image),但我们真的了解它的吗?
本文分两部分来介绍 border-image:
CSS3 border 最开始是做为一个独立模块(CSS3 module: Border)被维护,后来(2005.2.16) W3C工作组将 border 和 background 两个模块合并作为一个新模块:CSS3 Backgrounds and Borders Module,08年又将其改名为 CSS Backgrounds and Borders Module Level 3。以下是具体过程:
在 CSS3 border 的第一个工作草案(WD)『W3C Working Draft 7 November 2002』 定义了 border-image 的用法,经过漫长的十几年修订,border-image 经历了三次重要的演变:
The border-image properties allow the author to assign images to borders. There are four groups of border image properties:
Specifying border images
These properties are used to specify the URI of the border image.
Fitting border images
These properties are used to specify how the image(s) are fitted in the border area.
Transforming border images
These properties are used to make the images of a side or a corner to be reflected or rotated versions of another.
The border image properties override the border style properties.
—— 摘录自:『W3C Working Draft 7 November 2002 #the-border-image』。
此时的 border-image 由3组属性成,分别是:
(ps: W3C 文档里提到有四组属性。不过笔者只找到上述三组)
1. border-image/border-corner-image
border-image 指定四边的图像,border-corner-image 指定四个角的图像,注意这里分开指定四条边和四个角的图片即需要使用8个图像。如下:
另外,每条边都可以指定三张图片:
2. border-fit/border-corner-fit
指定 border-image/border-corner-image 的平铺方式。
3.border-transform/border-corner-transform
指定 border-image/border-corner-image 的变换方式。
具体可以参见:https://www.w3.org/TR/2002/WD-css3-border-20021107/#the-border-image
这个古老的版本对 bordr-image 做了全面的定义,甚至比当前(2016.11.1)的标准还要周到详细,但是这个版本过于笨重,很快被 W3C 的新标准替代。
『W3C Working Draft 16 February 2005』对上个版本做了极大的精简工作,并重新定义了一个简洁的 border-image ,语法如下:
|
|
The four numbers or percentages immediately following the
specify which part of that image is used for which part of the border. They divide the image into nine parts: four corners, four edges and a middle part. The middle part is used as an extra background image.
—— 摘录自:https://www.w3.org/TR/2005/WD-css3-background-20050216/#the-border-image
这个版本提出了九宫格的概念(border-image的精髓)并提供了简洁的语法。 Chrome/Safari 私有的 -webkit-border-image
实现了这个版本的语法,并将其发扬光大。至今(2016.11.1)国内不少介绍 border-image 技术文章都是在介绍这个版本。
修订版 『W3C Working Draft 15 October 2009』在上个版本的基础上将 border-image 分拆成 border-image- 家族,同时加入一个新的成员 border-image-outset。border-image- 成员如下:
border-image 成为上述五个属性的简写,语法也从此稳定下来,俨然已是一个正式的 W3C 标准(REC)。
Authors can specify an image to be used in place of the border styles. In this case, the border’s design is taken from the sides and corners of an image specified with ‘border-image-source’, whose pieces may be sliced, scaled and stretched in various ways to fit the size of the border image area. The border-image properties do not affect layout: layout of the box, its content, and surrounding content is based on the ‘border-width’ and ‘border-style’ properties only.
—— 摘录自: https://www.w3.org/TR/css3-background/#border-images
border-image 通过指定一张图片来取替 border-style 定义的样式,但 border-image 生效的前提是: border-style 和 border-width 同时为有效值(即 border-style 不为 none,border-width 不为 0)。
本章按 『W3C Candidate Recommendation 9 September 2014』规范来介绍 border-image 的用法。
语法:
指定边框图片的地址。 none 表示border-image不做任何效果,边框使用 border-style 指定的样式。
语法:
border-image-slice 从名字上看就很好理解:边框图像切片。指定4个值(4条分割线:top, right, bottom, left)将 border-image-source 分割成9宫格,如下:
四条分割线的值
border-image-slice 四条线的值类型为:number | percentage。
number
不带单位的数值。1 代表 1个图片像素。percentage
百分比。
错误的写法:
正确的写法:
关键字:fill
Specifies an image to use in place of the rendering specified by the ‘border-style’ properties and, if given the ‘fill’ keyword in ‘border-image-slice’, as an additional image backdrop for the element.
关键字fill的作用是:将border-image-source九宫格中间那一块切片作为DOM节点的背景。
素材图片box.png:
CSS 代码:
测试结果如下:
线上DEMO:
语法:
|
|
border-image-width 字面意思是边框图片宽度,作用是将 DOM 节点分割成九宫格。 假设 border-image-slice 分割 border-image-source 的九宫格为A, border-image-width 分割 DOM 的九宫格为 B,A 与 B 的每个格子存在一一对应关系,具体如下:
border-image-width 参数的四种类型:
length
带 px, em, in … 单位的尺寸值percentage
百分比number
不带单位的数字;它表示 border-width 的倍数auto
使用 auto, border-image-width 将会使用 border-image-slice 的值
border-image-width 的参数不能为负值
border-image-width的缺省值是 number 类型:1
border image area 是成熟阶段被引入用于解释 border-image-width 和 border-imaeg-out 的概念。
The border image is drawn inside an area called the border image area. This is an area whose boundaries by default correspond to the border box
—— 摘录自: https://www.w3.org/TR/css3-background/#border-image-width
用于绘画 border image 的区域叫 border image area,它默认与边框盒子(border box)完全重合。简单地说,border image area 就是 border-image-width 分割出来的九宫格。
上面有提到,border image area 默认与 border-box 是重合关系。如果把标准后退到发展阶段:
|
|
在发展阶段,DOM节点由 border-width 分割为九宫格,这个时期的 border-box 就是 border image area。
到了成熟阶段(即本章介绍的版本),border-box 与 border image area 是默认重合的两个空间,border-box 只负责盒子模型上的事务,border image area 则专注于边框图像空间分割。
在实际使用过程中,笔者发现 border-width 也可以分割 border image area。如下:
测试CSS代码:
截图如下:
如果单从上述测试结果而言,border-width 可以分割 border image area 是正确。不过,这个结论有一个前提:border-image-border 与 border-image-outset 同时缺省。如果将 CSS 代码修为:
|
|
截图如下:
设置了 border-image-outset 后 border-width 的分割 border image area 的假像就被揭穿了!!
border-width 分割 border image area 的假象源自 border-image-width 的缺省值1,数值1表示 border-image-width 是 1x border-width,大白话就是border-image-width 的默认值是border-width。
尽管在最新的CSS3标准中 “border-width 分割 border image area” 只是个假像,但当前(2016.11.1)阶段,为了更多浏览器兼容建议 border-width 与 border-image-width 保持一致,即使用 border-width 暂代 border-image-width。
语法:
border-image-outset 字面意思是边框图片开端。作用是重新指定 border image area 的边界。
The values specify the amount by which the border image area extends beyond the border box. If it has four values, they set the outsets on the top, right, bottom and left sides in that order. If the left is missing, it is the same as the right; if the bottom is missing, it is the same as the top; if the right is missing, it is the same as the top.
—— 摘录自:https://www.w3.org/TR/css3-background/#border-image-outset
通过指定 border-image-outset 的值,可以把 border image area 的区域延伸到 border-box 之外。如下:
|
|
对比:
二维码:
border-image-outset 与 border-image-width 参数的意义是一样的。
border-image-outset 的值不能为负值
语法:
border-image-repeat 字面意义是 边框图片平铺。作用是指定 border-image 的平铺方式。语法上最多可接收两个参数,第一个参数指定水平方向边框的平铺方式,第二个参数指定垂直方向边框的平铺方式,九宫格的中间区域受这两参数的共同影响,如下:
目前只能四值可供选择:stretch, repeat, round, space。
其中,stretch 是默认值,space 目前chrome浏览器按 repeat 来渲染。这四个参数的效果如下:
repeat 与 round 的区别:round 除了能平铺外还能通过伸缩使背景完整显示。
round 与 space 的区别:虽然都使背景完整显示,但是 space 在小方块之间有一定的空隙。
二维码:
语法:
|
|
简写其实没什么好说的,不过由于 border-image-slice、border-image-width 与 border-image-outset 这三者的参数相似,所以有必要说一下,这三个参数在简写里有两个注意点:
一、 border-image-outset 参数一定要在 border-image-width 之后,假设border-image-width缺省,仍然需要在原来 border-image-width 写上 /,如下:
正确的写法:
错误的写法:
二、 如果有 border-image-width/ border-image-outset 属性值,border-image-slice必须指定数值,否则不合语法,如下:
正确的写法:
错误的写法:
参考资料:
]]>在 ES6 中增加了对类对象的相关定义和操作(比如 class
和 extends
),这就使得我们在多个不同类之间共享或者扩展一些方法或者行为的时候,变得并不是那么优雅。这个时候,我们就需要一种更优雅的方法来帮助我们完成这些事情。
decorators 即 装饰器,这一特性的提出来源于 python 之类的语言,如果你熟悉 python 的话,对它一定不会陌生。那么我们先来看一下 python 里的装饰器是什么样子的吧:
A Python decorator is a function that takes another function, extending the behavior of the latter function without explicitly modifying it.
装饰器是在 python 2.4 里增加的功能,它的主要作用是给一个已有的方法或类扩展一些新的行为,而不是去直接修改它本身。
听起来有点儿懵😳,“show me the code !”
|
|
这里的 @decorator
就是我们说的装饰器。在上面的代码中,我们利用装饰器给我们的目标方法执行前打印出了一行文本,并且并没有对原方法做任何的修改。代码基本等同于
|
|
通过代码我们也不难看出,装饰器 decorator 接收一个参数,也就是我们被装饰的目标方法,处理完扩展的内容以后再返回一个方法,供以后调用,同时也失去了对原方法对象的访问。当我们对某个应用了装饰以后,其实就改变了被装饰方法的入口引用,使其重新指向了装饰器返回的方法的入口点,从而来实现我们对原函数的扩展、修改等操作。
那么我们了解到了装饰器在 python 中的表现以后,会不会觉得其实装饰器其实蛮简单的,就是一个 wrapper 嘛,对于 Javascript 这种语言来说,这种形态不是很常见吗,干嘛还要引入这么一个东西呢?
是的,在 ES6 之前,装饰器对于 JS 来说确实显得不太重要,你只是需要加几层 wrapper 包裹就好了(虽然也会显得不那么优雅)。但是正如文章开头所说,在 ES6 提出之后,你会发现,好像事情变得有些不同了。当我们需要在多个不同的类之间共享或者扩展一些方法或行为的时候,代码会变得错综复杂,极其不优雅,这也就是装饰器被提出的一个很重要的原因。
话说从装饰器被提出已经有一年多的时间了,同时期的很多其他新的特性已经随着 ES6 的推进而被大家广泛使用,而这货现在却还停留在 stage 2 的阶段,也很少被人提及和应用。那么,装饰器到底是在 Javascript 中是怎样表现的呢?我们下面来一起看一下吧!
先来看一下装饰器在代码中是长成什么样子吧
|
|
嗯,代码中的 @decorator
就是 JS 中的装饰器,看起来基本和 python 中的样子一样,以 @
作为标识符,可以作用于类,也可以作用于类的属性。那么接下来,我们就来看看它具体的表现及运行原理吧。
首先我们先来看一下关于 ES6 中的类吧
|
|
上面这段代码是 ES6 中定义一个类的写法,其实只是一个语法糖,而实际上当我们给一个类添加一个属性的时候,会调用到 Object.defineProperty
这个方法,它会接受三个参数:target
、name
和 descriptor
,所以上面的代码实际上在执行时是这样的:
|
|
好了,有了上面这段代码以后,我们再来看看装饰器在 JS 中到底是怎么样工作的吧!
当一个装饰器作用于类的时候,大概是这个样子的:
|
|
是不是很像之前我们在 python 中看到的装饰器?
(๑•̀ㅂ•́)و✧
所以这段代码实际上基本等同于:
|
|
那么我们再来看一下作用于类的单个属性方法的装饰器
比如有的时候,我们希望把我们的部分属性置成只读,以避免别人对其进行修改,如果使用装饰器的话,我们可以这样来做:
|
|
我们通过上面的代码把 say
方法设置成了只读,所以在我们后面再次对它赋值的时候就不会生效,调用的还是之前的方法。
在上面的代码中我们可以看到,我们在定义装饰器的时候,参数是有三个,target
、name
、descriptor
。
诶?等一下,这里怎么这么眼熟?⊙_⊙
没错,就是我们上文提到过的关于类的定义那一块儿的 Object.defineProperty
的参数,所以其实装饰器在作用于属性的时候,实际上是通过 Object.defineProperty
来进行扩展和封装的。
所以在上面的这段代码中,装饰器实际的作用形式是这样的:
|
|
嗯嗯,是不是这样看就清楚很多了呢?这里也是 JS 里装饰器作用于类和作用于类的属性的不同的地方。
我们可以看出,当装饰器作用于类本身的时候,我们操作的对象也是这个类本身,而当装饰器作用于类的某个具体的属性的时候,我们操作的对象既不是类本身,也不是类的属性,而是它的描述符(descriptor),而描述符里记录着我们对这个属性的全部信息,所以,我们可以对它自由的进行扩展和封装,最后达到的目的呢,就和之前说过的装饰器的作用是一样的。
当然,如果你喜欢的话,也可以直接在 target
上进行扩展和封装,比如
|
|
OK,让我们再来看一下 JS 里对于装饰器的描述吧:
Decorators make it possible to annotate and modify classes and properties at design time.
A decorator is:
- an expression
- that evaluates to a function
- that takes the target, name, and decorator descriptor as arguments
- and optionally returns a decorator descriptor to install on the target object
装饰器允许你在类和方法定义的时候去注释或者修改它。装饰器是一个作用于函数的表达式,它接收三个参数 target
、 name
和 descriptor
, 然后可选性的返回被装饰之后的 descriptor
对象。
现在是不是对装饰器的作用及原理都清楚了呢?
最后一点就是,现在装饰器因为还在草案阶段,所以还没有被大部分环境支持,如果要用的话,需要使用 Babel 进行转码,需要用到 babel-plugin-transform-decorators-legacy
这个插件:
|
|
如果你感兴趣的话,也可以看一下转码以后的代码,我这里就不做详细介绍了,很有帮助哦~
如果本文描述的有错误的地方,欢迎留言~ ヾ(o◕∀◕)ノ
css遮罩是2008年4月由苹果公司添加到webkit引擎中的。遮罩提供一种基于像素级别的,可以控制元素透明度的能力,类似于png24位或png32位中的alpha透明通道的效果。
2012年11月15号,遮罩第一次出现在W3C公布的草案中。但是跟苹果公司的是不同的版本。
摘录自: http://www.w3cplus.com/css3/css-masking.html
当前(2016.10.19)mask
处于 候选标准阶段(CR),还不是正式标准(REC),webkit/blink 内核加前缀 -webkit-
可使用。
以下是来自 caniuse的统计:
ie/edge 全面不支持,Android 和 iOS Safari 阵营几乎全线飘浅绿,意味着支持部分功能 。不过,Android 4.0 及以下版本的对 mask
的兼容性并不友好!多亏了近几年智能手机市场的良(e)性竞争,给移动前端制造了一个相对良好的环境,以下是 Android 各版本的市场占用率:
Android 4.0 以下版本的占有率不足 5%,已不在主流测试机型内可以忽略不计。
蒙板可以是 CSS3 渐变或者半透明的PNG图片,蒙板元素的alpha值为0的时候会覆盖下面的元素,为1的时候会完全显示下面的内容。如下:
到目前为止,mask 在 W3C 上一共有6个版本(5个草案+1候选标准),如下:
当前 -webkit-mask
的标准基本上等同于第一个WD(草案)。(有理由相信『W3C Working Draft 15 November 2012』参考了 webkit.org 的 『 CSS Masks』)
Definitions of CSS properties and values in this specification are analogous to definitions in CSS Backgrounds and Borders [CSS3BG].
摘录自: https://www.w3.org/TR/css-masking-1/#terminology
按 W3C 官网的说法,mask
的语法与 background
是相仿的。以下是 mask 与 background 属性的对照表:
mask | background |
---|---|
mask-clip | background-clip |
*mask-composite | - |
mask-image | background-image |
*mask-mode | - |
mask-origin | background-origin |
mask-position | background-position |
mask-repeat | background-repeat |
mask-size | background-size |
- | background-attachment |
- | background-color |
上表中,mask 与 background 对应的六个属性在 webkit/blink 内核都能完全支持,并且与W3C的标准保持一致,语法与 background
相通。background 的语法不赘述。
另外两个属性的情况如下:
mask-mode
当前没有任何浏览器支持。-webkit-mask-composite
属性值与W3C不同, 参见:『 -webkit-mask-composite - CSS | MDN』与 『mask-composite - CSS | MDN』图标的主流方案有两种:Icon-Font 和 SVG Sprite。凹凸实验室推荐使用 SVG Sprite,具体参见 @高大师 的大作『 拥抱Web设计新趋势:SVG Sprites实践应用』。
笔者看来,这两种方案存在一个前提:图标必须是矢量图。这其实是道门槛,不是所有团队/个人都跨得过去的。
本节将介绍一个低成本的方案:Mask Icon
素材icon.png :
SASS源码:
HTML源码:
|
|
截图如下:
使用起来跟 Icon-Font 是很类似,不过 mask
方案是通过 background-color
控制 icon 颜色,而 Icon-Font 是通过 color
。 color
比 background-color
有优势,它可以继承当前文本的色值,而 background-color 却无法继承文本色值(因为继承当前文本的背景色等同于透明)。
能实现通过 color
来改变 mask
icon 的颜色吗?
大神张鑫旭在『currentColor-CSS3超高校级好用CSS变量』给出了答案:currentColor。
|
|
|
|
通过改进mask 图标几乎可以取代 Icon-Font。
测试DEMO:
浏览器兼容是 CSS3 Mask
的最大局限,以 mask-image
为例,浏览器的支持情况如下:
PC端:
移动端:
webkit 两兄弟支持 mask-image
,而 firefox 和 ie/edge 需要通过 SVG Mask 来实现蒙层。
移动端 ≈ Android + iOS, Mask Icon
可当作为移动端的一个便捷方案。
The mask of a box can have multiple layers. The number of layers is determined by the number of comma-separated values in the ‘mask-image’ property.
摘录自:https://www.w3.org/TR/2012/WD-css-masking-20121115/#layering
按W3C的定义,mask 可设置多蒙层 。由于缺少多蒙层的兼容性数据,笔者做了一个简单的例子来检验多蒙层在不同机型下的表现。
目标图形:
素材:
测试用机:
iPhone 6/6+(iOS 10)、 荣耀3c(Android 4.2.2)、 红米(Android 4.4.4 )、魅蓝(Android 4.4.4) 三星GT-I9300(Android 4.3)、Microsoft Limia RM-1090(windows mobile)
测试代码如下:
|
|
测试结果:
设备 | 浏览器 | 支持度 | 截图 |
---|---|---|---|
iPhone 6/6+ (iOS 9/10) |
Safari/WeChat | 支持 | |
魅蓝 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
魅蓝 (Android 4.4.4) |
原生浏览器 | 支持 | |
红米 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
红米 (Android 4.4.4) |
原生浏览器 | 支持 | |
三星GT-I9300 (Android 4.3) |
WeChat 6.3.25 | 支持 | |
三星GT-I9300 (Android 4.3) |
原生浏览器 | 不友好 | |
荣耀3c (Android 4.2.2) |
WeChat 6.3.25 | 支持 | |
荣耀3c (Android 4.2.2) |
原生浏览器 | 不友好 | |
Lumia RM-1090 (windows mobile) |
ie | 不支持 |
测试解读:
虽然测试的机型不多,但仍然可以看出 Android 4.3及以下版本原生浏览器对多蒙层的支持不友好,windows mobile是 不支持,而使用 X5
内核的 WeChat 6.3.25对多蒙层的支持度是良好的。
将 取消多蒙层 视作 多蒙层的降级以处理不友好的机型:
– 降级 ->
通过 @supports
来做降级处理:
|
|
不过,使用 @supports 后,(荣耀3c与三星 GT I9300的)X5内核也被降级,这并不是最理想的结果。以下是测试DEMO:
(未使用 @supports 降级)
(使用 @supports 降级)
测试结论
在实际项目中,笔者经常使用 border-image
来实现 background 没办法简单实现的需求。mask 有一个 mask-border
的成员与 border-image 相对应,具体可参见:https://www.w3.org/TR/css-masking/#mask-borders。
现阶段(2016.10.19),未有任何主流浏览器直接支持 mask-border,因为在 mask 草案(WD)的漫长修改过程中,mask-border 经历了两次名称的变更,具体过程如下:
W3C Working Draft 15 November 2012 – mask-box-image
W3C Working Draft 20 June 2013 – mask-box-image
W3C Last Call Working Draft, 29 October 2013 – mask-box-image
W3C Working Draft, 13 February 2014 – mask-box
W3C Last Call Working Draft, 22 May 2014 – mask-border
W3C Candidate Recommendation, 26 August 2014 – mask-border
这个过程与 flex
是相似的。
当前 webkit/blink 内核 (Chrome/Safari)采用了第一个WD(草案)的 mask-box-image
。不过,mask-border
代表了未来,所以在写 css 时少不了兼容的代码,这个后面会介绍到。
The syntax of mask-border corresponds to the border-image property of CSS Background and Borders [CSS3BG].
摘录自: https://www.w3.org/TR/css-masking-1/#mask-borders
*为了方便描述,本章的 mask-border 都是指 mask-box-image ,读者自觉替换
mask-border 与 border-image 的对照列表如下:
mask-border | border-image |
---|---|
mask-border-source | border-image-source |
*mask-border-mode | - |
mask-border-slice | border-image-slice |
mask-border-width | border-image-width |
mask-border-outset | border-image-outset |
mask-border-repeat | border-image-repeat |
mask-border-mode
是在『 W3C Last Call Working Draft, 22 May 2014 』才出现的属性,当前没有任何浏览器支持 ,所以 mask-border 与 border-image 在语法上是完全相通!理解了 border-image就理解了 mask-border 。border-image 的具体使用可以参考MDN的『border-image - CSS | MDN』,这里也不赘述。
如果是手写 css 的同学,在使用 mask-border 需要注意代码的兼容性。其实很简单如下:
|
|
如果是使用 sass
、less
的同学,推荐安装 autoprefixer 插件。autoprefixer
可以为使用者解决 css 兼容写法的烦恼,有了它,mask-border 可以直接使用兼容未来写法:
|
|
上节多蒙层的例子使用 mask-border 来实现会更合适:
目标图形:
素材:
测试用机:
iPhone 6/6+(iOS 10)、 荣耀3c(Android 4.2.2)、 红米(Android 4.4.4 )、魅蓝(Android 4.4.4) 三星GT-I9300(Android 4.3)、Microsoft Limia RM-1090(windows mobile)
测试代码:
|
|
|
|
以下是测试DEMO:
测试结果:
设备 | 浏览器 | 支持度 | 截图 |
---|---|---|---|
iPhone 6/6+ (iOS 9/10) |
Safari/WeChat | 支持 | |
魅蓝 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
魅蓝 (Android 4.4.4) |
原生浏览器 | 支持 | |
红米 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
红米 (Android 4.4.4) |
原生浏览器 | 支持 | |
三星GT-I9300 (Android 4.3) |
WeChat 6.3.25 | 支持 | |
三星GT-I9300 (Android 4.3) |
原生浏览器 | 支持 | |
荣耀3c (Android 4.2.2) |
WeChat 6.3.25 | 支持 | |
荣耀3c (Android 4.2.2) |
原生浏览器 | 支持 | |
Lumia RM-1090 (windows mobile) |
ie | 不支持 |
测试结论mask-border
在移动端的兼容性很好,推荐使用。
CSS Masks
-webkit-mask-composite - CSS | MDN
mask-composite - CSS | MDN
CSS Masking Module Level 1
mask - CSS | MDN
拥抱Web设计新趋势:SVG Sprites实践应用
ICON-FONT图标字体的四类制作方法
currentColor-CSS3超高校级好用CSS变量
-webkit-mask-box-image - CSS | MDN
border-image - CSS | MDN
朋友,你听说过 CSS3 Mask 这个属性吗?
没听说过?不是很了解?
没关系,听我娓娓道来。
css遮罩是2008年4月由苹果公司添加到webkit引擎中的。遮罩提供一种基于像素级别的,可以控制元素透明度的能力,类似于png24位或png32位中的alpha透明通道的效果。
2012年11月15号,遮罩第一次出现在W3C公布的草案中。但是跟苹果公司的是不同的版本。
摘录自: http://www.w3cplus.com/css3/css-masking.html
当前(2016.10.19)mask
处于 候选标准阶段(CR),还不是正式标准(REC),webkit/blink 内核加前缀 -webkit-
可使用。
以下是来自 caniuse的统计:
ie/edge 全面不支持,Android 和 iOS Safari 阵营几乎全线飘浅绿,意味着支持部分功能 。不过,Android 4.0 及以下版本的对 mask
的兼容性并不友好!多亏了近几年智能手机市场的良(e)性竞争,给移动前端制造了一个相对良好的环境,以下是 Android 各版本的市场占用率:
Android 4.0 以下版本的占有率不足 5%,已不在主流测试机型内可以忽略不计。
蒙板可以是 CSS3 渐变或者半透明的PNG图片,蒙板元素的alpha值为0的时候会覆盖下面的元素,为1的时候会完全显示下面的内容。如下:
到目前为止,mask 在 W3C 上一共有6个版本(5个草案+1候选标准),如下:
当前 -webkit-mask
的标准基本上等同于第一个WD(草案)。(有理由相信『W3C Working Draft 15 November 2012』参考了 webkit.org 的 『 CSS Masks』)
Definitions of CSS properties and values in this specification are analogous to definitions in CSS Backgrounds and Borders [CSS3BG].
摘录自: https://www.w3.org/TR/css-masking-1/#terminology
按 W3C 官网的说法,mask
的语法与 background
是相仿的。以下是 mask 与 background 属性的对照表:
mask | background |
---|---|
mask-clip | background-clip |
*mask-composite | - |
mask-image | background-image |
*mask-mode | - |
mask-origin | background-origin |
mask-position | background-position |
mask-repeat | background-repeat |
mask-size | background-size |
- | background-attachment |
- | background-color |
上表中,mask 与 background 对应的六个属性在 webkit/blink 内核都能完全支持,并且与W3C的标准保持一致,语法与 background
相通。background 的语法不赘述。
另外两个属性的情况如下:
mask-mode
当前没有任何浏览器支持。-webkit-mask-composite
属性值与W3C不同, 参见:『 -webkit-mask-composite - CSS | MDN』与 『mask-composite - CSS | MDN』图标的主流方案有两种:Icon-Font 和 SVG Sprite。凹凸实验室推荐使用 SVG Sprite,具体参见 @高大师 的大作『 拥抱Web设计新趋势:SVG Sprites实践应用』。
笔者看来,这两种方案存在一个前提:图标必须是矢量图。这其实是道门槛,不是所有团队/个人都跨得过去的。
本节将介绍一个低成本的方案:Mask Icon
素材icon.png :
SASS源码:
HTML源码:
|
|
截图如下:
使用起来跟 Icon-Font 是很类似,不过 mask
方案是通过 background-color
控制 icon 颜色,而 Icon-Font 是通过 color
。 color
比 background-color
有优势,它可以继承当前文本的色值,而 background-color 却无法继承文本色值(因为继承当前文本的背景色等同于透明)。
能实现通过 color
来改变 mask
icon 的颜色吗?
大神张鑫旭在『currentColor-CSS3超高校级好用CSS变量』给出了答案:currentColor。
|
|
|
|
通过改进mask 图标几乎可以取代 Icon-Font。
测试DEMO:
浏览器兼容是 CSS3 Mask
的最大局限,以 mask-image
为例,浏览器的支持情况如下:
PC端:
移动端:
webkit 两兄弟支持 mask-image
,而 firefox 和 ie/edge 需要通过 SVG Mask 来实现蒙层。
移动端 ≈ Android + iOS, Mask Icon
可当作为移动端的一个便捷方案。
The mask of a box can have multiple layers. The number of layers is determined by the number of comma-separated values in the ‘mask-image’ property.
摘录自:https://www.w3.org/TR/2012/WD-css-masking-20121115/#layering
按W3C的定义,mask 可设置多蒙层 。由于缺少多蒙层的兼容性数据,笔者做了一个简单的例子来检验多蒙层在不同机型下的表现。
目标图形:
素材:
测试用机:
iPhone 6/6+(iOS 10)、 荣耀3c(Android 4.2.2)、 红米(Android 4.4.4 )、魅蓝(Android 4.4.4) 三星GT-I9300(Android 4.3)、Microsoft Limia RM-1090(windows mobile)
测试代码如下:
|
|
测试结果:
设备 | 浏览器 | 支持度 | 截图 |
---|---|---|---|
iPhone 6/6+ (iOS 9/10) |
Safari/WeChat | 支持 | |
魅蓝 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
魅蓝 (Android 4.4.4) |
原生浏览器 | 支持 | |
红米 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
红米 (Android 4.4.4) |
原生浏览器 | 支持 | |
三星GT-I9300 (Android 4.3) |
WeChat 6.3.25 | 支持 | |
三星GT-I9300 (Android 4.3) |
原生浏览器 | 不友好 | |
荣耀3c (Android 4.2.2) |
WeChat 6.3.25 | 支持 | |
荣耀3c (Android 4.2.2) |
原生浏览器 | 不友好 | |
Lumia RM-1090 (windows mobile) |
ie | 不支持 |
测试解读:
虽然测试的机型不多,但仍然可以看出 Android 4.3及以下版本原生浏览器对多蒙层的支持不友好,windows mobile是 不支持,而使用 X5
内核的 WeChat 6.3.25对多蒙层的支持度是良好的。
将 取消多蒙层 视作 多蒙层的降级以处理不友好的机型:
– 降级 ->
通过 @supports
来做降级处理:
|
|
不过,使用 @supports 后,(荣耀3c与三星 GT I9300的)X5内核也被降级,这并不是最理想的结果。以下是测试DEMO:
(未使用 @supports 降级)
(使用 @supports 降级)
测试结论
在实际项目中,笔者经常使用 border-image
来实现 background 没办法简单实现的需求。mask 有一个 mask-border
的成员与 border-image 相对应,具体可参见:https://www.w3.org/TR/css-masking/#mask-borders。
现阶段(2016.10.19),未有任何主流浏览器直接支持 mask-border,因为在 mask 草案(WD)的漫长修改过程中,mask-border 经历了两次名称的变更,具体过程如下:
W3C Working Draft 15 November 2012 – mask-box-image
W3C Working Draft 20 June 2013 – mask-box-image
W3C Last Call Working Draft, 29 October 2013 – mask-box-image
W3C Working Draft, 13 February 2014 – mask-box
W3C Last Call Working Draft, 22 May 2014 – mask-border
W3C Candidate Recommendation, 26 August 2014 – mask-border
这个过程与 flex
是相似的。
当前 webkit/blink 内核 (Chrome/Safari)采用了第一个WD(草案)的 mask-box-image
。不过,mask-border
代表了未来,所以在写 css 时少不了兼容的代码,这个后面会介绍到。
The syntax of mask-border corresponds to the border-image property of CSS Background and Borders [CSS3BG].
摘录自: https://www.w3.org/TR/css-masking-1/#mask-borders
*为了方便描述,本章的 mask-border 都是指 mask-box-image ,读者自觉替换
mask-border 与 border-image 的对照列表如下:
mask-border | border-image |
---|---|
mask-border-source | border-image-source |
*mask-border-mode | - |
mask-border-slice | border-image-slice |
mask-border-width | border-image-width |
mask-border-outset | border-image-outset |
mask-border-repeat | border-image-repeat |
mask-border-mode
是在『 W3C Last Call Working Draft, 22 May 2014 』才出现的属性,当前没有任何浏览器支持 ,所以 mask-border 与 border-image 在语法上是完全相通!理解了 border-image就理解了 mask-border 。border-image 的具体使用可以参考MDN的『border-image - CSS | MDN』,这里也不赘述。
如果是手写 css 的同学,在使用 mask-border 需要注意代码的兼容性。其实很简单如下:
|
|
如果是使用 sass
、less
的同学,推荐安装 autoprefixer 插件。autoprefixer
可以为使用者解决 css 兼容写法的烦恼,有了它,mask-border 可以直接使用兼容未来写法:
|
|
上节多蒙层的例子使用 mask-border 来实现会更合适:
目标图形:
素材:
测试用机:
iPhone 6/6+(iOS 10)、 荣耀3c(Android 4.2.2)、 红米(Android 4.4.4 )、魅蓝(Android 4.4.4) 三星GT-I9300(Android 4.3)、Microsoft Limia RM-1090(windows mobile)
测试代码:
|
|
|
|
以下是测试DEMO:
测试结果:
设备 | 浏览器 | 支持度 | 截图 |
---|---|---|---|
iPhone 6/6+ (iOS 9/10) |
Safari/WeChat | 支持 | |
魅蓝 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
魅蓝 (Android 4.4.4) |
原生浏览器 | 支持 | |
红米 (Android 4.4.4) |
WeChat 6.3.25 | 支持 | |
红米 (Android 4.4.4) |
原生浏览器 | 支持 | |
三星GT-I9300 (Android 4.3) |
WeChat 6.3.25 | 支持 | |
三星GT-I9300 (Android 4.3) |
原生浏览器 | 支持 | |
荣耀3c (Android 4.2.2) |
WeChat 6.3.25 | 支持 | |
荣耀3c (Android 4.2.2) |
原生浏览器 | 支持 | |
Lumia RM-1090 (windows mobile) |
ie | 不支持 |
测试结论mask-border
在移动端的兼容性很好,推荐使用。
CSS Masks
-webkit-mask-composite - CSS | MDN
mask-composite - CSS | MDN
CSS Masking Module Level 1
mask - CSS | MDN
拥抱Web设计新趋势:SVG Sprites实践应用
ICON-FONT图标字体的四类制作方法
currentColor-CSS3超高校级好用CSS变量
-webkit-mask-box-image - CSS | MDN
border-image - CSS | MDN
Jinkey原创
感谢 showonne、yubang 技术指导
Demo 地址:
http://demo.jinkey.io/vue2
源码:
https://github.com/Jinkeycode/vue2-example
Vue 是一个前端框架,特点是
数据绑定
比如你改变一个输入框 Input 标签的值,会自动同步更新到页面上其他绑定该输入框的组件的值

组件化
页面上小到一个按钮都可以是一个单独的文件.vue,这些小组件直接可以像乐高积木一样通过互相引用而组装起来


Homebrew 1.0.6(Mac)、Node.js 6.7.0、npm 3.10.3、webpack 1.13.2、vue-cli 2.4.0、Atom 1.10.2
打开终端运行以下命令
|
|
brew install nodejs
用 npm install npm@3.10.3 更新 npm 版本报错:
(node:42) fs: re-evaluating native module sources is not supported.
解决办法:
在官网下载6.70的安装包再安装一次(刚刚相当于帮你配置好环境变量,现在再安装一次升级到最新的 npm)
- 好像以前官网的安装包不会自动配置环境变量的,由于我电脑上之前安装过 nodejs 所以环境变量已经配置好了,不知道现在的安装包会不会自动配置环境变量。
Windows 下直接下载安装包即可
|
|
npm install -g cnpm –registry=https://registry.npm.taobao.org
cnpm install webpack -g
npm install vue-cli -g
Mac
cd 目录路径
vue init webpack-simple 工程名字<工程名字不能用中文>
或者创建 vue1.0 的项目
vue init webpack-simple#1.0 工程名字<工程名字不能用中文>
会有一些初始化的设置,如下输入:
Target directory exists. Continue? (Y/n)
直接回车默认(然后会下载 vue2.0模板,这里可能需要连代理)Project name (vue-test)
直接回车默认Project description (A Vue.js project)
直接回车默认Author
写你自己的名字
工程目录如图所示:
一定要从官方仓库安装,npm 服务器在国外所以这一步安装速度会很慢。
npm install
不要从国内镜像cnpm安装(会导致后面缺了很多依赖库)
cnpm install
vue-router
和网络请求模块vue-resource
cnpm install vue-router vue-resource –save
npm run dev
【重点】后来发现这些坑是由于 npm 不是最新的版本3.10.2, 用 npm 3.9.5就会出现以下坑
解决办法: 请运行以下命令
npm update -g
报错
Error: Cannot find module ‘opn’
Error: Cannot find module ‘webpack-dev-middleware’
Error: Cannot find module ‘express’
Error: Cannot find module ‘compression’
Error: Cannot find module ‘sockjs’
Error: Cannot find module ‘spdy’
Error: Cannot find module ‘http-proxy-middleware’
Error: Cannot find module ‘serve-index’
如果你用的是老版本的 vue-cli 还可能报其他错误,需要更新一下 vue-cli
npm update vue-cli
然后可以查看一下当前全局 vue-cli 的版本
npm view vue-cli
安装一下这个依赖到工程开发环境
cnpm install opn –save-dev
cnpm install webpack-dev-middleware –save-dev
cnpm install express –save-dev
cnpm install compression –save-dev
cnpm install sockjs –save-dev
cnpm install spdy –save-dev
cnpm install http-proxy-middleware –save-dev
cnpm install serve-index –save-dev
cnpm install connect-history-api-fallback –save-dev
再启动项目,报错
ERROR in ./src/main.js
Module build failed: Error: Cannot find module ‘babel-runtime/helpers/typeof’
at Function.Module._resolveFilename (module.js:440:15)
at Function.Module._load (module.js:388:25)
at Module.require (module.js:468:17)
at require (internal/module.js:20:19)
at Object.(/Volumes/MacStorage/Coding/Web/vue-test/node_modules/.6.17.0@babel-core/lib/transformation/file/index.js:6:16)
at Module._compile (module.js:541:32)
at Object.Module._extensions..js (module.js:550:10)
at Module.load (module.js:458:32)
at tryModuleLoad (module.js:417:12)
at Function.Module._load (module.js:409:3)
@ multi main
ERROR in ./~/.2.1.0-beta.8@webpack-dev-server/client/socket.js
Module not found: Error: Can’t resolve ‘sockjs-client’ in ‘/Volumes/MacStorage/Coding/Web/vue-test/node_modules/.2.1.0-beta.8@webpack-dev-server/client’
@ ./~/.2.1.0-beta.8@webpack-dev-server/client/socket.js 1:13-37
@ ./~/.2.1.0-beta.8@webpack-dev-server/client?http://localhost:8080
@ multi main
安装一下 babel-runtime
cnpm install babel-helpers –save-dev
启动项目,再次报错
Module build failed: Error: Cannot find module ‘babel-helpers’
Module build failed: Error: Cannot find module ‘babel-traverse’
Module build failed: Error: Cannot find module ‘json5’
Module build failed: Error: Cannot find module ‘babel-generator’
Module build failed: Error: Cannot find module ‘detect-indent’
Module build failed: Error: Cannot find module ‘jsesc’
找不到依赖那就再安装一下
cnpm install babel-helpers –save-dev
cnpm install babel-traverse –save-dev
cnpm install json5 –save-dev
...不写了,请把全部把旧的环境全部清除,更新到最新版本
遇到
Module build failed: Error: Cannot find module ‘模块名’
那就安装
cnpm install 模块名 –save-dev(关于环境的,表现为npm run dev 启动不了)
cnpm install 模块名 –save(关于项目的,比如main.js,表现为npm run dev 成功之后控制台报错)
比如escape-string-regexp、strip-ansi、has-ansi、is-finite、emojis-list
输入完命令会自动启动浏览器,如果默认打开 IE 不行
npm run dev
自动启动浏览器就会看到这 帅帅的界面了。
推荐 Atom 打开项目,需要安装 Vue 语法高亮的插件
我们来看官网的一个例子,(中文文档请自行上网搜索)

打开 工程目录下的 App.vue
template 写 html,script写 js,style写样式
为了方便叙述,我们把官网例子写在同一个组件内
这里有两个坑:
第一。一个组件下只能有一个并列的 div,可以这么写,所以复制官网示例的时候只要复制 div 里面的内容就好。
但是不能这样写:

第二。数据要写在 return 里面而不是像文档那样子写
错误的写法:
这样子可以自己啃完官网文档组件之前的部分了。
前面讲得基本上都是各种常用组件的数据绑定,下面还得说说的是 Vue 的组件的使用。
在工程目录/src
下创建component
文件夹,并在component
文件夹下创建一个 firstcomponent.vue
并写仿照 App.vue 的格式和前面学到的知识写一个组件。
|
|
duang… 不能按官网文档那样子叫我做就做,我得先试试再告诉你,我做完效果是这样子的,希望观众做完也是这样子。(迷之微笑 )
然后在 App.vue 使用组件 ( 因为在 index.html
里面定义了
第一步,引入。在<script></script>
标签内的第一行写
|
|
第二步,注册。在<script></script>
标签内的 data 代码块后面加上 components: { firstcomponent }。记得中间加英文逗号!!!
|
|
第三步,使用。
在<template></template>
内加上
|
|
完成后的代码:
这时候看看浏览器上的 http://localhost:8080/
页面(之前打开过就会自动刷新),如果你没看到效果是因为你没有对 App.vue
和 firstcomponent.vue
进行保存操作,保存后页面会自动刷新。
之前已经通过命令安装了vue-router
cnpm install vue-router –save
在webpack.config.js
加入别名
|
|
为什么要加 alias 配置项?其作用可以在文档中有相应的描述:
修改完之后的webpack.config.js
是这样子的:
再按之前的方法写一个组件 secondcomponent.vue
|
|
这时候修改 main.js,引入并注册 vue-router
|
|
并且配置路由规则和 app 启动配置项加上 router,旧版的 router.map 方法在 vue-router 2.0
已经不能用了。修改后的main.js
如下:
|
|
这样子改完再打开浏览器看看。
点击那两个链接试试,会发现<router-view class="view"></router-view>
的内容已经展示出来,同时注意浏览器地址已经变更。
另外,也可以把 App.vue 的内容写在 main.js 也是可以的不过不建议这么做
如果你使用 vue1.0和0.7版本的 vue-router,请参照下面这个教程, 他整个系列都不错的,当然仅限于 vue1.0 :
这时候的页面都是静态的(数据在写程序的时候已经固定了不能修改),而每个应用基本上都会请求外部数据以动态改变页面内容。对应有一个库叫 vue-resource
帮我们解决这个问题。
使用命令行安装
cnpm install vue-resource –save
在 main.js 引入并注册 vue-resource
:
|
|
我们在 secondcomponent.vue 上来动态加载数据
添加一个列表:
在 data 里面加入数组 articles 并赋值为[]
然后在 data 后面加入加入钩子函数 mounted
(详细请参照官方文档关于 vue 生命周期的解析),data 和 mount 中间记得记得加逗号
这里使用的是豆瓣的公开 GET 接口,如果接口是跨域的 POST 请求,则需要在服务器端配置:
Access-Control-Allow-Origin: *
这时候运行看看。等一会接口返回数据,咦,数据加载出来了,棒棒哒 !
更多 vue-router
的使用方法可以看
vue-router 0.7
http://m.doc00.com/doc/1001004eg
vue-router 2.0
http://router.vuejs.org/zh-cn/index.html
组件、双向绑定、路由、数据请求等基本特性都能用了,写到这里一个单页应用基本上成型了。但是,这几面也太 TM 难看了吧。自己写 UI 框架太费劲?那就上网找一个吧。
本来想给大家介绍 Vux 的,因为他用的是微信的 WeUI 设计规范,对于开发微信小程序或者微信内的网页非常和谐,但由于写这篇文章的时候 Vux 还不支持 vue2.0,只能用别的框架了。
命令行安装 ElementUI (此处某公司的人应该发红包了…)
cnpm install element-ui@next -S
然后在 main.js 引入并注册
|
|
保存,这时候程序报错
Uncaught Error: Module parse failed: /Users/**/Desktop/vue2/node_modules/.1.0.0-rc.5@element-ui/lib/theme-default/index.css Unexpected character ‘@’ (1:0)
You may need an appropriate loader to handle this file type.
官网文档又有坑了,安装教程也不跟我们说这一步,当我们都是高手了…
报错是由于我们引入了index.css
这个 CSS 文件,但是 webpack 打包的时候无法识别并转换成 js,所以就需要配置才能读取 css 和字体文件,运行命令安装下面三个东西(如果之前安装过就不需要了)
cnpm install style-loader –save-dev
cnpm install css-loader –save-dev
cnpm install file-loader –save-dev
在 webpack.config.js
中的 loaders 数组加入以下配置,记得该加逗号的地方加逗号!
|
|
修改完的 webpack.config.js
如下
给豆瓣的电影列表套个衣服(样式) :
|
|
打开浏览器,输入网址:

列表比之前漂亮多了,你还可以参照 ElementUI 的文档使用更多组件样式
npm run build
又报错了…orz
ERROR in build.js from UglifyJs
SyntaxError: Unexpected token punc «(», expected punc «:» [build.js:32001,6]
把node_modules/.bin/cross-env里的
require(‘../dist’)(process.argv.slice(2));
后来发现直接运行 webpack 命令就可以打包了
webpack –color –progress
接着把 index.html 和整个 dist 目录丢到服务器就可以了。
]]>前不久抽空对目前比较火的视频直播,做了下研究与探索,了解其整体实现流程,以及探讨移动端HTML5直播可行性方案。
发现目前 WEB 上主流的视频直播方案有 HLS 和 RTMP,移动 WEB 端目前以 HLS 为主(HLS存在延迟性问题,也可以借助 video.js 采用RTMP),PC端则以 RTMP 为主实时性较好,接下来将围绕这两种视频流协议来展开H5直播主题分享。
HTTP Live Streaming(简称 HLS)是一个基于 HTTP 的视频流协议,由 Apple 公司实现,Mac OS 上的 QuickTime、Safari 以及 iOS 上的 Safari 都能很好的支持 HLS,高版本 Android 也增加了对 HLS 的支持。一些常见的客户端如:MPlayerX、VLC 也都支持 HLS 协议。
HLS 协议基于 HTTP,而一个提供 HLS 的服务器需要做两件事:
浏览器使用的是 m3u8 文件。m3u8 跟音频列表格式 m3u 很像,可以简单的认为 m3u8 就是包含多个 ts 文件的播放列表。播放器按顺序逐个播放,全部放完再请求一下 m3u8 文件,获得包含最新 ts 文件的播放列表继续播,周而复始。整个直播过程就是依靠一个不断更新的 m3u8 和一堆小的 ts 文件组成,m3u8 必须动态更新,ts 可以走 CDN。一个典型的 m3u8 文件格式如下:
#EXTM3U
#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=200000
gear1/prog_index.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=311111
gear2/prog_index.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=484444
gear3/prog_index.m3u8#EXT-X-STREAM-INF:PROGRAM-ID=1, BANDWIDTH=737777
gear4/prog_index.m3u8
可以看到 HLS 协议本质还是一个个的 HTTP 请求 / 响应,所以适应性很好,不会受到防火墙影响。但它也有一个致命的弱点:延迟现象非常明显。如果每个 ts 按照 5 秒来切分,一个 m3u8 放 6 个 ts 索引,那么至少就会带来 30 秒的延迟。如果减少每个 ts 的长度,减少 m3u8 中的索引数,延时确实会减少,但会带来更频繁的缓冲,对服务端的请求压力也会成倍增加。所以只能根据实际情况找到一个折中的点。
对于支持 HLS 的浏览器来说,直接这样写就能播放了:
|
|
注意
:HLS 在 PC 端仅支持safari浏览器,类似chrome浏览器使用HTML5 video
标签无法播放 m3u8 格式,可直接采用网上一些比较成熟的方案,如:sewise-player、MediaElement、videojs-contrib-hls、jwplayer。
Real Time Messaging Protocol(简称 RTMP)是 Macromedia 开发的一套视频直播协议,现在属于 Adobe。这套方案需要搭建专门的 RTMP 流媒体服务如 Adobe Media Server,并且在浏览器中只能使用 Flash 实现播放器。它的实时性非常好,延迟很小,但无法支持移动端 WEB 播放是它的硬伤。
虽然无法在iOS的H5页面播放,但是对于iOS原生应用是可以自己写解码去解析的, RTMP 延迟低、实时性较好。
浏览器端,HTML5 video
标签无法播放 RTMP 协议的视频,可以通过 video.js 来实现。
|
|
协议 | 原理 | 延时 | 优点 | 使用场景 | |
---|---|---|---|---|---|
HLS | 短链接Http | 集合一段时间数据生成ts切片文件更新m3u8文件 | 10s - 30s | 跨平台 | 移动端为主 |
RTMP | 长链接Tcp | 每个时刻的数据收到后立即发送 | 2s | 延时低、实时性好 | PC+直播+实时性要求高+互动性强 |
目前直播展示形式,通常以YY直播、映客直播这种页面居多,可以看到其结构可以分成三层:① 背景视频层 ② 关注、评论模块 ③ 点赞动画
而现行H5类似直播页面,实现技术难点不大,其可以通过实现方式分为:① 底部视频背景使用video视频标签实现播放 ② 关注、评论模块利用 WebScoket 来实时发送和接收新的消息通过DOM 和 CSS3 实现 ③ 点赞利用 CSS3 动画
了解完直播形式之后,接下来整体了解直播流程。
直播整体流程大致可分为:
视频采集端:可以是电脑上的音视频输入设备、或手机端的摄像头、或麦克风,目前以移动端手机视频为主。
直播流视频服务端:一台Nginx服务器,采集视频录制端传输的视频流(H264/ACC编码),由服务器端进行解析编码,推送RTMP/HLS格式视频流至视频播放端。
视频播放端:可以是电脑上的播放器(QuickTime Player、VLC),手机端的native播放器,还有就是 H5 的video标签等,目前还是以手机端的native播放器为主。
对于H5视频录制,可以使用强大的 webRTC (Web Real-Time Communication)是一个支持网页浏览器进行实时语音对话或视频对话的技术,缺点是只在 PC 的 Chrome 上支持较好,移动端支持不太理想。
① 调用 window.navigator.webkitGetUserMedia()
获取用户的PC摄像头视频数据。
② 将获取到视频流数据转换成 window.webkitRTCPeerConnection
(一种视频流数据格式)。
③ 利用 WebScoket
将视频流数据传输到服务端。
注意
:虽然Google一直在推WebRTC,目前已有不少成型的产品出现,但是大部分移动端的浏览器还不支持 webRTC(最新iOS 10.0也不支持),所以真正的视频录制还是要靠客户端(iOS,Android)来实现,效果会好一些。
① 音视频的采集,利用AVCaptureSession和AVCaptureDevice可以采集到原始的音视频数据流。
② 对视频进行H264编码,对音频进行AAC编码,在iOS中分别有已经封装好的编码库(x264编码、faac编码、ffmpeg编码)来实现对音视频的编码。
③ 对编码后的音、视频数据进行组装封包。
④ 建立RTMP连接并上推到服务端。
① 先clone nginx项目到本地:
|
|
② 执行安装nginx-rtmp-module
|
|
查找到nginx.conf配置文件(路径/usr/local/etc/nginx/nginx.conf),配置RTMP、HLS。
① 在http节点之前添加 rtmp 的配置内容:
|
|
② 在http中添加 hls 的配置
|
|
重启nginx服务,浏览器中输入 http://localhost:8080,是否出现欢迎界面确定nginx重启成功。
当服务器端接收到采集视频录制端传输过来的视频流时,需要对其进行解析编码,推送RTMP/HLS格式视频流至视频播放端。通常使用的常见编码库方案,如x264编码、faac编码、ffmpeg编码等。
鉴于 FFmpeg 工具集合了多种音频、视频格式编码,我们可以优先选用FFmpeg进行转换格式、编码推流。
1.安装 FFmpeg 工具
2.推流MP4文件
视频文件地址:/Users/gao/Desktop/video/test.mp4
推流拉流地址:rtmp://localhost:1935/rtmplive/home,rtmp://localhost:1935/rtmplive/home
|
|
注意
: 当我们进行推流之后,可以安装VLC、ffplay(支持rtmp协议的视频播放器)本地拉流进行演示
3.FFmpeg推流命令
① 视频文件进行直播
|
|
② 推流摄像头+桌面+麦克风录制进行直播
|
|
更多命令,请参考:
FFmpeg处理RTMP流媒体的命令大全
FFmpeg常用推流命令
移动端iOS和 Android 都天然支持HLS协议,做好视频采集端、视频流推流服务之后,便可以直接在H5页面配置 video 标签播放直播视频。
|
|
ps
:① video标签添加webkit-playsinline
属性(iOS支持)是保证视频在网页中内嵌播放。
② 针对微信浏览器,video标签层级最高的问题,需要申请添加白名单,请参照 http://bbs.mb.qq.com/thread-1242581-1-1.html?ptlang=2052。
本文从视频采集上传,服务器处理视频推流,以及H5页面播放直播视频一整套流程,具体阐述了直播实现原理,实现过程中会遇到很多性能优化问题。
① H5 HLS 限制必须是H264+AAC编码。
② H5 HLS 播放卡顿问题,server 端可以做好分片策略,将 ts 文件放在 CDN 上,前端可尽量做到 DNS 缓存等。
③ H5 直播为了达到更好的实时互动,也可以采用RTMP协议,通过video.js
实现播放。
参考资料:
]]>VR即Virtual Reality虚拟现实,大白话来讲,就是利用电脑创造一个全新的虚拟世界,通过欺骗你的感官(目前更多是视觉上),让你置身于一个与现实相似又完全虚拟的世界中,这就像是在《黑客帝国》中,主人公尼奥对这个看似真实的世界产生怀疑,在崔妮蒂的帮助下,最终与墨菲斯见面,并吃下了象征真相的红色药丸,在矩阵醒来后的尼奥发现,原来他一直生活在矩阵所创造的虚拟世界中。
《Pygmalion’s Spectacles》(《皮格马利翁的眼镜》)
虚拟现实这个概念最早来自于Stanley G. Weinbaum在1935年发表的科幻小说《皮格马利翁的眼镜》,被认为是探讨虚拟现实的第一部科幻作品,简短的故事中详细的描述了包括嗅觉、触觉和全息眼镜为基础的虚拟现实系统。
Sensorama
VR的雏形,要追溯到1962年被称为虚拟现实之父Morton Heilig发明的Sensorama,它的外观就像是一个笨重的街机盒子。
Morton Heilig发明Sensorama的初衷是为了打造未来的电影,在当时那个年代拍摄3D电影并不容易,Morton Heilig为了配合拍摄,又发明了3D摄像机和投影仪,为Sensorama拍摄了五部短片,以直升机、卡丁车、自行车和摩托车等骑行体验为主,Sensorama播放在布鲁克林骑摩托车的短片时能够提供真实的3D影像,感受行车时的颠簸,周围环境的立体声、迎面吹来的风和气味,这在当时大部分人都还在使用黑白电视的时代,是多么令人震撼的,遗憾的是该项目未能获得资金的支持,因此Sensorama的研发工作止步不前,最终回看历史,其实它更像是现代的4D电影。
The Sword of Damocles(达摩克利斯之剑)
1968年,Ivan Edward Sutherland和他的学生Bob Sproull在麻省理工学院的林肯实验室创造了第一个虚拟现实及增强现实头戴式显示器系统,第一个显示应用是悬浮在空中的一个立方体,这款头戴显示器因为要跟踪用户的视线,需要通过机械臂移动,因此设备非常笨重,只能挂在天花板上减轻重量使用,被戏称为“达摩克利斯之剑”。
Sega VR(世嘉VR)
1991年世嘉宣布了Sega VR,定价为200美元,从1991年到1994年,世嘉就在起VR设备上不断宣传,并且在1993年的夏季CES展会上提供VR的试玩,就在大家万分期待之时,1994年世嘉表示停止该项目,给出的理由是虚拟现实效果过于逼真,同时佩戴耳机的用户有可能移动和伤害到自己,当然明白人都知道这是个借口,花了如此大力气的投入和探索最终却放弃了,原因或许是因为SEGA VR的游戏和软件太少了,在夏季CES展会上也只是公布了屈指可数的4款游戏:《Nuclear Rush》、《Iron Hammer》、《Matrix Runner》、《Outlaw Racing》。
Virtual Boy
1995年,任天堂发布了Virtual Boy,Virtual Boy采用二极管和视差技术创造出游戏的3D效果,是任天堂在N64上市之前用来取代Game Boy的32位便携游戏机,由设计Game Boy的横井军平主导,最终发售22个游戏,全球销量77万台,造成其失败的原因很大程度是因为Virtual Boy只能提供红黑两色的游戏画面,760克的重量需要支架支撑,玩家只能卷缩身体坐着玩,并且玩家在过程中会有强烈的眩晕感。
Oculus Rift
VR这个词在近两年火热起来也正是因为Oculus Rift,92年生的Palmer Freeman Luckey对任何事物皆有好奇其心的人,喜欢收藏各类VR设备,并且是Meat to be seen技术论坛的版主,Palmer对市面上VR的低对比度、高延迟和低视野感到失望,他希望制作一个能跟踪玩家头部转动的360度全景,并以极低的延迟显示画面的VR头戴设备。
同样作为Meat to be seen技术论坛成员之一的FPS之父卡约翰·卡马克,看到此项目非常感兴趣,要求给予一台原型机,并对《DOOM3》进行VR适配,在2012年E3展出此设备,获得了E3最佳外设奖,一时间获得各大媒体的关注,随后在Kickstarter发起众筹,短时间内就获得超过9500人支持,总资金达243万美金。
在2014年7月,Oculus Rift被Facebook以20亿美元收购后,各个厂家似乎又看到另一片蓝海,大量的涌入这个市场,HTC的HTC Vive,索尼的PlayStation VR,各类的手机VR等等。
历史讲完回到正题,Mozilla认为目前的WebVR发展太困难了,为了使其更加快速、方便、愉快的打造3D/VR场景,2015年12月16日Mozilla旗下的MozVR团队,在经过长时间的试验,并且从一些3D/VR工具(例如:JanusVR、GLAM、SceneVR)中汲取经验,公布了JavaScript开源框架A-Frame。
A-Frame使用Web开发者熟悉的HTML标签来创建WebVR场景,使得非WebGL开发者无需学习强大而又复杂的WebGL API来创建VR场景,降低了初学者学习的门槛,并且A-Frame一个最大的优势在于跨平台性,目前版本已迭代到v0.3.0,支持VR头戴设备Oculus Rift和HTC Vive以及非VR设备的PC端和Mobile端(可开启VR模式,利用Google Cardboard、Gear VR等设备观看)。
简单概括A-Frame特点如下:
A-Frame为开发者提供了许多的功能模块,例如在官网Docs里Primitives提供了基础的几何形状(盒子、球面、圆柱、平面等)、可导入3D建模工具制作或从网上下载的Collada模型、定义背景的天空、定义用户从哪个角度观看场景的相机、动画、光影、全景视频等功能,通过标签开发者就可以轻松的创建WebVR场景,更多功能可以到官网查看。
这里以官网的一个例子作为基础讲讲怎么使用A-Frame。
1、创建场景
<a-scene>
是一个全局的根对象,所有实体都存在于这个场景中,<a-scene>
处理了所有three.js和WebVR boilerplate需要做的事:
2、创建天空
|
|
<a-sky>
允许为场景设置纯色背景或是一张360度全景图片,<a-assets>
则是预加载WebVR页面所需的资源。
3、创建相机
<a-camera>
定义了用户从哪个角度观看场景,改变<a-camera>
的position
需要将其置于<a-entity>
中,直接在<a-camera>
设置不会生效。
4、创建球面、立方体、圆柱、平面
position
定位实体x、y、z轴的位置rotation
调整实体x、y、z轴的旋转角度color
改变实体的颜色depth
景深width
、height
设置实体的宽高radius
圆角半径5、添加动画
需要为某个实体添加动画则将<a-animation>
置于标签之内,这里我给球scale
缩放,立方体rotation
360度Y轴旋转,圆柱改变height
,并且无限次轮流反向播放动画。
attribute
定义要执行的动画属性from
动画的起始to
动画的结束repeat
定义了要循环的次数,可以是一个数字或indefinite表示无限循环dur
动画的时长direction
动画轮流反向播放A-Frame的动画属性与CSS3 animation非常相似,还有填充模式fill
、延时delay
、速度曲线easing
等等。
6、创建光标
通过给相机增加一个光标cursor
,让我们可以通过点击和注视与实体互动。
|
|
一个框架是否能持续发展,很大程度看社区的活跃状态,A-Frame目前在Github上已经超过3500个star,Mozilla鼓励开发者们加入1400人的A-Frame Slack聊天组,并在社区中分享自己的作品,在A-Frame Twitter里,每周都会精选出优秀的A-Frame作品供开发者们参考学习。
在A-Frame的Github Roadmap后续的版本更新线路图中表明,后续除了常规的升级外还将推出教程(制作更多的学习资源、文章、指南、录屏)帮助初学者更好学习。
360全景图素材
3D模型免费素材
资源集
说到 3D Touch 不能不提与之相似的 Force Touch。Force Touch
是苹果公司在 2014 年 9 月公布的一项压力敏感屏幕技术,最早用于 Apple Watch ,可识别轻点、轻按两种操作。随后 Force Touch 于 2015 年 9 月在 iPhone 6s 上得到改进并更名为 3D Touch,提供了更高灵敏度的触控力度识别、及更强的触感反馈,支持轻点、轻按及重按三个维度。
支持 3D Touch 的设备,目前有 iPhone 6s、iPhone 6s Plus、iPhone 7 以及 iPhone 7 Plus。
3D Touch 最为典型的交互有 Quick Actions 和 Peek and Pop 两种。在 APP 图标上重按呼出一组快捷操作菜单,这即是典型的 Quick Actions:
而使用 Peek and Pop 则可以快速地对内容进行预览,以及后续的其他操作:
如上图所示,在系统的邮件 APP 中,以一定的力度按压邮件列表中的某一项,会触发 Peek 弹出一个内容预览窗口(在 Peek 状态下上滑还能触发 Quick Actions 调出一些快捷操作项哦),如果继续加大按压力度,则会触发 Pop 进入邮件内容界面,这整个过程就称之为 Peek and Pop。
除了邮件 APP 以外,信息、照片等多个系统 APP 以及一些第三方 APP (如 微信、Facebook、Twitter 等)也都很好的支持了 Peek and Pop 这种 3D Touch 交互形式。
要在网页中实现 3D Touch,需要用到以下两个知识点:
1、Touch.force
在 touch 对象中包含有一个名为 force
的只读属性,它的取值从 0 到 1,表示的是触碰点的按压力度,0 表示没有检测到压力,而 1 则是设备能识别出的最大压力
2、touchforcechange
touchforcechange
是 Safari 10 新增的事件,该事件会在按压力度改变时被触发
(注:在 MacOS Safari 上也有与之对应的 webkitmouseforcechanged
事件,该事件会在支持 Force Touch 的 Trackpad 上反应出按压力度值 force 的变化,但本文仅讨论手机设备的情况)
要实现 3D Touch 效果,关键在于实时地获取 Touch.force 的值。而由于网页上的 3D Touch 很大程度上受限于设备及浏览器的支持情况,因此我们划分以下 3 种情况,分别来看看要如何实现
1、支持 3D Touch 且升级到了 iOS 10 的设备
在这种最为理想的情况下,只需要监听 touchforcechange 事件即可获取到 force 的当前值,将 force 值的变化以适当的形式反馈在界面上以实现 3D Touch 效果。
2、支持 3D Touch 但系统版本低于 iOS 10 的设备
这种情况虽然无法监听 touchforcechange 事件,但 Touch 对象的 force 属性仍然可以反应出正确的按压力度,可以巧妙地设置一个定时器,以轮询的方式获取 force 的当前值
3、不支持 3D Touch 的设备
这种情况下 Touch.force 的取值始终为 0,虽然可以用长按的交互形式来代替,但建议还是以优雅降级的方式,索性就不处理了吧
看到这里你肯定想说 “Shut up and show me the code…” 好的,那我们来看一个例子,在这个事例页面,用支持 3D Touch 的设备按压蓝色按钮可以将树懒兄逗笑哦,嘿嘿嘿~
你可扫描以上二维码,或戳我进行预览,注意请使用 iOS Safari 浏览器进行访问!使用 iOS Safari 浏览器!!使用 iOS Safari 浏览器!!!重要的事情要说三遍。。因为目前微信 WebView 并不支持 3D Touch。
实现思路其实比较简单,根据刚刚说到的知识,我们分别监听 touchforcechange、touchstart、touchend、touchcancel 事件
而树懒兄大笑的动画则用的是以下这张雪碧图,根据当前 Touch.force 值来设置 background-position 以显示对应的动画帧来实现的
你可以访问这个 Github 项目 来查看源码,核心代码位于 ThreeDTouch.js,该文件封装了一个名为 ThreeDTouch 的类,事例化时传入一个 DOM 对象即可在 callback 中获取到按压力度值的变化。
|
|
通过 Stack Overflow 的搜索结果,你可以很轻易地发现很多人有相同的困扰。我们自己或是不必或是没有时间去钻研所有的边缘的情况。
所以这里有一些博主总结的实用并速记的规则,并且在未来博主也会持续地跟进和更新。
永远不会修改的内容:JS 和 CSS 文件,图片,和任何类型的二进制文件都属于这个类目。
永远,我确实说的是永远。为静态资源指定版本号是很通用的做法。它们无论什么时候改动了,它们的 URL 就改变了。
这里是一些针对静态资源的简单的规则:
|
|
针对静态资源的设置就是那么简单。
针对应用程序私密性和新鲜度方面需求的不同,我们应该使用不同的缓存控制设置。
对于非私密性和经常性变动的资源(想像一下股票信息),我们应该使用下面这些:
这些设置的效果是:这些资源可以被公开地(通过浏览器和代理服务器)缓存起来。每一次在浏览器使用这些资源之前,浏览器或者代理服务器会检查这些资源是否有更新的版本,如果有,就把它们下载下来。
这样的设置需要注意,浏览器在重新检查资源时效性方面有一定的灵活性。典型的是,当用户点击了「返回/前进」按钮时,浏览器不会重新检查这些资源文件,而是直接使用缓存的版本。你如果需要更严格的控制,需要告知浏览器即使当用户点击了「返回/前进」按钮,也需要重新检查这些资源文件,那么可以使用:
不是所有的动态资源都会马上变成过时的资源。如果它们可以保持至少5分钟的时效,可以使用:
经过这样的设置,浏览器只会在5分钟之后才重新检查。在这之前,缓存的内容会被直接使用。如果在5分钟后,这些过时的内容需要严格控制,你可以添加 must-revalidate
字段:
对于私密或者针对用户的内容,需要把 public
替换为 private
以避免内容被代理缓存。
当同时使用 Cache-Control
和 Expires
时,Cache-Control
获得优先权。
同时使用 Cache-Control
和 Expires
意味着得到更广泛的支持(被不同的浏览器和版本)。当然,它们两个应该被配置成相同的时效值,以避免引起困惑。
这两个头在浏览器对资源做重新检查验证的时候会使用到。大致来说,浏览器只是盲目地存储这两个来自于服务器的头的值,然后在需要检查验证的时候,浏览器根据请求条件,把这两个指发送给服务器(分别通过 If-None-Match
和 If-Modified-Since
)。
注意只有在资源过期的情况下,检查验证才会发生。
在有条件的请求下,If-None-Match
和 If-Modified-Since
头的出现取决于服务器。然而,由于是服务器生成的 ETag
和(或) Last-Modified
,所以实际上,这没有什么大问题。大多数的浏览器在可能的情况下都会把着两者都发送给服务器。
参考 What takes precedence: the ETag or Last-Modified HTTP header?
一个通常的建议是:避免使用 ETag
。这不是一个总是有用的建议。ETag
在判断内容是否真的改动方面确实提供了更为精确的控制。针对生成的 ETag
,默认的Apache方法需要把文件的索引节(inode),大小(size)和最后修改时间作为输入求值得到。这会导致在负载均衡的环境中,生成的 ETag
值变得毫无用处,因为每个服务器都会针对相同的文件生成一个不同的 Etag
值。这个可能就是唯一的问题导致很多人完全禁用 ETag
,其实只要精确地针对一个匹配的文件生成一个独一无二的 ETag
值,就没有必要禁用 ETag
了。
当按下 Ctrl-R
时,浏览器会携带下面的请求,以检查是否需要更新缓存内容:
注意这并不只是和原服务器建立连接,其同样适用于代理服务器。本质上,它只是重新检查验证内容。如果服务器回应了一个304,浏览器将会使用缓存的内容。
这个头对于一些人来说可能比较陌生。
当一个资源启用了 gzip 压缩,并且被代理服务器缓存,客户端如果不支持 gzip 压缩,那么在这样的情况下将会得到不正确的数据(也就是,压缩过的数据)。这将会使代理服务器缓存两个版本的资源:一个是压缩过的,一个是没压缩过的。正确版本的资源将在请求头发送之后进行传输。
还有一个现实的原因:IE 浏览器不缓存任何带有 Vary
头但值不为 Accept-Encoding
和 User-Agent
的资源。所以通过这种方式添加这个头,才能确保这些资源在 IE 下被缓存。
本文译自 Bryan Tsai 的 《Http Caching》。
在本文中,我们将会看到一个不同的 import 系统,这个系统基于最新的 JavaScript 标准。我们将使用 ES6 的 import,然后通过Babel转换器将我们的代码转换成在 Node 和浏览器环境都可以运行的兼容性代码。
为什么说
ES6
而不是ES2015
?诚然,ES6
作为一个标签正逐渐被遗忘,因为现在相关委员会已经通过基于时间的命名方案,而不是通过版本数字的方式去管理那些规范。但事实证明,模块和加载器是一个多层次的复杂的业务,包含着后期会推出的不同方面的东西。所以,我们将会使用ES6模块
以避免一些细节。
我们需要在一些前期的文章注1获得一些代码片段作为起点,生成本文的代码:
我们的 index.html 文件非常简单,这来自于 Webpack 相关的文章:
Webpack 的配置也是一样:
经过这些步骤,Webpack 将会把下面的 app1.js
和 lib1.js
打包,通过 webpack-dev-server
提供服务:
|
|
|
|
最后,我们从之前的文章中获取一份 Mocha 测试文件:
|
|
让我们安装 Babel – 一个现代的 JavaScript 转换器,通过一个 loader
在 Webpack 打包的时候进行编译转换:
这一步将会在我们的 package.json
文件的 devDependencies
中添加依赖:
让我们把基于 CommonJS 的模块和导入,转换为基于 ES6 的模块。首先,我们创建一个 app2.js
文件,其使用了不一样的 import 语法:
|
|
当我们这样做的时候,PyCharm 马上就会崩溃。它目前只支持 ECMAScript5.1
标准作为 JavaScript 的语法。我们现在给了它无效的语法,因此我们需要在 Preferences -> Languages & Frameworks -> JavaScript -> JavaScript language version
中,更改其为 ECMAScript 6
。
这时候,ESLint 也会失效 – 它必须要认识这些新的语法。幸运的是,ESLint 和 Babel 一样,都是非常流行和快速更新换代的开源项目。它们很容易就能和 ES2015、ES2016 和 ES2017 协同工作。让我们更新我们的 .eslintrc
文件:
|
|
让我们现在新建一个 lib2.js
文件,其将使用 ES6 的 export 语法导出我们的函数:
|
|
在这种条件下,此模块除了该函数,没有任何可以导入的东西。因为它是一个不一样的,不兼容的语法;我们可以避免繁琐的 module.exports
,而直接在声明中使用 export default
。
在导出语法中使用 default
,意味着我们的意思是把这个函数是这个模块唯一导出的东西。事实上,你还可以像这样导出:
|
|
这就是一个匿名函数的导出方式。通过默认的 export,这个函数的名字来源于导入这个函数的模块,而不是导出这个函数的模块。
我们现在得到了通过 ES6 进行导入和导出的模块。为了查看它在 Node 中是否能正常工作,我们可以在我们的测试中使用 ES6 的 import 来检验:
|
|
让我们跑一个:
汗,报错了:
这好像是 Mocha – 一个 Node 应用,不能解析我们的 JavaScript。好吧,没开玩笑……它现在使用ES6编写的。我们需要一些东西转把「未来」的 JavaScript(ES6 模块)转换成现在的 JavaScript(Node)。
这种转换称为「编译(transpiling)」注2,而 Babel 是使用人数最多的编译工具。我们在 Pythonic JavaScript with ES2015 中谈论了更多关于编译方面的东西。
我们需要告诉 Mocha 以运行我们的 JavaScript – 包括应用代码和测试代码。这些代码包含了需要 Babel 新代码风格。首先,Babel 有一个配置文件,显然现在人手一份这个东西:
|
|
这是用来配置 Babel本身的。为了告知我们的 PyCharm Mocha 运行配置以使用 Babel,我们需要编辑运行配置,在额外的 Mocha 选项(Extra Mocha options)中添加这个:
|
|
我们的 Mocha 测试现在就能正常运行了。这也意味着我们的 lib2.js
也能跑起来了。为了能在 npm run
脚本下处理这些变更,我们需要修改 package.json
:
|
|
这一步适用于 Node。那浏览器下呢?我们需要像 Mocha 那样,告知 Webpack 在打包的时候使用 Babel 编译代码吗?是的,并且这个非常简单:
|
|
我们为所有的 .js
后缀的文件都添加了一个 module
项,并且定义了一个 loader
。这个 loader
通过 babel-loader
软件运行这些文件,而这个软件是一个 Babel 在 Webpack 下的插件。babel-loader
使用的是在 .babelrc
文件下共享的设置,这可以避免 Babel 在 Mocha 和 Webpack 下的独立设置。
我们现在只是使用到了 ES6 中很小一部分的特性:模块和导入。为了使用这部分特性,我们需要引入一个新的工具– Babel 到前端工具链中,并处理由此带来的连锁效应:
在你编程的时候,你的 CPU 诚然会一直处于繁忙的状态,因为它要一直编译和打包。有解决这个问题的办法,但是目前来说,相信我……这很值得,因为在下一遍文章中就会明白了~
本文译自 Paul Everitt 的 《ES6 Imports with Babel》。
语法:
|
|
thisArg:fun函数运行时指定的this值,可能的值为:
例如:
|
|
经常会看到这种使用情况:
|
|
为什么能实现这样的功能将arguments转成数组?首先call了之后,this指向了所传进去的arguments。我们可以假设slice方法的内部实现是这样子的:创建一个新数组,然后for循环遍历this,将this[i]一个个地赋值给新数组,最后返回该新数组。因此也就可以理解能实现这样的功能了。
语法:
|
|
例如:
|
|
平时Math.max只能这样子用:Math.max(5,6,2,3,7)
;
利用apply的第二个参数是数组的特性,从而能够简便地从数组中找到最大值。
语法:
|
|
bind()方法会创建一个新函数,称为绑定函数。
bind是ES5新增的一个方法,不会执行对应的函数(call或apply会自动执行对应的函数),而是返回对绑定函数的引用。
当调用这个绑定函数时,thisArg参数作为 this,第二个以及以后的参数加上绑定函数运行时本身的参数按照顺序作为原函数的参数来调用原函数。
简单地说,bind会产生一个新的函数,这个函数可以有预设的参数。
|
|
把类数组换成真正的数组,bind能够更简单地使用:
|
|
|
|
相同之处:改变函数体内 this 的指向。
不同之处:
http://www.cnblogs.com/coco1s/p/4833199.html
https://segmentfault.com/a/1190000004568767
https://segmentfault.com/a/1190000002929289
http://www.cnblogs.com/coco1s/p/4833199.html
<script>
而改用 SeaJS 这样的 JS 模块加载器了,确实模块加载器对于代码的可维护性带来了较大的提升。SeaJS 是一个模块加载器,模块加载器需要实现两个基本功能:
下文会围绕模块定义规范以及模块系统的启动与运行两方面进行讲解,最后理清一下模块加载大体流程,以及说明与RequireJS的主要区别。
CMD 规范的前身是Modules/Wrappings规范。
SeaJS更多地来自 Modules/2.0 的观点,同时借鉴了 RequireJS 的不少东西,比如将Modules/Wrappings 规范里的 module.declare改为 define 等。
SeaJS遵循的CMD(Common Module Definition) 模块定义规范就是属于Modules/2.0流派阵营。
在 CMD 规范中,一个模块就是一个文件。代码的书写格式如下:
|
|
|
|
SeaJS 只支持 CMD 模块的话,没法实现 JS 文件的合并了,所以SeaJS 还支持一种 Transport 格式。
Transport 格式其实就是加上了名字的 CMD 模块,SeaJS 在遇到这种模块时通过定义的 id 来缓存模块:
|
|
id:模块标识。
deps:一个数组,表示模块依赖。
在用普通压缩工具压缩时,如果项目需要支持 IE,务必写上第一个参数id或通过工具提取 id;而且如果项目对性能有要求,上线后需要合并文件,也确保手工写上 id 参数。
如何理解:SeaJS 只支持 CMD 模块的话,没法实现 JS 文件的合并了?
没有id的两个模块JS文件a.js和b.js合并成一个文件index.js如下:
|
|
首先,CMD 规范 中一个模块就是一个文件,一个文件里面定义了两个,所以出现异常也不奇怪了。
另外,CMD 模块没有显式地指定该模块的 id,同时SeaJS 会用这个 JS 文件的 URL 作为它的 id ,并缓存 id 与 模块之间的关系,因此只有最后一个定义的 CMD 模块会被识别,因为前面定义的模块都被它覆盖了。
实际上在版本 1.3.1 之前,有一个特性叫做firstModuleInPackage,即当一个文件里有多个 define 时,默认将第一个define里的模块作为主模块进行返回。由于各种原因作者去掉了这个特性。
|
|
require是一个函数方法,用来获取其他模块提供的接口,而且是同步往下执行。require的模块不能被返回时,应该返回null。
require.async(id, callback?):用来在模块内部异步加载模块,并在加载完成后执行指定回调。require的模块不能被返回时,callback应该返回null。callback接受返回的模块作为它的参数。
require.resolve(id):不会加载模块,只返回解析后的绝对路径。
注意事项:
为什么那么死规定?!
首先你要知道SeaJS 是如何知道一个模块的具体依赖的。SeaJS 通过 factory.toString() 拿到源码,再通过正则匹配 require 的方式来得到依赖信息。这也是必须遵守 require 书写约定的原因。
有时会希望可以使用 require 来进行条件加载,如下:
在浏览器端中,加载器会把这两个模块文件都下载下来。 这种情况下,推荐使用 require.async 来进行条件加载。
用来在模块内部对外提供接口。
exports 仅仅是 module.exports 的一个引用。在 factory 内部给 exports 重新赋值时,并不会改变 module.exports 的值。因此给 exports 赋值是无效的,不能用来更改模块接口。
模块标识id尽量遵循路径即 ID原则,减轻记忆模块 ID 的负担。
模块标识id会用在 require、 require.async 等加载函数中的第一个参数。
三种类型的标识:
相对标识:以 . 开头(包括.和..),相对标识永远相对当前模块的 URI 来解析。
顶级标识:不以点(.)或斜线(/)开始, 会相对模块系统的基础路径(即 SeaJS配置 的 base 路径)来解析。
|
|
|
|
如上所示,/js/b
可省略后缀.js,但是”.css” 后缀不可省略。
SeaJS 在解析模块标识时,除非在路径中有问号(?)或最后一个字符是井号(#),否则都会自动添加 JS 扩展名(.js)。
通过define定义许多模块后,得让它们能跑起来,如下:
|
|
直接使用 script 标签同步引入sea.js文件后,就可以使用seajs.use(id, callback?)在页面中加载模块了!
最佳实践:
|
|
讲到seajs.use,当然要提一下Sea.js 的调试接口。
模块加载大体流程:
文字描述:
RequireJS 遵循 AMD(异步模块定义)规范,SeaJS 遵循 CMD (通用模块定义)规范。
SeaJS按需执行依赖避免浪费,但是require时才解析的行为对性能有影响。
SeaJS是异步加载模块的没错, 但执行模块的顺序也是严格按照模块在代码中出现(require)的顺序。
RequireJS更遵从js异步编程方式,提前执行依赖,输出顺序取决于哪个 js 先加载完(不过 RequireJS 从 2.0 开始,也改成可以延迟执行)。如果一定要让 模块B 在 模块A 之后执行,需要在 define 模块时申明依赖,或者通过 require.config 配置依赖。
如果两个模块之间突然模块A依赖模块B:SeaJS的懒执行可能有问题,而RequireJS不需要修改当前模块。
当模块A依赖模块B,模块B出错了:如果是SeaJS,模块A执行了某操作,可能需要回滚。RequireJS因为尽早执行依赖可以尽早发现错误,不需要回滚。
|
|
SeaJS努力成为浏览器端的模块加载器,RequireJS牵三挂四,兼顾Rhino 和 node,因此RequireJS比SeaJS的文件大。
RequireJS 有一系列插件,功能很强大,但破坏了模块加载器的纯粹性。SeaJS 则努力保持简单,并支持 CSS 模块的加载。
https://github.com/seajs/seajs/issues/242
https://github.com/seajs/seajs/issues/258
https://github.com/seajs/seajs/issues/263
https://github.com/seajs/seajs/issues/266
https://github.com/seajs/seajs/issues/277
http://www.zhihu.com/question/21157540
http://annn.me/how-to-realize-cmd-loader/
http://chaoskeh.com/blog/why-its-hard-to-combo-seajs-modules.html
https://github.com/cmdjs/specification/blob/master/draft/module.md
https://www.douban.com/note/283566440/
https://imququ.com/post/amd-simplified-commonjs-wrapping.html
https://lifesinger.wordpress.com/2011/05/17/the-difference-between-seajs-and-requirejs/
“目镜在他眼前涂上了一抹朦胧的淡色,映射着一幅弯曲的广角画面:一条灯火辉煌的大街,伸向无尽的黑暗。但这大街其实并不存在,它只是电脑绘出的一片虚拟的空间。”——《Snow Crash》,Neal Stephenson 1992年
VR(Virtual Reality)是利用电脑模拟产生一个三维空间的虚拟世界,提供用户关于视觉等感官的模拟,让用户感觉仿佛身历其境,可以及时、没有限制地观察三维空间内的事物。用户进行位置移动时,电脑可以立即进行复杂的运算,将精确的三维世界视频传回产生临场感。—— 维基百科
了解 VR 显示原理前,先了解我们人眼的立体视觉的成像原理:
人眼的视觉是可以感觉出深度的,也就是深度知觉(depth perception)。而有了深度的信息后,才能判断出立体空间的相对位置。
另外,由于两个眼睛的位置不一样(一般人两眼相距 5~7 厘米),所以看到的东西会有两眼视差(binocular parallax),大脑再将这两个图像做融合处理,从而产生立体的感觉(即所谓的 binocular cues)。
立体视觉
头戴式显示器(HMD)是 VR 目前最常见的一种体验方式。它的原理是将小型二维显示器所产生的图像经由光学系统放大。具体而言,小型显示器所发射的光线经过凸状透镜使图像因折射产生类似远方效果。利用此效果将近处物体放大至远处观赏,从而达到所谓的全息视觉(Hologram)。另外,显示器被分为左右两个部分,分别显示左右眼看到的图像。大脑再将左右眼所看到的图像(两眼视差)做融合处理,从而产生 3D 效果。同时,HMD 会根据头部运动让视角与之同步。综合上述特性,用户通过 HMD 体验 VR 时就如同在现实中观看一样,这种体验也被称为沉浸式体验。
HMD 原理示意图
目前市场上主要有以下 3 种 HMD 设备:
对于想初步体验或入门 VR 的用户,推荐谷歌的 Carboard 或国内的性价比高的滑配式设备。
Google Carboard
VR 是最具科幻色彩以及梦幻体验的东西,单独一个 HMD 并不能发挥 VR 的最大效果,加上“属性加成”的周边才能体验极致的 VR。
2015 年,澳大利亚开设了世界首家 VR 沉浸式竞技游戏店—— Zero Latency。 这家店拥有 4300 平方英尺,安装有 129 台 PlayStation Eye 摄像头,用于捕捉玩家的动作。整套系统可以最多同时供 6 名玩家进行游戏。
现实与虚拟
当然,各式各样的 VR 周边设备也越来越多,如 Virtuix Omni 跑步机:
Virtuix Omni
这些设备无疑会增强了 VR 的体验,给用户带来更加刺激与逼真的体验。
上文说了这么多关于 VR 的东西,视乎还没有入正题(⊙﹏⊙))
许多 VR 体验是以应用程序的形式呈现的,这意味着你在体验 VR 前,必须进行搜索与下载。而 Web VR 则改变了这种形式,它将 VR 体验搬进了浏览器,Web + VR = WebVR。
下面根据我目前的见解,分析一下 WebVR 的现状。
WebVR 是早期和实验性的 JavaScript API,它提供了访问如 Oculus Rift 和 Google Cardboard 等 VR 设备功能的 API。
VR 应用需要高精度、低延迟的接口,才能传递一个可接受的体验。而对于类似 Device Orientation Event 接口,虽然能获取浅层的 VR 输入,但这并不能为高品质的 VR 提供必要的精度要求。WebVR 提供了专门访问 VR 硬件的接口,让开发者能构建舒适的 VR 体验。
WebVR API 目前可用于安装了 Firefox nightly 的 Oculus Rift、Chrome 的实验性版本和 Samsung Gear VR 的浏览器。当然,如果你现在就想在你的移动端浏览器体验 WebVR,可以使用 WebVR Polyfill。
在 Web 上开发 VR 应用,有下面三种(潜在)方式:
由于 WebVR 仍处于草案阶段并可能会有所改变,所以建议你基于 webvr-boilerplate 进行 WebVR 开发。
上面说道,在 Web 上开发 VR 应用有 3 种(潜在)方式,前两种都离不开直接接触 Three.js,而第三种方式则为时尚早。对于没接触过 Three.js,但又想体验一把 WebVR 开发的同学们来说,无疑会存在一定的门槛。
如果你想以较低的门槛体验一把 WebVR 开发,那么可以试试 MozVR 团队 开发的 A-Frame 框架。
PS:写着写着,A-Frame 的版本从 v0.2 升到到 v0.3(这很前端),但文档等各方面变得更加完善了。
A-Frame 是一个通过 HTML 创建 VR 体验的开源 WebVR 框架。通过该框架构建的 VR 场景能兼容智能手机、PC、 Oculus Rift 和 HTC Vive。
MozVR 团队开发的 A-Frame 框架的目的是:让构建 3D/VR 场景变得更易更快,以吸引 web 开发社区进入 WebVR 的生态。WebVR 要成功,需要有内容。但目前只有很少一部分 WebGL 开发者,却有数以百万的 Web 开发者与设计师。A-Frame 要把 3D/VR 内容的创造权力赋予给每个人。
<a-scene>
标签。
减少冗余复杂的代码
talk is cheap,show me the c… hello world.
A-Frame 的 Hello world
在手机的浏览器(如:Chrome、QQ浏览器)中呈现的效果
实现代码如下:
基本概念(以 v0.3 版本为参考):
当然,上述案例只是 A-Frame 的 Hello World demo。若你感兴趣,可以深入学习,再结合自己的想法,相信你能作出让人拍手称赞的作品。
下面就列举一些 WebVR 的相关案例,如果你已具备了 VR 的体验环境,不妨体验一下。
更多 WebVR 内容等着你们发现
许多人将 2016 年称为 VR 元年。VR 的前景有人看好,也有人看衰。但无论如何,新技术的出现都值得我们去体验一番。当然,作为一名开发者,也可以从代码的角度体验一番。
想详细了解更多关于 VR 的行业报告,可以阅读 《VR与AR:解读下一个通用计算平台》。
3D 全景并不是什么新鲜事物了,但以前我们在 Web 上看到的 3D 全景一般是通过 Flash 实现的。若我们能将 CSS3 Transform
的相关知识运用得当,也是能实现类似的效果。换句话说,3D 全景其实就是 CSS3 3D 的应用场景之一。
在实现 CSS3 3D 全景之前,我们先理清部分 CSS3 Transform 属性:
z=0
平面的距离,使具有三维位置变换的元素产生透视效果。(默认值:none,值只能是绝对长度,即负数是非法值);下面对上述的一些点进行更深入的分析:
对于 perspective
,该属性指定了“眼睛”与元素的 perspective-origin
(默认值是 50% 50%
)点的距离。那么问题来了:“当我们应用 px
作为衡量单位时,那它的实际距离该如何量化呢(即如何得到我们熟悉且易于表达的距离)?”
答:当我们的屏幕的分辨率是 1080P(1920*1080px),且该元素或祖先元素的 perspective 值是 1920px
时,该应用了 CSS3 3D Transform 的元素的立体效果就相当于我们在距离一个屏幕宽度(1920px)的屏幕前观看该元素的真实效果。尽管如此,目前我也不清楚如何准确地为元素设置一个合适的 perspective
值,只能猜测个大概值,然后再动态修改值,以达到满意的呈现效果。
根据 相似三角形 的性质可计算出被前移的元素最终在屏幕上显示的实际大小
另外,关于 perspective
还有另外一个重要的点。因为,perspective-origin 的默认值是 50% 50%
,因此,对哪个元素应用 perspective
属性,就决定了“眼睛”的位置(即我们的“眼睛”是在哪个角度看物体)。一般来说,当我们需要正视物体时,就会将该属性设置在与该元素中心重合的某一祖先元素上。
再另外,如果说:“如何让一个元素(的背面)不可见?”,你可能会说 backface-visibility:hidden;
。其实,对于在“眼睛”背后的元素(以元素的 transform-origin
为参考点),即元素的z轴坐标值大于 perspective 的值,浏览器是不会将其渲染出来的。
对于 transform-style
,该属性指定了其子元素是处于 3D 场景还是 2D 场景。对于 2D 场景,元素的前后位置是按照平时的渲染方式(即若在普通文档流中,是按照代码中元素的先后顺序,后面的元素会遮住在其前面的元素);对于 3D 场景,元素的前后位置则按照真实世界的规则排序(即越靠近“眼睛”的,会遮住离“眼睛”更远的元素)。
另外,由于 transform-style
属性是非继承的,对于中间节点需要显式设定。
对于 transform
属性:下图整理了 rotate3d、translate3d 的变换方向:
transform 中的变换属性的顺序是有关系的,如 translateX(10px) rotate(30deg) 与 rotate(30deg) translateX(10px) 是不等价的。
另外,需要注意的是 scale 中如果有负数值,则该方向会产生 180 度的翻转;
再另外,部分 transform 效果会导致元素(字体)模糊,如 translate 的数值存在小数、通过 translateZ 或 scale 放大元素等等。每个浏览器都有其不同的表现。
上面理清了一些 CSS Transform 的知识点,下面就讲讲如何实现 CSS 3D 全景 :
想象一下,当我们站在十字路口中间,身体不动,头部旋转 360°,这个过程中所看到的画面就形成了一幅以你为中心的全景图了。其实,当焦距不变时,我们就等同于站在一个圆柱体的中心。
但是,虚拟世界与现实的最大不同是:没有东西是连续的,即所有东西都是离散的。例如,你无法在屏幕上显示一个完美的圆。你只能以一个正多边形表示圆:边越多,圆就越“完美”。
同理,在三维空间,每个 3D 模型都等同于一个多面体(即 3D 模型只能由不弯曲的平面组成)。当我们讨论一个本身就是多面体(如立方体)的模型时并不足以为奇,但当我们想展示其它模型时,如球体时,就需要记住这个原理了。
淘宝造物节的活动页 就是 CSS 3D 全景的一个很赞的页面,它将全景图分隔成 20 等份,相邻的元素间差 18°(360/20)。需要注意的是:我们要确保每个元素的正面是指向圆柱中心的,所以要计算好每等份的旋转角度值。然后再将元素向外(即 Z 轴方向)平移 r
px。对于立方体 r
就是 边长/2
,而对于其它更复杂的正多面体呢?
举例:对于正九面体,每个元素的宽为 210px
,对应的角度为 40°
,即如下图:
图片来自:https://desandro.github.io/3dtransforms/docs/carousel.html
正九面体的俯视图
计算过程
由此得到一个公用函数,只需传入含有元素的宽度和元素数量的对象,即可得到 r
值:
俯视时所看到的元素外移动画
另外,为了让下文易于理解,我们约定 html 结构是:
正多面体构建完成后,就需要将我们的“眼睛”放置在正多面体内。由于在“眼睛”后的元素是不会被浏览器所渲染的(与 .div元素
是否设置 backface-visibility:hidden;
无关),且我们保证了 .div元素
的正面都指向了正多面体的中心,这就形成 360° 被环绕的效果了。
那“眼睛”具体是被放置在哪个位置呢?
答:通过设置 #stage
元素的 translateZ 的值,让不能被看到的 .div元素
的 Z 轴坐标值大于 perspective 的值即可。如:立方体的正面的 translateZ 是 -300px(为了保证立方体的正面是指向立方体中心,正面元素需要设置 rotateY(-180deg) translateZ(-300px)
,即正面元素向“眼球”方向平移了 300px),而 #view 的 perspective 的值为 1000px,那么 #stage 的 translateZ 的值应该大于 700px 且小于 1300px,具体数值则取决于你想要呈现的效果了。
根据上述知识,我粗略地模仿了“造物节”的效果:http://jdc.jd.com/lab/zaowu/index_new.html
其实,只需 6 幅图就可以实现一张常见的无死角全景图。
我自己又试验了下:http://jdc.jd.com/lab/zaowu/index2.html
可由下图看出,将水平的 4 张图片合成后就是一张全景图:
理解上述知识后,就可以通过为元素设置合适的 CSS3 Transform 属性值,即可实现一张可交互的全景图了。当然,交互的效果可以是拖拽,也可以是重力感应等。
正如在上文提到的:“没有东西是连续的,即所有东西都是离散的…”。将《造物节》与后续全景图的水平方向上的图片分别合成一张图后,可以发现:图片数量越多,图片的要求也越低。你觉得呢?
造物节全景图
将全景图制作分为设计类与实景类:
要制作类似 《淘宝造物节》 的全景页面,设计稿需要有以下这些要求。
注:下面提及的具体数据均基于《造物节》,可根据自身要求进行调整(若发现欠缺,欢迎作出补充)。
整体背景设计图如下(2580*1170px,被分成 20 等份):
基本要求:
当然,上图只是背景图,还可以添加一些小物体素材(通过运动速度的差异形成视差,增强立体效果),如:
小物体元素(虚线是用于参考的,造物节中共有 21 个小物体)
如上图所示,每个图片也是被等分成 M 等份。当然,M 取决于物体在背景上的具体位置和本身大小。
另外,M 的宽度是与 N 的宽度相等的。尽管部分物体(M>1)的两侧等份的图案占比小,但建议保留同样的宽度。
注:如果小物体有特殊的变形效果,应该备注具体变形参数。
对于顶部和底图图片,则无特殊要求。
如果想制作实景的全景,可以看看 Google 街景:
Google 街景 推荐的设备如下:
如上图,最实惠的就是最后一个选项—— Google 街景 APP,该应用内部提供了全景相机功能,但正如图片介绍所说,这是需要练习的,因此对操作要求比较高。
补充:
上周六(2016.8.20)参加了 TGDC 的分享会,嘉宾分享了他们处理全景的方式:
其中 Web 技术有以下3种可选方式(当然,还有其它):
当时,嘉宾现场快速制作的 会议现场全景。
可见,优秀硬件设备的出现,大大减少了后期处理的时间,而 Web 则提供了一个很好的展现平台。
随着终端设备的软硬件不断完善和提高,Web 在 3D 领域也不甘落后,如果你玩腻了 2D 的 H5 或者想为用户提供更加新颖优秀的体验,全景也许是一种选择。
最后,如有不清晰或不明白的地方,可以联系我,我会尽可能解决的。谢谢谢~
]]>Chrome 浏览器地址栏标志着 HTTPS 的绿色小锁头从心理层面上可以给用户专业安全的心理暗示,本文简单总结一下如何在 Nginx 配置 HTTPS 服务器,让自己站点上『绿锁』。
Nginx 配置 HTTPS 并不复杂,主要有两个步骤:签署第三方可信任的 SSL 证书 和 配置 HTTPS
有关 SSL 的介绍可以参阅维基百科的传输层安全协议和阮一峰先生的 《SSL/TLS协议运行机制的概述》。
SSL 证书主要有两个功能:加密和身份证明,通常需要购买,也有免费的,通过第三方 SSL 证书机构颁发,常见可靠的第三方 SSL 证书颁发机构有下面几个:
StartCom 机构上的 SSL 证书有以下几种:
其中 EV、OV、IV 需要付费
免费的证书安全认证级别一般比较低,不显示单位名称,不能证明网站的真实身份,仅起到加密传输信息的作用,适合个人网站或非电商网站。由于此类只验证域名所有权的低端 SSL 证书已经被国外各种欺诈网站滥用,因此强烈推荐部署验证单位信息并显示单位名称的 OV SSL 证书或申请最高信任级别的、显示绿色地址栏、直接在地址栏显示单位名称的 EV SSL 证书,就好像 StarCom 的地址栏一样:
更多关于购买 SSL 证书的介绍:SSL 证书服务,大家用哪家的?、DV免费SSL证书
配置 HTTPS 要用到私钥 example.key 文件和 example.crt 证书文件,申请证书文件的时候要用到 example.csr 文件,OpenSSL
命令可以生成 example.key 文件和 example.csr 证书文件。
使用 OpenSSl
命令可以在系统当前目录生成 example.key 和 example.csr 文件:
|
|
下面是上述命令相关字段含义:
生成 csr 文件后,提供给 CA 机构,签署成功后,就会得到一個 example.crt 证书文件,SSL 证书文件获得后,就可以在 Nginx 配置文件里配置 HTTPS 了。
要开启 HTTPS 服务,在配置文件信息块(server block),必须使用监听命令 listen
的 ssl 参数和定义服务器证书文件和私钥文件,如下所示:
|
|
证书文件会作为公用实体發送到每台连接到服务器的客戶端,私钥文件作为安全实体,应该被存放在具有一定权限限制的目录文件,并保证 Nginx 主进程有存取权限。
私钥文件也有可能会和证书文件同放在一個文件中,如下面情況:
|
|
这种情況下,证书文件的的读取权限也应该加以限制,仅管证书和私钥存放在同一个文件里,但是只有证书会被发送到客戶端
命令 ssl_protocols
和 ssl_ciphers
可以用来限制连接只包含 SSL/TLS 的加強版本和算法,默认值如下:
|
|
由于这两个命令的默认值已经好几次发生了改变,因此不建议显性定义,除非有需要额外定义的值,如定义 D-H 算法:
|
|
SSL 的运行计算需要消耗额外的 CPU 资源,一般多核处理器系统会运行多个工作进程(worker processes ),进程的数量不会少于可用的 CPU 核数。SSL 通讯过程中『握手』阶段的运算最占用 CPU 资源,有两个方法可以减少每台客户端的运算量:
这些会话会存储在一个 SSL 会话缓存里面,通过命令 ssl_session_cache 配置,可以使缓存在机器间共享,然后利用客戶端在『握手』阶段使用的 seesion id
去查询服务端的 session cathe(如果服务端设置有的话),简化『握手』阶段。
1M 的会话缓存大概包含 4000 個会话,默认的缓存超时时间为 5 分钟,可以通过使用 ssl_session_timeout 命令设置缓存超时时间。下面是一個拥有 10M 共享会话缓存的多核系统优化配置例子:
|
|
HSTS – HTTP Strict Transport Security,HTTP严格传输安全。它允许一个 HTTPS 网站要求浏览器总是通过 HTTPS 来访问,这使得攻击者在用戶与服务器通讯过程中拦截、篡改信息以及冒充身份变得更为困难。
只要在 Nginx 配置文件加上以下头信息就可以了:
|
|
当用户进行 HTTPS 连接的时候,服务器会发送一个 Strict-Transport-Security 响应头:
浏览器在获取该响应头后,在 max-age
的时间内,如果遇到 HTTP 连接,就会通过 307 跳转強制使用 HTTPS 进行连接,并忽略其它的跳转设置(如 301 重定向跳转):
307 跳转 Non-Authoritative-Reason 响应头
由于 HSTS 需要用戶经过一次安全的 HTTPS 连接后才会在 max-age 的时间內生效,因此HSTS 策略并不能完美防止 HTTP 会话劫持(HTTP session hijacking),在下面这些情況下还是存在被劫持的可能:
针对这种情況,Google 维护了一份『HSTS 预加载列表』,列表里包含了使用了 HSTS 的站点主域名和子域名,可以通过以下页面申请加入:
https://hstspreload.appspot.com/.
申请的时候会先验证站点是否符合资格,一般会检验待验证的站点主域和子域是否能通过 HTTPS 连接、HTTPS 和 HTTP 配置是否有 STS Header 等信息,通过验证后,会让你确认一些限制信息,如下图:
当确认提交后,就会显示处理状态:
申请通过后,列表内的站点名会被写进主流的浏览器,当浏览器更新版本后,只要打开列表内的站点,浏览器会拒绝所有 HTTP 连接而自动使用 HTTPS,即使关闭了 HSTS 设置。
可以在下面两个连接分別查找 Chrome 和 Firfox 的『HSTS 预加载列表』内容:
The Chromium Projects - HTTP Strict Transport Security
Firefox HSTS preload list - nsSTSPreloadList.inc
需要注意的是:
因此,如果自己站点子域名变化比较多,又沒有泛域证书,又沒法确定全站是否能应用 HTTPS 的朋友,就要谨慎申请了。
更多关于 HSTS 配置可参考:
《HTTP Strict Transport Security (HSTS) and NGINX》
MDN的《HTTP Strict Transport Security》
HTTPS 基础配置采取的默认加密算法是 SHA-1,这个算法非常脆弱,安全性在逐年降低,在 2014 年的时候, Google 官方博客就宣布在 Chrome 浏览器中逐渐降低 SHA-1 证书的安全指示,会从 2015 年起使用 SHA-2 签名的证书,可参阅 Rabbit_Run 在 2014 年发表的文章:《为什么Google急着杀死加密算法SHA-1》
为此,主流的 HTTPS 配置方案应该避免 SHA-1,可以使用 迪菲-赫尔曼密钥交换(D-H,Diffie–Hellman key exchange)方案。
首先在目录 /etc/ssl/certs
运行以下代码生成 dhparam.pem
文件:
|
|
然后加入 Nginx 配置:
|
|
如果服务器夠強大,可以使用更为复杂的 4096 位进行加密。
一般情況下还应该加上以下几个增强安全性的命令:
|
|
这几个安全命令在 Jerry Qu 大神的文章《一些安全相关的HTTP响应头》有详细的介紹。
|
|
可以同时配置 HTTP 和 HTTPS 服务器:
|
|
在 0.7.14 版本之前,在独立的 server 端口中是不能选择性开启 SSL 的。如上面的例子,SSL 只能通过使用 ssl 命令为单个 server 端口开启
|
|
因此没有辦法设置 HTTP/HTTPS 混合服务器。于是 Nginx 新增了监听命令 listen参数 ssl
來解决这个问题,Nginx 現代版本的ssl
命令并不推荐使用
一个常见的问题就是当使用同一个 IP 地址去配置两个或更多的 HTTPS 服务器的时候,出现证书不匹配的情況:
|
|
这种情况下浏览器会获取默认的服务器证书(如上面例子的 www.example.com.crt)而忽视请求的服务器名,如输入网址:www.example.org
,服务器会发送 www.example.com.crt
的证书到客戶端,而不是 www.exaple.org.crt
。
这是因为 SSL 协议行为所致,SSL 连接在浏览器发送 HTTP 请求之前就被建立,Nginx 并不知道被请求的服务器名字,因此 Nginx 只会提供默认的服务器证书。
解決这个问题最原始最有效的方法就是为每个 HTTPS 服务器分配独立的 IP 地址:
|
|
除此之外,官方还介绍了两个方法:泛域证书和域名指示(SNI)
其实 OpenSSL 在 0.9.8f版本就支持 SNI 了,只要在安裝的时候加上 --enable-tlsext
选项就可以。到了 0.9.8j版本,这个选项在安裝的时候会默认启用。如果创建 Nginx 的时候支持 SNI,可以在 Nginx 版本信息查到以下的字段:
|
|
因此,如果较新版本的 Nginx 使用默认的 OpenSSL 库,是不存在使用 HTTPS 同时支持基于名字的虚拟主机的时候同 IP 不同域名证书不匹配的问题。
注意:即使新版本的 Nginx 在创建时支持了 SNI,如果 Nginx 动态加载不支持 SNI 的 OpenSSL 库的话,SNI 扩展将不可用
有兴趣的朋友可以看下:
An SSL certificate with several names && Server Name Indication
OK,我们简单总结一下在 Nginx 下配置 HTTPS 的关键要点:
获得 SSL 证书
通过 listen 命令 SSL 参数以及引用 example.key 和 example.crt 文件完成 HTTPS 基础配置
HTTPS优化
HTTP/HTTPS混合服务器配置
基于服务器名称(name-based)的 HTTPS 服务器
其实简单的个人博客,如果没有敏感数据交互的话,使用 http 协议通讯,一般都夠用了,页面速度还会更快,但正如文章开头所说,戴上『绿锁』,更专业更安全~~有兴趣的同学可以去深入了解折腾下:)
]]>1.新建一个项目,打开cmd命令,执行npm init,创建package.json
2.在根目录下创建一个不带后缀的系统文件,作为主入口文件
3.安装本文所涉及到的模块commander、inquirer、chalk,在根目录下执行 npm install commander inquirer chalk –save-dev,这时候会看到根目录下多了一个node_modules目录,里面有刚刚安装的几个模块,package.json里面devDependencies依赖了这几个模块,如下图
根目录
package.json
我们先来认识一下commander吧
呃~~官方时刻到了哈:commander灵感来自 Ruby,它提供了用户命令行输入和参数解析的强大功能,可以帮助我们简化命令行开发。
根据其官方的描述,具有以下特性:
下面我们通过一个简单的实例来了解一下它的基本语法吧
执行一下看看效果吧!$ node app.js app (请各位看官自行体会这种执行方式哈)
//输出结果 Hello World
我们可以通过一些配置,然后以 模块名 + command的方式运行,实现这种方式分三步走:
配置package.json的bin字段。bin字段有啥用呢?它可以用来存放一个可执行的文件,如下配置所示
|
|
执行npm link。它将会把app这个字段复制到npm的全局模块安装文件夹node_modules内,并创建符号链接(symbolic link,软链接),也就是将 app 的路径加入环境变量 PATH
|
|
做好了以上三步后,然后运行$ app module
//输出结果 Hello World
我们逐个来看看各个属性的功能,一看秒懂哦
执行 $ app m –help
它会自动将description、option的信息显示在help中
我们也可以通过自定义的方式生成帮助信息
执行$ app m –help
在开发的过程中,我们需要频繁的跟命令行进行交互,借助inquirer这个模块就能轻松实现,它提供了用户界面和查询会话流程。它的语法是这样的(直接从官方拷贝~~)
|
|
需注意在旧的语法中,采用的是传统的function回调
这个模块相当简单,看个栗子就全明白了
|
|
执行命令 $ app m
以上为了代码组织方便使用了一个promps数组来接收参数以及借助了lodash模块的assign方法用来合并对象,lodash不属于本章的知识点哈,这里给大家提供一个中文API文档仅供大家学习参考
最后我们引入chalk这个美化命令行的模块,它具有轻量级、高性能、学习成本低等特点。继续在以上栗子中引入chalk进行输出
|
|
执行命令 $ app m
正如你所看到的输出结果,本篇文章收工咯,是不是so easy!
曾经在很长一段时间里,我一直不知道他们口中的老司机究竟是个什么梗。后来随着时间的拉长,以及在现实生活中对这个词所出现语境的理解,我的潜意识一度将它理解成了一个的词汇(咦,这里咋显示不出来呢)…后来才知道真正的老司机指的是在各个网站、论坛里接触时间比较长,熟悉站内各种规则、内容以及技术、玩法,并且掌握着一定资源的老手,亦指在某些方面熟门熟路,资历较老,见识广,经验足的人…
凹凸实验室前端流程工具: https://github.com/o2team/athena
博文: http://www.tuicool.com/articles/ZFNZjq
commander: https://www.npmjs.com/package/commander
inquirer: https://www.npmjs.com/package/inquirer
chalk: https://www.npmjs.com/package/chalk
示例源码: https://github.com/yangzicheng/command-line
本文译自 Jon Gold 的 Taking The Robots To Design School,略有删减。
大概在一年前,我开始尝试把「字体设计」和「人工智能」这两个东西结合起来。
现在是 2016 年了,我打算把脑子里的东西搬到网上来。部分原因是它实在是有趣,其次是我想作为日志,便于以后翻看。但最重要的是,我想更多人能读到这些东西。
自打我做设计师以来,我就被设计系统中数学的纯洁性所吸引。我一直很欣赏瑞士的设计。我一生中最爱的设计师或许就是 Karl Gerstner,他著有作品 programmatic design。2011 年我在 Prismatic 做系统化的网页布局算法。然后 2014 年受到了 Flipboard’s 布局算法 的启发,我开始把注意力转移到抽象化上,开始做一些 元-元-元-设计系统和工具,让人类设计师思维更开阔。
就这么到了 2015 春,我开始在 The Grid 工作——一个利用人工智能来建站的工具。介绍一下,这个工具某些地方跟 Squarespace 或者 Wix 挺像的,都可以让非设计师快速搭建起一个好看的网站。但不同的地方在于,它没有主题或者模板,而是根据内容,用精准的设计算法去设计网站,再呈现给用户。酷。这个是探索设计系统的好地方,我受命去把这个版式设计系统变得更好。
那是个开放性的理念,它让我变得对自己生涯有了更多的思考和有趣的探索。据我所知,我之前所做的版式设计系统的工作仍然在保密协议之内,但它实质上是针对人类的。对此我定义了自己的理念,我希望这个系统是可以认识、选择和根据人类设计专家的细微区别去应用排版。
有时当我说「AI 文字设计」时候,别人以为我在设计新的自生成字体。那确实是很有意思的领域,但那个臆断是因为用词不当。澄清一下,在开始之前和你对这篇文章失望之前:
版式设计(Typography):字体的使用
字体设计(Type design):新字体的创造
谢谢 Stephen Coles 对此简洁的定义。如果你对用神经网络设计字体感兴趣的话,不妨看下 Erik Bern 大神的 Deep Fonts 项目,大概对 50000 个字体进行视觉分析。所以说,「AI 文字设计」——让计算机做字体设计。
设计一个能生成复杂专业的中心法则的人工智能系统,确实是有点挑战。这需要我在工作中,从一些基本法则里学习人工智能和相关的所有学科(我并没有计算机科学的背景,我是项目中唯一一个设计工程师)。记得那个时候,我每晚都会远离屏幕去 Pacific Heights 附近散步,看着日落,仔细琢磨那个项目的艰巨性。有一次走着走着,Alan Turing(译者注:艾伦·图灵,计算机之父)的一句话跳进了我的脑子里。
与其说试着去写程序去模拟成年人的心智,为什么不试着去写个可以模拟小孩的呢?如果它受到适当的教育,它应该会获得一个成年人的大脑。
— Alan Turing, Computing Machinery and Intelligence, 1950
与其说把设计当成一个整体去看,如果我把设计看成一个个基于人类设计师教育的模型呢?
我相信在这 65 年期间,那个方法论被 AI 纯粹者给推翻了。但作为混沌的开端,它看起来还是合理的。
对于设计这个东西,你不用非要做设计师才能够触碰到它。在西方社会里,我们有数百年共通的视觉文化——我们先天就知道事物是怎么传达的,即便我们还不能去标记它。
通过文字(和 Taylor Swift)来传达完全不同的情感
平面设计的目的是去唤醒目标群体的情感——可以是设计师,但更多的是「普通群众」。就是那些喜欢聊颜色、图形的黑魔法和对字距泛泛而谈的外行人。但这不妨碍设计把信息传达给他们。如果一个普通人不能从设计中得到一些信号暗示的话,设计师就没有存在的必要了。非设计师不知不觉中,就能感受到设计带来的触动。
它们分别传达不同的含义——如果没有,就没设计师这个专业
但另一个角度来说,计算机,它们丝毫不懂得设计,一点点都不。对于计算机来说,字体就是一些 .otf
文件和还有字符串形式的字体名称。对于计算机来说,颜色就是一些十六进制、RGB 和 HSL 值。默认来说,计算机甚至不是一个非设计师人类的对手。
我们用的这些工具它们根本不知道自己长什么样子
对于计算机来说,它们只知道一些很卑微的字符串——最卑微的数据结构。但人类却有着非常强大的传达情感的方式。只是人类不能仅仅靠字体名称来做设计而已。因为我们可以看到它们和了解它们,所以计算机为什么不行呢?把我们的算法带到设计学校前,首先需要纠正这点。
设计学校提供了一个从情感到理性的映射——我们学到为什么事物在我们脑子里生成情绪反应,还有怎么利用那些思想去在别的事物上重现情感。为此我们学到了一些术语;学习了怎么辩别 歌德体 (Grotesque)
、人文字体 (Humanist)
、迪东体 (Didone)
和 过渡字体 (Transitional)
、德文尖角体 (Fraktur)
还有 圆体 (Rotunda)
。我们学会了怎么用一些元属性辩别一个字体:x字高
、笔画粗细对比
、宽度
、超过x高度部分 (ascenders)
、字怀 (counters)
,而不仅仅是字体名称。
抽象化的目的不是要得更含糊,而是利用它去创建一个能绝对描述准确的语义层。
- Edsger Dijkstra, The Humble Programmer, 1972
这些新的、精确的术语让我们在工作中有规律可依。我们可以用像 x字高
、笔画粗细对比
这些来标记字体。我们用这些特点来辩别字体,而不是简单的说,「哦我看它长得像这种字体」。
字形的每一个部分,都有一个能准确描述的术语——插图来自 Ellen Lupton 的《Thinking With Type》
我们要做的事情就是,让计算机用同样的概念去标记和选择字体。
乍一看,我们可以用手动去标记字体。但这带来两个问题:
上面第一点足以说服我永远不去碰需要人工干预的事儿了。那怎么才能用算法去区分字体呢?
我第一个反应就是观察下这些字体。有时可能会涉及一些计算机视觉和分类学。我想自己是否足够聪明去以截取部分的方式构建一些字符,然后弄明白它们长什么样儿。但我发现自己掉到一个全是像素的无底洞,一直在绘制字体和测量「模糊抗锯齿彩色印片法」输出的结果。
最开始有这个想法可以理解,但它效率并不是最高的。为了可以更快和重复地做上面这些操作,我用了 PhantomJS 和 HTML5 canvas 来做。但在 canvas 里画网页字体真的很蛋疼,而在 PhantomJS 里做些视觉复杂的事情也很麻烦。我花了 9 个月时间去尝试,期间经历过很多挫折;但我提起这件事的原因,是因为它让我发现……
面对柏林残暴的高温天气和无数次对 PhantomJS 和位图的吐槽之后,我决定重新来过。之前我之所以对一个个像素点分析,是因为我把问题归结到「视觉」上,而计算机视觉就是以像素为主。但其实应该还有其他办法!
因为字体是矢量的,这让一切计算都变得非常简单。它就是几何学而已。所以我开始写一个小小的库去解析字体文件,把它们转化成 SVG,但我很快又发现了一个更好的东西。opentype.js 是一个 OpenType 和 TrueType 文件的解析器,它让你在 Node.js 和浏览器环境中可以访问到矢量的字形,还包括所有的内嵌 opentype 数据。很好很强大!
有了这种靠谱的方式后,我开始写一些算法去标记那些功能了。看看这些例子:
「x字高」指的是小写字母 x 的高度,这很明显。为了让所有字体都能得到一个可规范化的数值(字体可能被绘制成不同的大小),我们用大写字母 A 作为对比,记录比例值。这个数值大概在 ~0.45 - 0.8 之间,听上去蛮合理的。
|
|
有些字符真的很能展示出字体的笔画粗细对比。特别是重叠 O
、o
、A
字符让我很清晰了解到字体是怎么分化的。这个对比可以用以下算法来表示:
|
|
找一些最容易感受到字体宽度变化的字符,M
和 N
相对于其它而言,就是个很好的选择。但我们不能只是量它们的宽度(因为字体可以是不同尺寸),所以我就取了它们高宽比例的平均数。哈哈。
|
|
然后发现一个有意思的事儿。我当时在给我的设计项目中找一些相似的字体。有想到 Gotham
, Proxima Nova
和 Avenir
;Tiempos Headline
和 Leitura Display
;San Francisco
, Aktiv Grotesk
和 Helvetica
; Aperçu
, Patron
和 Maison Neue
这些组合。
按照经验来说,搭配字体时都会找一些有笔画粗细差异的字体。像 Gotham
和 Proxima Nova
搭配比较少见,但如果 Gotham
搭配一个衬线字体就会有意思得多。
这是三种人类设计师觉得相似的字体。但计算机怎么看呢?
可是有些时候你想要找特定属性相近的字体。比如说,你可能会想要搭配一些x字高差不多的、笔画粗细接近的字体。
所以我弄了一个「相似度评分」,用来展示任意字体属性组合的欧氏距离。默认它表示每个字体属性之前的距离,但它也可以用来表示字体的一些差异,像 x字高
和 笔画粗细对比
。
|
|
在写了这么多算法之后,我在一些测试数据上跑了一下,把数据比例缩小到 0 和 1 之间,将结果绘制成了表格。
把 8 个字体按照与「Proxima Nova」的相似度排序
这个结果很理想!根据相似度排序之后,发现 Proxima Nova
和 Gotham
确实是最像的,而和 Didot
差异最大。这符合我的预期,但能用代码表示出来这很棒。
但当然了,只有 9 个字体不足以让人兴奋,所以我很残暴地把 Google Fonts 上的所有字体上都跑了一遍。
对比超过 800 个字体
这些结果再次说明了我的代码是对的。看字体的预览效果和它们的得分,我们可知这些数值是匹配的——赞!
但 <table>
并不是一个字体系统,所以我继续研究我的设计学校初级理论:规则。
设计是一连串的规则,不管你意识到这点没有。有一些很简单明了的规则:一行文字应该在 66 个字宽左右,文字颜色应该有一定对比度便于阅读。文章段落不该出现寡行或者川流(译者注:rivers, 即纵向裂缝);字体缩放比例的不同削弱了字体间的反差。这些都是显而易见的规则,我们可以很容易在书上找到。
但也有一些隐含的规则。这些隐含的规则是我们设计师经验的总结。我们接手的每个项目、每件事都可能做得更好或者更差;但每个奇淫技巧我们都记得。虽然没有万能的解决方案,但我们仍可以挽起袖子去不断探索。
即使是那些嘲笑现代主义设计的人,他们所在的职业也会有一些人不合常规。David Carson 有着他自己的一套,Neville Brody 也是。他们也许不是十分正统,也不会被写进书里,但我们每个人自己身上都有一套专属的规则。框架被用来处理设计,它有着一系列可以把设计需求变成可交付物的方法。这或许不是表面可见的东西,但我们可以去发掘一下。
在基于规则的系统前提之下,我提供了一些面向设计师的接口,可以用来创建规则。我受到了像 core.logic 和 miniKanren 这些逻辑编程程序库的影响后,就开始想让一个系统变得声明式(Declarative)
和组合式(Compositional)
。
如果听上去很抽象,那么给你看看这些我们都用的基于条件的程序:
这是两个常见的组合式、基于条件的查找器。酷。
所以这很酷——我们可以指定一些元规则像「给我找到有这几种属性的字体」,而不是说「给我找到这个字体、那个字体」,以更高的层面去理解字体。「根据这种新闻标题字体,给我根据这些属性搭配文章正文」、「如果像这种字体展现在页面上,就不要搭配像那种字体」。我想实际实现的规则不会有理论上那么有趣(因为规则可以被调整);但大意就是以更高的层面去探讨字体,而不是只看字体的名字。
*那个字体*
|
|
上面这些东西的目的就是暴露一个接口给设计师,去创建和调整被添加到系统的规则。但很快我意识到自己把这个模型很重要的一部分给忘了…
创建这些规则变得越来越手动化,所以我想自己对这个行业的理解,寻找一个解决方案,理论和实践相结合。这就是我的模型的第三部分:宏观观察。
作为一个学生,你会有一些行业领袖作为你的偶像,史书上的或者当代的。逐渐增长的设计知识让你能够把他们的创作和自己的关联起来;从你的事业轨迹中探寻到他们的作品。
在我学生时期,Experimental Jetset
,Spin
和 Non-Format
这些是我一些最爱的工作室。在伦敦学校无数个下着毛毛细雨的冬天夜里,我凝视它们的作品,试着去了解他们怎么修炼自己的个人美学。认出他们所用的字体,然后研究为什么这些字体就可以。看他们所用的颜色,然后去搞清楚它们是从哪个调色板中得到的。我从来没有想过在真实项目中复制他们的作品,但像那种形式的观察在学习过程中肯定是有意义的。
两个我最爱的网站(译者注:分别是 Font In Use 和 Typewolf)
我把这种「在现实中观察设计师们用什么、做什么」的形式给模型化了。有一些我很喜欢的网站,它们收集了其它一些排版能给人灵感的网站,根据字体分类,构建出一个庞大的关系型数据库。这可以是一个体力活,但如果让爬虫把这些数据全部自动化抓给我就更好了。无论如何,几行代码加上大量的 HTTP 请求,最终我就可以拿到真实世界字体使用情况的庞大数据集。
把它们可视化最自然的方式就是用节点图吧,通过线连接相互被使用的字体。虽然这样看上去很晕,但是数据看上去很丰富,很吸引人。
但这个图表有一个和字体簿(译者注:FontBook,MacOS 的字体管理器)一样的问题。虽然这些关系很有趣,但它们仍然通过字体名称进行关联。如果你读过之前的章节,疑惑机器学习会进入到哪一个人类衍生出来的相对狭隘的系统的话,那么这就是了。通过创建一个能分析任意字体和基于几何特性绘制出图表的系统,我们就可以把关系从字体名称转移到字体富属性上了。
我们可以通过查询来找出一些匹配的字体,但也可以遍历图表去找到许多相似的有意思的相关字体。我们可以看到一些字体组变得越来越流行,但可以看出是因为它们有着一些特定的字体属性。这个图表告诉了我们哪些字体是流行的,但我们的几何系统告诉了我们为什么流行。
通过看一些设计博客,你很容易了解到 Aperçu
, Maison Neue
, GT Walsheim
和 LL Circular
这些字体很流行。但对我而言,通过一个系统去找出字体搭配就更有意思了。这个系统可以知道为什么一些字体会流行起来,通过他对排版的知识也可以去预估未来设计的趋势。它或许可以提出一些人类想不到的,但看上去貌似合理的字体组合。
如果把这个行业当成一个整体,以一个高的层面去构建我们对潮流趋势的理解,那么去深入了解其它设计师也是很重要的。了解他们怎么做的,观察他们做出选择的过程。他们尝试过的方法,他们放弃的、保留着的东西。不断询问,留心他们解决问题的技巧。
在我们学生阶段、实习阶段、甚至是刚当设计师那会儿,就学会了怎么去做上面这些事情。最开始我们经常和一些年长的设计师打交道。我们从一些同行和大师中,学到了很多东西。
英国瑞文斯博学院。设计师在忙活。
作为人类,我们会以个体、小组和更大的社区形式,去为其他设计师创造一些模范。比如这些在工作室氛围中形成的文化基因,和一些设计师群体聚在一起,对外分享的一些思想火花。所有伦敦的设计学校,基本上就是「瑞文斯博风格」的学校和金斯顿大学、切尔西大学、伦敦传媒学院和中央圣马丁艺术与设计学院这些学校的较量。但也有「伦敦风」、英伦风和欧洲风这些不同风格群体。像更远的,「耶鲁风」一下子被认出来了。学校之外也有一些有着同样想法,但不同物理空间的派系——在 Dribbble 和 Behance 上设计师和插画家之间明显的潮流趋势,或者其他我们互相欣赏对方作品的地方。
了解其他设计师所掌握的东西,最好的方式就是坐下来和他们闲聊,或者窥看他们的画板和他们制作的方式。关于这个最酷的地方就是,这也是可以通过算法学习到的。把「站在其他设计师肩膀上」这个动作给数字化。
我有一些工具可以去探索这个想法:去合法观察其他设计师如何做设计。最酷的地方在于,我们的算法越聪明,我们就越能从匿名数据中抽取出所需要的含上下文的东西。这就需要去丰富和改变训练数据,让我们在设计师中找到潮流趋势。在大型的生产企业和工作室,会用一些内部设计工具,或许我们能构建出这些使用者的特征模型。更多关于设计工具融合化的事儿,以后再说。
我今年初(2016)离开了 The Grid 团队。这几个月由于公司需求太忙,这个版式设计项目一直没继续,我一直没能把它应用到生产环境上,现在想想还是很悲伤。#创业生活
当然事后很容易知道什么事情可以做得更好,这也督促着我的工作向前推进。The Grid 目的是想打造一个设计系统,把所有详细信息都抽象出来给终端用户。但在我把所有智慧装进这个版式设计系统之后,我被告知只能用一个 <input type="range" />
滑动条将其展示出来。
问这个系统怎么用的设计师朋友肯定会对此很失望。除去我在那里为设计行业未来人工智能所做的贡献之外,我更想让这些智能算法去更好帮助设计师,而不是说取代他们。
一个研究团体预计 1980 年前,人工智能的发展会让机器的独立思考或者解决军事问题成为可能。那就是说,大概还有 5 年时间去发展人机共生关系和 15 年的时间去使用它。这个 15 也可以是 10 年或者 500 年,但那应该人类历史上最激动人心和智慧最闪耀的时刻。
— J. C. R. Licklider, Man-Computer Symbiosis, 1960
在翘首完未来之后,再看现在所用的设计工具,无疑是让人失望的。我们的软件缺乏了上下文理解和行业意识。这些工具操作的是文本、矢量和布尔值;不是设计。但工具是最容易发生改变的东西,所以我在创造一些设计工具,它整合了跟设计流程相关的聪明算法,可以让设计师通过学习他们在做什么,我们在做什么,从而变得更好。增强他们而不是替代他们。
对于一些特定的设计子集,想法趋同很重要
设计师最不愿意去选择用什么工具了。对于算法设计来说,优化组合是一个有趣的机会。个人来说我并没有想把互联网变得更同质化,但设计确实是有一些共通的法则。这不需要变得很趋同,有很多方式可以去公开和使用这些数据。我们的工具应该让彼此更接近,或者远离趋势。当然我们现在去提议一个更受欢迎的字体,但我们找到那些主流分支相似的但未被发现的宝藏。我们可以提议、暗示和修正;这些都建立在扎实的设计原则和我们作为设计师的经验总结之上。这些都是触手可及的。
设计就是一系列的规则,而我们从这些规则中学习。
对于设计者来说,Licklider(译者注:利克里德,人工智能的先驱,上文观点被引用的人)已经离开有些年头了。虽说 Macintosh 机器让我们工作得更快,但我们对于语义层面的交互来停留在 1984 年。几十年之后,我们就会开始觉得像是开拓一个新的时代。那将是我们行业历史上最激动人心和智慧闪耀的时代,真正人机共生的尖端时刻。
]]>]]>我们是凹凸实验室,来自京东(深圳)最年轻富有基情的技术团队。
我们现在有34位凹凸曼小伙伴,期待下一个你。
投递简历至aotu{AT}jd.com,今年夏天,O2不见不散!
在工作的沟通交流中,邮件是必不可少的工具之一。而规范一致的邮箱签名设计有利于打造和传播团队品牌形象,以提高团队的知名度。同时,借助工具能够使得签名生成更为方便、快捷。
点击体验工具:https://sign.aotu.io/
项目托管在Github上开源,喜欢的话可以给项目加个『星星』
因为计划是个小而快的项目,所以更多的考虑是走捷径——用现成的库和框架。
选择Vue,主要还是私心,刚学习完Vue想拿来实践下。
若是要对比Angular来说,Vue相对来说更轻巧易上手,文档中文化。考虑到项目并不需要数据库,不需要后端处理,是个完全依靠前端的项目,所以选择专注“让编写动态的UI界面变得轻松简单”的Vue其实是比较合适的。
工欲善其事,必先利其器。
一、明确功能核心。
邮件签名设计的主要功能是给予统一的签名设计模版,用户只需要输入个人信息即可生成规范的个人邮件签名。
二、确认技术路线。
从技术角度说就是,根据邮件签名设计模版先构建好对应的静态DOM结构,结合用户输入的数据动态实现从DOM结构转化为图像,然后供以下载使用。
三、库的选用
那么,对应技术点各个击破:
方法概览
|
|
使用HTML5的文件上传API来读取用户选取的图像文件,一旦监听到图像上传完毕,则调用loadCropper()
方法来初始化。
|
|
根据官方文档说明,选择合适的参数来定制功能。
按照邮件签名设计的模版,头像需要裁剪成要求一比一的比例,因此只需要简单的一比一裁切功能即可。
|
|
完成裁剪,将裁剪结果输出为base64格式。
|
|
调用dom-to-image以及FileSaver.js库,将对应的DOM结构输出为图像并下载。
秉承着”用户体验至上”是产品的黄金原则之一的理念,因此,在项目完成基本的功能之余,在用户体验方面做些了小优化:
一、信息格式化,优化用户输入
对于手机号码,用“-”分隔符更便于用户阅读和记忆,但是用户在输入信息时,会自动忽略“-”的存在,因此,考虑到的阅读和输入习惯,利用Vue的过滤器(Filter)来优化用户输入。
除此之外,在配色设置中,采用的是HTML5的原生取色器,严格规定颜色色值输入是RRGGBB格式的十六进制代码,但是对于用户习惯来说,对于#FFFFFF类似的写法更倾向于缩写为#FFF的简洁形式,因此,在色值输入时,同样是通过过滤器(Filter)来自动将缩写格式转化为标准形式。
二、生成二倍图,适配高清屏
在如今Retina屏幕的兴起的时代,适配高清屏已经成为UI界面设计的重要优化步骤,因此,为了适配高清屏,在dom-to-image的原基础上进行了代码优化,使得输出结果为二倍图形式。
三、个性化设置,供团队外共享
在实际的应用中,考虑到工具的分享与传播,在维持原有基本的LOGO、版面设计外,特意设计配色个性化设置功能,让团队成员外的用户能够随心所欲选用自己喜欢的配色,设计出有个人特色的签名图。
四、功能划分、字体,交互设计优化
考虑到工具的主要面向对象是团队成员,使用频率较低,所以配色设置作为附加功能设计在预览区的右上角,通过点击可以将信息输入面板切换到配色设置面板。
这样的设计将不同的功能集合在不同区域,在页面上更为合理地利用有限空间外,同时使得用户更专注于主要的功能块。
在另一方面,在Windows平台和Mac平台共同支持的默认字体有限,同时要考虑到遵循设计还原,因此,通过使用第三方字体来优化字体,使其在不同平台下能够表现一致。
对于Vue.js的学习,我只算是新手初试,在这里总结下遇到的一些常见问题,作为分享给大家的小技巧:
1.使用v-text
替代{{}}
类似于Angular的v-bind
替换{{}}的做法一样,在Vue中使用v-text
替代{{}},可以避免在渲染页面的过程中出现双向绑定数据的未处理状态。
2.没有v-change
这样的指令
在Agular中用ng-change可以为元素帮到change事件响应,但是在Vue中,在默认情况下,v-model 在input 事件中同步输入框值与数据,可以添加一个特性 lazy,从而改到在 change 事件中同步。
3.v-bind:class
替换 v-show
指令
Vue中的v-show指令是通过内联样式display:none
来来处理显示和隐藏元素(true时,不做内联样式;false时内联display:none)。
所以,如果使用v-show
指令在渲染页面的时候一开始,元素就会显示出来再被隐藏,这样的用户体验并不好。所以,可以考虑使用v-bind:class
来处理,自己写好显示隐藏的样式,通过绑定类名来切换元素显示隐藏状态。
4.延续上一问,为什么不使用v-if
替代,与v-show
的区别在哪里呢?
从表现来看v-if
和v-show
都是用来显示隐藏元素。但v-if
则是通过删除添加DOM结构的手段来实现显隐的,在元素上绑定的监听事件就会随着删除而销毁,是一个局部编译/卸载的过程。因此,在这样的情景限制下,我没有选择使用v-if
来替代v-show
。
5.Vue.js的学习资料推荐
Vue.js是国人尤雨溪大大作为主要开发的,所以文档中文化做得非常棒,官网有齐全的资料,快速入门教程、API文档等等,个人认为作为上手研读官网(Vue.js)的教程即可,推荐:)。
]]>感谢长篇阅读至此,“说一做二想三”,这个小项目也算是初成,告一段落了。:)
作为一个小项目,自认为并不是特别能带来很多的干货,但是也就是这种小尝试、小实践去慢慢积累经验,前端路漫漫,互勉加油吧。
随着互联网的发展,我们的业务也日益变得更加复杂且多样化起来,前端工程师也不再只是做简单的页面开发这么简单,我们需要面对的十分复杂的系统性问题,例如,业务愈来愈复杂,我们要如何清晰地梳理;团队人员愈来愈多,我们要如何更好地进行团队协作;功能愈来愈多,我们要如何保证页面的性能不至于下降,等等。所有的这些都可以归结为如何提升开发体验和性能问题。
我们主要从以下三个方面来提升我们的开发体验。
当团队人员不断扩充时,我们需要制定统一的规范来对平时的开发工作做出一定约束和指导。统一的规范包括前端的代码规范,根据规范定义好一套代码检查的规则,在代码提交的时候进行检查,让开发人员知道自己的代码情况。
同时,根据以往的开发经验,我们制定了统一的项目框架,根据业务功能不同,将一个项目(app)拆分成不同的业务模块(module),而每一个模块都包含自身的页面(page)以及构成页面所需要的组件(widget),每一个项目涉及到app、module、page、widget这些已经约定好的概念,这样让项目结构更加清晰,而且让团队内不同业务的人员之间切换无障碍。
在项目中引入组件化的概念,这里的组件对应上文讲到的widget,每一个组件都会包含组件自身的模板、css、js、图片以及说明文件,我们使用组件来拼装页面,像搭积木一样来拼装我们的页面,同时一个组件内可以调用另一个组件。
在拿到设计稿后,我们首先需要确定哪些需要做成公共组件,那些是要做成独立组件,以及组件间如何进行通信。在页面中调用这些组件后,会自动加载组件的模板以及组件的静态资源,而当组件不再需要时,只要移除掉组件引用,那么相应的模板和静态资源也会不再加载。
组件化的好处主要有这么几点
在前端开发中,我们总是会去使用很多工具、手段来优化代码、提升开发效率,例如,我们会使用sass、less等CSS预处理工具来编写更好维护的样式代码,我们也会使用CSSLint、eslint等代码检查工具来检查代码的语法错误,使用文件合并压缩等手段来减少资源大小,除此之外我们还会去做雪碧图合并、多倍图处理、字体压缩处理、代码发布等等。
曾经有大神说过,超过90s的工作都应该自动化掉。而以上所有的这些工作,贯穿我们整个开发流程,但是不同工具的切换不但显得凌乱,而且影响开发效率。在自动化、工程编译的思想早已深入人心的当下,我们当然也要紧跟潮流,所以我们考虑通过自动化手段来提升我们的效率,让所有操作可以一键式开速执行完。
我们将通过定义好一系列的编译任务,按照一定顺序依次对我们的项目自动进行编译操作,最后产生出可上线的代码。
我们主要从以下四个方面来做好性能优化。
页面的打开速度一直是大家非常关心的一个指标,一个页面打开太慢会让让用户失去等待的耐心,为了让用户更快地看到页面,我们考虑将页面中部分静态资源代码直接嵌入页面中,我们通过工具处理,在工程编译阶段,将指定的静态资源代码内嵌入页面中,这样可以减少HTTP请求,提升首屏加载速度,同时降低页面裸奔风险。
同时,我们考虑通过尽量减小页面体积来提升页面打开速度,在业务上我们将页面划分为一个个楼层组件,以京东美妆馆为例,页面中从上而下分为首焦、至IN尖货、今日特惠、潮流前沿、口碑榜单这么几个楼层组件,其实这个页面还有很长,内容非常多且复杂。
之前我们的做法是整个页面直出,这样一次性加载的内容会非常多,为了提升打开速度,我们考虑通过按需加载的方式来优化页面的加载。我们在页面中只放每一个楼层的框架性代码,楼层的模板和数据都通过异步的方式去拉取,来实现楼层组件的按需加载,同时我们可以对模板以及数据进行缓存,以此来减少请求,做更极致的优化。在开发中我们以正常组件的方式去开发整个页面,随后通过编译工具,在代码编译阶段自动将楼层的模板抽离成一个独立的JS文件,并给楼层容器打上标记位,通过页面加载逻辑去按需拉取模板,再进行渲染。
通过给楼层容器和模板分别加上标记位
o2-out-tpl-wrapper
o2-out-tpl
在编译时自动将指定的模板代码抽离成独立js文件
并且给楼层容器打上标记
同时在逻辑脚本适当位置自动加入模板的版本
通过上述步骤,实现按需加载的自动化生成,在提升性能的同时,很好地解放我们生产力。
根据页面组件化,通过工具分析,我们将获得页面与组件的依赖关系表,同时也能确认页面所引用资源的依赖关系,例如,我们在页面hello中同步引用组件topbar,那么依赖关系表中将会记录同步引用关系hello引用topbar.tpl、topbar.css、topbar.js,那么页面hello将会自动加载组件topbar的CSS与JS,同时依赖表会记录异步引用的关系,假如我们在组件C中通过API异步引用了组件D的js,那么会在依赖表中记录C异步引用D.js这一个依赖关系,这样D.js这个资源将会在用到的时候被异步调用。
同步引用的资源通过生成combo形式链接,在服务端进行文件合并,这样在页面加载的时候,页面只会加载自己需要的同步资源,异步的资源将会在用到的时候再加载,有效避免资源冗余。同时删除、增加组件也非常方便,只需改动模板中对组件调用,通过编译工具会自动重新生成模板以及combo链接。
我们可以将资源加载的操作抽离出来,形成一套统一的资源加载框架设计,这样我们使用的模板可以变得更加灵活,无论是纯html模板,还是PHP或Java之类的后端模板都能有效支持。编译工具扫描代码后只生成资源依赖表,我们通过实现各语言平台的资源加载框架,让不同语言的模板都能基于同一个资源依赖表进行资源加载。
同时,对资源进行MD5重命名处理,文件md5重命名也是一种提升性能的有效手段,使用文件md5后开启服务器强缓存,可以提升缓存的利用率并避免不必要的缓存判断处理。但文件md5重命名后会出现开发时引用的文件名对不上的问题,这就需要在资源表中记录原文件名与md5重命名后之间的对应关系,当我们引用一个资源时,就会通过查表获取重命名后的资源名,然后利用代码中引用资源定位的能力来进行资源名自动替换。
所谓静态资源预加载,就是当用户在进行浏览页面的时候,我们可以在当前页面静默加载下一个页面的静态资源,这样当用户进入到下一个页面时就能快速打开页面,从而在不知不觉中提升页面的打开速度。
我们会在静态资源预加载平台上配置每一个页面id对应需要预加载页面资源的id,然后系统通过读取资源依赖表获取到所需要预加载的静态资源,生成预加载资源列表文件,再将文件推送到线上服务器,通过页面挂载js请求获取预加载资源列表,随后静默加载资源。在有了资源依赖表后,我们可以准确地分析到每一个页面引用资源的请求,就可以很好地实现静态资源预加载的功能。
工欲善其事,必现利其器。为了实现我们对提升开发效率和产品性能的诉求,我们提出了比较完整的工程化解决方案以及对应的工具Athena。
Athena是由京东【凹凸实验室】(aotu.io) 推出的一套项目流程工具,通过Athena,我们可以很流程地跑完整个开发流程。Athena分为两部分,一是本地自动化编译工具,二是资源管理平台,其架构如下
Athena本地编译工具是一个基于NodeJs的命令行工具,通过执行命令的方式来优化我们的开发流程,目前Athena的主要功能有
在执行创建命令时,Athena会从管理平台下载自定义好的项目模板,可以根据模板创建项目、模块、页面、和组件。Athena有四个创建命令:
通过执行 $ ath app demo
命令就可以生成定义好目录结构的项目。
随后可以通过 $ ath module home
来创建一个业务模块;
通过 $ ath page index
来创建页面;
通过 $ ath widget widgetName
来创建组件。
Athena中实现组件化主要是分为两种,一是针对纯HTML模板,通过扩展模板引擎方法实现,提供了组件化API widget.load
,它可以方法接收三个参数,第一个参数是widget的名称,后面两个参数是可选参数,第二个是向widget传递的一些参数,第三个是widget所属的模块,如果是本模块,可以不传例如
|
|
通过模板引擎编译,执行widget.load方法,可以实现加载模板,记录依赖关系的目的。
二是针对不同语言的后端模板,通过实现各自的组件化框架来进行组件的加载,例如 PHP
下使用 <?= $widget->load('user', NULL, 'gb') ?>
来进行组件加载,再通过代码扫描得出组件依赖关系。
Athena针对模板提供了一系列的API来扩展丰富的功能,例如前面提到的 <%= widget.load() %>
来实现组件化。
同时Athena中还提供了其他API:
<%= getCSS() %>
、<%= getJS() %>
用来引用CSS/JS文件,传入文件名和模块名;
<%= uri() %>
提供了资源定位功能,可以在模板中标记资源,编译过程中会进行替换,而且在JS中也有资源定位API __uri()
;
<%= inline() %>
提供了内联资源的功能,传入文件名和模块名,可以在模板中内联任意资源,例如图片以及JS脚本;而且 inline
也可以内联一段网络资源,例如线上的JS文件,同样的在JS中也有内联资源API __inline()
;
雪碧图标识 ?__sprite
,在CSS中引用图片最后加上标识 ?__sprite
可以自动生成自定义名称雪碧图,同时支持自定义生成多张雪碧图,只需要要标识后面带上一个文件名,就可以生成一张以这个文件名来命名的雪碧图,例如 ?__sprite=icons
,这样所有带同样标识的图片就会生成一张以 icons
为文件名的雪碧图。
在编写完项目,就可以通过命令来对项目进行编译了,执行编译命令 $ ath build
,会针对指定模块执行已经定义好的编译任务,根据项目需求,目前编译都是基于业务模块去编译,编译任务的最小执行单位是页面,每次编译都会执行以下编译列表
执行预览命令 $ath serve
会执行精简版编译任务来编译项目,编译完项目后会生成一份站点地图,随后打开一个本地服务器来预览项目,使用这个命令可以很方便地进行开发,在预览时会同时watch目录和文件的改动,并且提供了livereload功能,我们可以在预览时任意修改文件,都将实时地反映到页面中,同时可以新建另一个窗口执行新增组件和页面的操作,让整个开发过程非常顺畅,我们只需关注开发本身就好,不需要再关注其他事。
执行完编译任务后,默认自动打开浏览器,预览站点地图
在进行项目预览的同时,Athena同时提供了mock data的服务,我们可以配置相应的路由,以及路由接口对应的假数据,所有的接口请求会发送到mock server上,在mock server中可以选择将请求代理到假数据平台还是代理到线上接口,这样就可以脱离后端进行开发联调了,以此实现数据的前后端分离。
在开发预览完后,通过命令 $ ath publish
就可以将项目发布到配置好的测试机上,发布同时支持ftp、sftp以及http形式。
我们通过组件化的手段已经将我们的项目进行组件化了,这样我们经过业务迭代积累,产出很多业务公共组件,但在以往的项目开发中,公共组件的更新与维护一直很受限制,而且有哪些公共组件、公共组件长什么样子,只能依靠口口相传或者手工维护的文档。所以在Athena中我们加入了组件平台,在组件平台上统一展示各个业务的公共组件,而得益于本地工具,组件平台不需要人工干预维护,我们可以在本地通过命令 $ ath widget-publish [widgetName]
命令来发布一个组件到组件平台,这样其他人就可以立即在组件平台进行组件的预览,而其他人若想使用该组件时,在本地通过命令 ath widget-load [widgetId]
就可以下载该组件到自己的模块目录下了。
这样组件的维护更加自动化,公共组件的使用也更加方便了。
组件发布
组件下载
为了提升开发效率,Athena做了一些优化操作
在开发时进行项目预览时,会执行精简版的编译任务,剔除了类似文件压缩、雪碧图生成、模板抽离处理等耗时的操作,只保留核心、必须的编译任务,这样可以极大地减少编译时间,提升开发的效率。
在开发进行预览时,会对所有文件的改动进行监听,而针对每一类文件都有非常细化的操作,当文件改动时只会执行改文件所需要的编译任务,而不会进行整体编译,这样可以很好地提升开发效率。例如改动某一组件的CSS文件,则只会针对该文件执行一些相关的CSS操作。
同时得益于所有文件依赖关系的记录,在监听时会根据依赖关系进行文件编译,例如某sass文件中引入了另一个sass库文件,修改这个sass库文件的时候,会根据引用关系表同时更新到所有引用到这个sass文件的文件,这样项目文件更新及时,让开发流程更加流畅。
在图片压缩和sass编译时,开启文件缓存,将已经编译过且没有改动的文件过滤掉,不再编译,大幅提升编译速度。
设置发布过滤,根据文件md5过滤掉已经发布过的文件,提升发布速度。
Athena本地工具早期技术选型是 Yeoman
+ Gulp
的方式,但后来由于安装、更新非常麻烦,命令太长很难打的原因,我们改成了自己开发一个全局安装包的方式,编译核心使用的还是 Gulp
的 vinyl-fs
来实现文件流处理,通过 ES6 Promise
来进行编译流程控制,最小以页面为单位,经过一系列编译任务,最后产出编译好的文件。
性能优化一直是前端工程师探索的课题,很多时候就是资源的分配问题,也就是资源管理。为了更好地配合本地构建工具来管理资源,我们搭建了管理平台。我们来看下,结合本地构建工具和管理平台,工作流程变成了怎样?
在上面的publish指令中,工具会扫描所有文件,执行代码检查,扫描页面文件,获取组件依赖关系,根据组件依赖关系进行文件合并,然后会进行样式处理、js处理以及图片的处理,根据配置是否进行md5重命名文件,组装html,插入样式、js和图片,最后将编译好的文件发布到相应的机器。在整个过程里面,会生成资源关系依赖表,最终会将资源关系表及编译后的文件上传至管理平台。
除此之外,每个指令的操作都会上报给管理平台。管理平台收到数据后,会对数据进行处理,最终可以在平台上看到项目相关的信息。
整体工作流程图如下:
从上面的工作流程中,我们可以看到,管理平台需要有数据统计、资源管理以及项目管理的功能。整体架构图如下:
数据统计包含项目操作日志,主要是用于统计团队每个成员具体的操作,方便项目成员查看项目代码变更;另一部份是统计样式表、脚本以及图片的压缩数据,用于显示工具给我们项目带来的提升。
以下是操作日志统计:
资源管理是管理平台的核心,主要分为4个部分:模块展示、依赖关系、组件预览和权限控制。这部分功能主要通过本地构建工具提供的资源关系表来完成。
模块展示,用于记录项目具体包含哪些模块以及模块具体的信息。在平常开发中,我们的项目会分为许多模块,不同的模块有不同的人来开发和维护。当项目越大的时候,可以通过管理平台清晰地看到模块具体的信息。
依赖关系,主要是html、css、js和图片相互之间的关系。通过分析资源关系依赖表,可以获取到各个资源被引用的情况以及线上版本的情况。当线上环境采用md5来做资源管理时,我们不是很清晰地知道静态资源对应线上哪个版本的资源,而有了这个依赖关系表,当出现问题时,我们可以更快地定位到具体的资源。
我们采用组件来拼凑页面,当项目越大时,组件越多,那么如何管理组件成为了一个棘手的问题。比如说,有一些比较老的冗余组件,我们不确定是否为其他页面所引用,那么就不能愉快地删除它。有了组件管理,可以清晰地知道组件的被调用情况,就可以对组件做相应的操作。
组件管理,结合组件平台来使用,在管理平台上引用组件地址预览组件,同时可以获取到组件被引用以及引用资源(如css、js、图片)的相关情况。
我们的组件分为两种,一类是通过ath w自动创建的,通过ath pu提交到管理平台的,在管理平台上进行组件的相关分析和编译,得到组件的信息,这类组件主要是跟业务绑定的;另一类是通过ath widget-publish提交到组件平台的,由组件平台进行相关处理,这类组件是通用组件,与业务无关,用于展示给开发以及相关业务方看的。
在组件平台上可以预览与编辑相关的组件,通过与设计师约定相关的设计规范来促使组件达到尽可能地复用,进而减少设计师的工作量,提升我们的工作效率。
通过ath widget-publish指令将组件提交到组件平台,组件平台会对组件源码进行编译,将组件名称md5、组件归类以及组件版本记录等等。
通过ath widget-load指令将组件下载到本地,当本地构建工具向组件平台发起请求时,会带上组件名称,组件平台会将源码进行编译,将组件名称重命名,并且相应地替换源码中的组件名称,同时记录组件的被引用记录。
权限控制,项目中存在公共组件模块,公共组件比较稳定,比如说轮播组件、选项卡组件等等,这部分代码一般比较少变动,可由少部分人来更新和维护,所以加入了权限控制机制,保证公共组件的稳定性。
我们在使用本地构建工具时,需要配置多个参数,比如主机信息、选择模版等,在命令行环境下有些不直观。为了简化这个操作,管理平台提供了项目创建的功能,同时提供了模版创建的功能。
在项目信息、模块信息以及组件信息发生变更的时候,为了第一时间能够通知项目成员更新,加入了消息通知的功能,目前通过发送邮件的方式,后期可以加入微信提醒的功能。
管理平台前端采用React+Redux的方式,后端采用Express+MongoDB,整体技术选型如下:
在平常的开发中,经常需要前后端联调,但是在项目开始之初,很多接口并没有提供,在以前的开发模式下,需要等待后端提供接口或者自己先定义接口,前端开发的进度可能会受影响。
为了不影响前端开发的进度,我们搭建了Mock数据平台,通过与后端协商数据格式,自定义数据接口,这样子就可以做到前后端分离,让前端独立于后端进行开发。
Mock数据平台基于mockjs搭建而成,通过简单的mock语法来生成数据。
Mock数据平台目前有如下功能:
本次分享首先讲述了我们在业务膨胀、人员不断增加的背景下遇到的项目开发上的问题,并提出了我们自己对于这些问题思考总结后得出的解决方案与思路,最后产出适合我们团队、业务的开发工具—— Athena。希望我们的方案能给大家带来一定的借鉴作用。
]]>随着移动互联网的到来,各种高清屏幕移动设备的层出不穷,导致H5应用在移动设备retina
屏幕下经常会遇到图标不清晰的问题。
为了解决屏幕分辨率对图标影响的问题,通常采用CSS Sprite,Icon Font,CSS Icon以及SVG以适应@x1屏、@2屏、@3屏,相对比较而言SVG矢量性、缩放无损等诸多优点,更应受前端设计师的青睐,可在许多公司的移动项目应用中却很鲜见,究其主因在于SVG开发学习成本比较高以及在绘制的性能上比PNG要差。此篇文章将从SVG快速导出到SVG、SVG Symbol组件化在项目中实战进行讲解,并提供SVG Symbol快速导出工具,教你如何提高SVG开发效率减少成本。
SVG是一种开放标准的矢量图形语言,使用svg
格式我们可以直接用代码来描绘图像,可以用任何文字处理工具打开svg
图像,通过改变部分代码来使图像具有交互功能,并可以随时插入到HTML中通过浏览器来浏览。
优点 | 缺点 |
---|---|
1.缩放无损还原,显示清晰 | 1.SVG在绘制的性能上比PNG要差 |
2.语义性良好 | 2.局限性,对应单色或普通渐变没什么问题,但对不规则的渐变以及特效叠加效果显示不全 |
3.可用CSS控制图标样式以及动画 | 3.兼容性稍差,android4.1才开始支持 |
4.减少http请求 | 4.学习应用成本较高 |
1.PC 端 SVG 有兼容性问题,因此 PC 端还是用 iconfont 比较靠谱。那么,与其为移动端多弄一套 SVG 方案,为什么不直接公用同一套 iconfont 库?成本问题。
2.知道 SVG Sprite 的人不多,而 iconfont 俨然成为前端面试必考题了。
3.抛开兼容,再就是SVG的局限性:单色或线性渐变(从左向右笔直进行渐变),径向渐变(从内到外进行圆形渐变)都没问题、但是不规则的渐变、就实现不了了。
4.SVG比图片麻烦、设计稿如果不优化节点、直接导出、代码量那个惊人,然而ai导出的SVG代码、节点优化后,也不能直接用、还得小改、总体来说SVG比图片好耗费功力太多。
此方法的缺点主要在于每个图标都需单独保存成一个 SVG 文件,使用时单独请求,增加了HTTP请求数量。
|
|
Inline SVG 作为HTML文档的一部分,不需要单独请求。临时需要修改某个图标的形状也比较方便。但是Inline SVG使用上比较繁琐,需要在页面中插入一大块SVG代码不适合手写,图标复用起来也比较麻烦。
|
|
这里的Sprite技术,类似于CSS中的Sprite技术。图标图形整合在一起,实际呈现的时候准确显示特定图标。其实基础的SVG Sprite也只是将原来的位图改成了SVG而已,控制SVG大小、颜色需要重新合并SVG文件。
|
|
SVG Symbols的使用,本质上是对Sprite的进一步优化,通过<symbol>
元素来对单个SVG元素进行分组,使用<use>
元素引用并进行渲染。这种方法的解决了上述三种方式带来的弊端,少量的http请求,图标能被缓存方便复用,每个SVG图标可以更改大小颜色,整合、使用以及管理起来非常简单。
①SVG Symbols作为body的第一个元素插入在HTML中使用:
|
|
②使用完整路径引用Icon(此方法涉及到跨域问题)
存在跨域问题,同域可以使用。
|
|
③js控制SVG写入body进行引用
推荐使用,有效分离结构和展现、解决缓存以及跨域问题。
svg.js:
svg.html:
Ps可以对矢量图层进行导出,即通过矢量工具绘制所在图层或图层文件夹进行导出,若对不是矢量图形进行导出,可能会引起错误或者导出的文件是位图。
SVG导出,建议图形一定要撑满整个画布,若存在间隙,网页使用时图标居中对齐就会比较麻烦。
1.批量导出SVG
批量导出SVG,只需在矢量图层或失落图层文件夹名后添加相应格式后缀(如.svg),依次点击菜单“文件->生成->图像资源”,确认该菜单项已被勾选。但是此方法会根据icon实际大小进行导出,若icon图标存在小数导出就不太适用,我们需要手动去调节。
2.设置导出为单个导出
设置导出为单个导出,选中矢量图层单击右键,依次点击“导出为->设置参数->导出”,此方法可以设置SVG的图像实际大小,以及画布大小。
导出为:
设置导出SVG图像实际大小,以及画布大小:
Photoshop导出来的是单个SVG文件,需要将这些单个SVG文件进行合并生产symbol
的SVG,这样才能通过symbol+use
进行引用。可以使用手工合并,推荐使用工具,安利给大家一个专门用于处理SVG Symbols用的glup插件gulp-svg-symbols。
下面我们就来以一个实例一步一步来使用下这个插件。
首先新建一个文件夹,目录结构如下图所示:
|
|
安装gulp-svg-symbols插件,若没有预先安装gulp请先行安装:
|
|
gulpfile.js
写入如下执行任务:
|
|
执行任务导出svg-symbols:
为了方便快速批量合成SVG图标生成 SVG Symbol,提高效率,我们开发了简易版的SVG Symbol可视化工具svg tool
。
打开后界面
主要支持单层结构SVG进行合并生产symbol,对于多层结构SVG合并注意手动修改对应颜色。
1.支持拖拽文件夹,自动遍历SVG文件合并生成symbol文件。
2.提供两种导出方式,正常模式、修正模式。
3.对文件viewBox大小不为整进行提示,可以选择自动修正模式系统自动放大处理。
4.导出svg-symbol.svg、svg-symbol.js,可直接引入svg-symbol.js方便使用。
svg tool下载地址:
mac:http://jdc.jd.com/svg/svgtoolfile/svgtool-1.0.0.dmg
win32: http://jdc.jd.com/svg/svgtoolfile/svgtool-win32-ia32.zip
win64: http://jdc.jd.com/svg/svgtoolfile/svgtool-win32-x64.zip
参考资料:
]]>gulp-ui做出来后再团队中使用了一段时间,以单个项目来执行的方式确实在经常多项目开发的使用环境中多有不便。于是在这个基础上,重写了整个代码结构,开发了现在的版本feWorkflow.
feWorkflow改用了electron做为底层,使用react, redux, immutable框架做ui开发,仍然基于运行gulpfile的方案,这样可以使每个使用自己团队的gulp工作流快速接入和自由调整。
与 NW.js 相似,Electron 提供了一个能通过 JavaScript 和 HTML 创建桌面应用的平台,同时集成 Node 来授予网页访问底层系统的权限。
使用nw.js时遇到了很多问题,设置和api比较繁琐,于是改版过程用再开发便利性上的考虑转用了electron。
electron应用布署非常简单,存放应用程序的文件夹需要叫做 app
并且需要放在 Electron 的 资源文件夹下(在 macOS 中是指 Electron.app/Contents/Resources/
,在 Linux 和 Windows 中是指 resources/
) 就像这样:
macOS:
|
|
在 Windows 和 Linux 中:
|
|
然后运行 Electron.app
(或者 Linux 中的 electron
,Windows 中的 electron.exe
), 接着 Electron 就会以你的应用程序的方式启动。
package.json
主要用来指定app的名称,版本,入口文件,依赖文件等
|
|
main.js
应该用于创建窗口和处理系统事件,官方也是推荐使用es6
来开发,典型的例子如下:
|
|
index.html
则用来输出你的html:
|
|
electron官方提供了一个快速开始的模板:
|
|
更多入门介绍可以查看这里Electron快速入门.
React做为一个用来构建UI的JS库开辟了一个相当另类的途径,实现了前端界面的高效率高性能开发。React的虚拟DOM不仅带来了简单的UI开发逻辑,同时也带来了组件化开发的思想。
ES6做为js的新规范带来了许多新的变化,从代码的编写上也带来了许多的便利性。
一个简单的react
模块示例:
|
|
|
|
|
|
通过React.createClass
创建一个react
模块,使用render
函数返回这个模块中的实际html模板,然后引用ReactDOM
的render
函数生成到指定的html模块中。调用HelloMessage
的方法,则是写成一个xhtml
的形式<HelloMessage name="John" />
,将name
里面的”John”做为一个属性值传到HelloMessage
中,通过this.props.name
来调用。
当然,这个是未经编译的jsx
文件,不能实际输出到html中,如果想要未经编译使用jsx
文件,可以在html
中引用babel
的组件,例如:
|
|
自从es6
正式发布后,react
也改用了babel
做为编译工具,也因此许多开发者开始将代码开发风格项es6
转变。
于是React.createClass
的方法被取代为es6中的扩展类写法:
|
|
我们可以看到这些语法有了细微的不同:
|
|
在feWorkflow中基本都是使用ES6的写法做为开发, 例如最终输出的container模块:
|
|
import做为ES6的引入方式,来取代commonJS的require模式,等同于
|
|
输出从module.export = Container;
替换成export default Container;
这种写法其实等同于:
|
|
{ lists }
的写法编译成ES5的写法等同于:
|
|
相当于减少了非常多的赋值操作, 极大了减少了开发的工作量。
ES6中介绍了一下编译之后的代码,而每个文件里其实也并没有import必须的react模块,其实都是通过Webpack这个工具来执行了编译和打包。在webpack中引入了babel-loader
来编译react
和es6
的代码,并将css通过less-loader
,css-loader
, style-loader
自动编译到html的style标签中,再通过
|
|
的形式,将react组件注册到每个js文件中,不需再重复引用,最后把所有的js模块编译打包输出到dist/bundle.js
,再html中引入即可。
流程图:
webpack部分设置:
|
|
webpack需要设置入口文件entry
,在此是引入了源码文件夹src中的index.js
,和一个或多个出口文件output
,输出devtoolsource-map
使得源代码可见,而非编译后的代码,然后制定所需要的loader
来做模块的编译。
与electron
相关的一个比较重要的点是,必须指定target: atom
,否则会出现无法resolve electron modules的报错提示。
更多介绍可以参考Webpack 入门指迷
feWorkflow项目中选用了react-transform-hmr做为模板,已经写好了基础的webpack
文件,支持react
热加载,不再需要经常去刷新electron,不过该作者已经停止维护这个项目,而是恢复维护react-hot-reload
,现在重新开发React Hot Loader 3, 有兴趣可以去了解一下。
Redux是针对JavaScript apps的一个可预见的state容器。它可以帮助我们写一个行为保持一致性的应用,可以运行再不同的环境中(client,server,和原生),并非常容易测试。
Redux 可以用这三个基本原则来描述:
单一数据源
整个应用的 state 被储存在一个 object tree 中,并且这个 object tree 只存在于唯一一个 store 中。
|
|
State是只读的
惟一改变 state 的方法就是触发 action,action 是一个用于描述已发生事件的普通对象。
所有的修改都被集中化处理,且严格按照一个接一个的顺序执行. 而执行的方法是调用dispatch
。
|
|
使用纯函数来执行修改
为了描述 action 如何改变 state tree ,你需要编写 reducers
。
Reducer
只是一些纯函数,它接收先前的 state
和 action
,并返回新的 state
。
|
|
redux流程图:
redux在react中应用还需要加载react-redux
模块,因为store
为单一state结构头,我们仅需要在入口处调用react-redux的Provider
方法抛出store
|
|
这样,在container的内部都能接收到store
。
我们需要一个操作store的reducer
. 当我们的reducer拆分好对应给不同的子组件之后,redux提供了一个combineReducers
的方法,把所有的reducers
合并起来:
|
|
然后通过createStore
的方式链接store
与reducer
:
|
|
上文介绍redux
的时候也说过,state是只读的,只能通过action来操作,同样我们也可以把dispatch
映射成为一个props传入Container中。
在子模块中, 则把这个store映射成react的props,再用connect
方法,把store和component链接起来:
|
|
这样,就完成了redux与react的交互,很便捷的从上而下操作数据。
Immutable Data是指一旦被创造后,就不可以被改变的数据。
通过使用Immutable Data,可以让我们更容易的去处理缓存、回退、数据变化检测等问题,简化我们的开发。
所以当对象的内容没有发生变化时,或者有一个新的对象进来时,我们倾向于保持对象引用的不变。这个工作正是我们需要借助Facebook的 Immutable.js来完成的。
不变性意味着数据一旦创建就不能被改变,这使得应用开发更为简单,避免保护性拷贝(defensive copy),并且使得在简单的应用 逻辑中实现变化检查机制等。
|
|
feWorkflow项目中使用最多的是List
来创建一个数组,Map()
来创建一个对象,再通过set
的方法来更新数组,例如:
|
|
更新的时候使用setIn
方法,传递Map
对象的序号,选中location
这个属性,通过action
传递过来的新值action.location
更新值,并返回一个全新的数组。
|
|
存:
immutable的数据已经不是单纯的json数据格式,当我们要做json格式的数据存储的时候,可以使用toJS()
方法抛出js对象,并通过JSON.stringnify()
将js数据转换成json字符串,存入localstorage中。
|
|
取:
读取本地的json格式数据后,当需要加载进页面,首先需要把这段json数据转换会immutable.js数据格式,immutable
提供了fromJS()
方法,将js对象和数组转换成immtable的Maps
和Lists
格式。
|
|
上文介绍了整个feWorkflow的UI技术实现方案,现在来介绍下实际上gulp在这里是如何工作的。
思路
我们知道node
中调用child_process
的exec
可以执行系统命令,gulpfile.js本身会调用离自身最近的node_modules,而gulp提供了API可以通过flag的形式(—cwd)来执行不同的路径。以此为思路,以最简单的方式,在按钮上绑定执行状态(dev或者build,包括flag等),通过exec
直接运行gulp file.js.
实现
当按钮点击的时候,判断是否在执行中,如果在执行中则杀掉进程,如果不在执行中则通过exec
执行当前按钮状态的命令。然后扭转按钮的状态,等待下一次按钮点击。
命令模式如下:
|
|
—cwd
把gulp的操作路径指向了我们定义的src路径,—gulpfile
则强行使用feWorkflow中封装的gulp file.js。我在js中对路径做了处理,以src
做为截断点,拼接命令行,假设拖放了一个位于D:\Code\work\vd\lottery\v3\src
下的路径,那么输出的命令格式为:
|
|
同时,通过action
扭转了按钮状态:
|
|
调用dispatch
发送给reducer
:
|
|
这样,就是整个文件执行的过程。
这次的改版做了很多新的尝试,断断续续的花了不少时间,还没有达到最初的设想,也还缺失了一些重要的功能。后续还需要补充不少东西。成品确实还比较简单,代码也许也比较杂乱,所有代码开源在github上,欢迎斧正。
参考资料:
]]>很多人不明所以,凹凸实验室作为协办方,还负责YY、映客的会议直播,我还是有义务照例简单介绍一下的哈。
HiFSD全称Hi Full Stack Developver,由腾讯TGIdeas主办的全栈工程师交流分享平台。
本届分享会以前端
分享为主,邀请了京东、腾讯、微众银行和阿里的分享嘉宾,主题为:
其实利用YY、映客做现场直播只是我们临时加进去的内容,应该是业界首次利用当红的直播APP做技术分享会的直播,效果虽然说不算完美,但最终还是有几乎近千位网友通过直播的方式观看了本次分享会的内容。
感谢伙伴们的支持,请期待凹凸实验室在下一次同类的分享会中做更给力的线上直播。
直播回放地址请猛击这里。
或扫描二维码进行观看:
]]>Shadow DOM它允许在文档(document)渲染时插入一棵DOM元素子树,但是这棵子树不在主DOM树中。
因此开发者可利用Shadow DOM 封装自己的 HTML 标签、CSS 样式和 JavaScript 代码。
看一个简单的video:
|
|
页面完成了,在浏览器chrome中打开,然后打开 Chrome 的开发者工具,点击右上角的“Settings”按钮,勾选“Show user agent shadow DOM”。
浏览器截图:
#shadow-root称为影子根,可以看到它在video里面,换句话说,#shadow-root寄生在video上,所以video此时称为影子宿主。可以看到上图有两个#shadow-root,这是因为#shadow-root可以嵌套,形成节点树,即称为影子树(shadow trees)。影子树对其中的内容进行了封装,有选择性的进行渲染。这就意味着我们可以插入文本、重新安排内容、添加样式等等。如下所示:
使用createShadowRoot()来创建Shadow DOM,并赋值给一个变量,然后添加元素给变量即可。
|
|
浏览器截图:
有没有注意到.shadowroot_son的样式color: #f00;不生效?!那是因为影子宿主和影子根之间存在影子边界(shadow boundary),影子边界保证主 DOM写的 CSS 选择器和 JavaScript 代码都不会影响到Shadow DOM,当然也保护主文档不受 shadow DOM 样式的侵袭。
需要完成此项任务,需要两个利器:<content>
和<template>
。
<content>
通过 <content>
标签把来自主文档并添加到 shadow DOM 的内容被称为分布节点。
<content>
的select属性告诉<content>
标签有选择性的插入内容。select 属性使用 CSS 选择器来选取想要展示的内容,选择器包括类选择器、元素选择器等。
<template>
目前的模板HTML做法通常是在<script>
中嵌入模板HTML,让内部的HTML标签按照字符串处理的,从而使得内容不显示:
|
|
<template>
元素的出现旨在让HTML模板变得更加标准与规范。
<template>
在使用前不会被渲染,不会执行加载等操作,也能够实现隐藏标签内容,而且位置任意性,可以在<head>
中,也可以在<body>
或者<frameset>
中。
通过以上对 <content>
和<template>
的简单了解,我们来通过一个实例加深理解:
|
|
浏览器截图:
我们来看一下下面三个属性的用途:
|
|
贪心插入点:如果把select=”.shadowhost_content1”改成select=””或者select=”*”,那么会有不一样的结果。因为贪心选择器放在了模板的第一个,他会将所有内容都抓取,不给其他select 选择器留一点内容。浏览器截图如下:
在shadow DOM中利用:host定义宿主的样式,当然用户可以在主文档中覆盖这个样式。
:host 是伪类选择器(Pseudo Selector),:host或者 :host(*)是默认给所有的宿主添加样式,或者单独给一个宿主添加样式,即通过:host(x),x可以是宿主的标签或者类选择器等。
另外:host还可以配合:hover、:active等状态来设置样式,如:
|
|
原则上来说,影子边界保证主 DOM写的 CSS 选择器和 JavaScript 代码都不会影响到Shadow DOM。
但你可能会想打破影子边界的所谓保证,主文档能够给Shadow DOM添加一些样式,这时可以使用::shadow。
::shadow 选择器的一个缺陷是他只能穿透一层影子边界,如果你在一个影子树中嵌套了多个影子树,那么使用 /deep/ 。
还记得什么叫分布节点吗?通过 <content>
标签把来自主文档并添加到 shadow DOM 的内容被称为分布节点。
分布节点的样式渲染需要用到 ::content。即使分布节点为em标签,直接写 em {} 不生效,应该写成::content > em {}。
|
|
浏览器截图如下:
Shadow DOM 里的 JS 与传统的 JS 一个真正不同的点在于事件调度(event dispatching)。要记住的一点是:原来绑定在 shadow DOM 节点中的事件被重定向了,所以他们看起来像绑定在影子宿主上一样。
当你点击“shadow text”的输入框时控制台却输出了宿主元素(就是 #host)的 id 。这是因为影子节点上的事件必须重定向,否则这将破坏封装性。
分布节点来自原有 DOM 结构,没必要重定向。
|
|
分别单击每个输入框,控制台打印截图如下:
事件abort、 error、 select 、change 、load 、reset 、resize 、scroll 、selectstart不会进行重定向而是直接被干掉,因此事件不能冒泡到文档中,事件监听重定向至文档,因此无法监听到这一事件。
把上面的监听事件click改成select,即改成:
|
|
分别双击每个输入框,你会发现,shadow text的输入框没有打印,就是没有发生select事件。
看上去只能在chrome中愉快地玩耍。
webcomponents.js使得Shadow DOM在非 native 支持的浏览器上实现。
A Guide to Web Components
Shadow DOM系列文章
HTML5 <template>
标签元素简介
不管是PC还是移动端,图片一直是流量大头。不管是在京东首页还是频道页,商品图片以及广告图片占据了大部分的流量。
评价网站性能好坏的一个主要指标就是页面响应时间,也就是说用户打开完整页面的时间。基于JPEG还有PNG图片格式的网页,其图片资源加载往往都占据了页面耗时的主要部分,那么如何保证图片质量的前提下缩小图片体积,成为了一件有价值的事情。
而如今,对JPEG、PNG以及GIF这些格式的图片已经没有太大的优化空间。但是,Google推出的WebP图片格式给图片优化提供了另一种可能。
WebP是一种支持有损压缩和无损压缩的图片文件格式,根据Google的测试,无损压缩后的WebP比PNG文件少了26%的体积,有损压缩后的WebP图片相比于等效质量指标的JPEG图片减少了25%~34%的体积。
通过研究WebP图片格式,尽可能全面地了解WebP图片的优劣势以及应用WebP图片给我们带来的收益以及风险,最终提升用户体验。
京东商品图以及频道页广告图目前基本为JPG图片,以下数据主要为JPG和WebP图片的对比,测试图片采用京东商品图。
WebP目前支持桌面上的Chrome和Opera浏览器,手机支持仅限于原生的Android浏览器、Android系统上的Chrome浏览器、Opera Mini浏览器。
根据对目前浏览器占比与WebP的兼容性分析,如果采用WebP图片,大约有42%的用户可以直接体验到。
Google提供了命令行工具用于将图片转换为webp。
在Mac下,可以使用homebrew安装webp工具:
|
|
Linux采用源码包来安装(CentOS下):
|
|
安装完命令行工具后,就可以使用cwebp将JPG或PNG图片转换成WebP格式。
|
|
options参数列表中包含质量参数q,q为0~100之间的数字,比较典型的质量值大约为80。
也可以使用dwebp将WebP图片转换回PNG图片(默认)。
|
|
更多细节详见使用文档
下面我们以一张图片为例,分别用不同质量进行压缩。
WebP图片相比于JPG,拥有:
目标图像的质量和文件大小之间存在明显的折中关系。在很多情况下,可以很大程度降低图像的大小,而用户却几乎不会注意到其中的差别。
抽取100张商品图片,采用80%有损压缩,大约能减少60%的文件大小。
更多测试。
根据Google的测试,目前WebP与JPG相比较,编码速度慢10倍,解码速度慢1.5倍。
在编码方面,一般来说,我们可以在图片上传时生成一份WebP图片或者在第一次访问JPG图片时生成WebP图片,对用户体验的影响基本忽略不计,主要问题在于1.5倍的解码速度是否会影响用户体验。
下面通过同样质量的WebP与JPG图片加载的速度进行测试。测试的JPG和WebP图片大小如下:
测试数据折线图如下:
从折线图可以看到,WebP虽然会增加额外的解码时间,但由于减少了文件体积,缩短了加载的时间,页面的渲染速度加快了。同时,随着图片数量的增多,WebP页面加载的速度相对JPG页面增快了。所以,使用WebP基本没有技术阻碍,还能带来性能提升以及带宽节省。
在浏览器中可以采用JavaScript检测是否支持WebP,对支持WebP的用户输出WebP图片,否则输出其他格式的图片。
JavaScript检测是否支持WebP代码如下:(出自Google官方文档)
|
|
在浏览器向服务器发起请求时,对于支持WebP图片的浏览器,会在请求头Accept中带上image/webp的信息,服务器便能识别到浏览器是否支持WebP,在服务器中处理图片。
采用JavaScript能力检测的方式来加载WebP图片,通常的做法是通过图片懒加载的方式来完成。主要流程如下:
页面加载会很快,无需等待图片加载。之后,javascript代码会动态地更新图片标签,根据浏览器支持WebP格式与否,动态生成WebP图像或JPG图像链接。
Google开发的PageSpeed模块有一个功能,会自动将图像转换成WebP格式或者是浏览器所支持的其它格式。
以nginx为例,它的设置很简单。
首先在http模块开启pagespeed属性。
|
|
然后在你的主机配置添加如下一行代码,就能启用这个特性。
|
|
我们可以看下经过转换后的代码:
页面原始代码:
|
|
Chrome打开后源码如下:
|
|
Safari打开如下:
|
|
更多详见ngx_pagespeed文档:https://developers.google.com/speed/pagespeed/module/build_ngx_pagespeed_from_source#dependencies
很明显,WebP格式是提升用户体验的又一利器,虽然浏览器对WebP的支持仍有很多需要改进的地方,但是通过是使用一些工具和技术,能够体会到WebP的好处,使得页面加载速度更快,同时节省了带宽。
]]>console.log的基本语法如下:
|
|
一打开天猫、知乎的页面console,招聘信息就袭来啦!
天猫招聘:
知乎招聘:
小女子就好奇了,怎么做的呢,可以更酷炫一点吗?
原来啊,故事是这样开始的:浏览器提供了这么一个API:第一个参数可以包含一些格式占位符比如%c,console.log方法将依次用后面的参数替换占位符,然后再进行输出。
格式占位符 | 作用 |
---|---|
%s | 字符串 |
%d or %i | 整数 |
%f | 浮点数 |
%o | 可展开的DOM |
%O | 列出DOM的属性 |
%c | 根据提供的css样式格式化字符串 |
给爷来一段全部用上占位符的代码,然后小女子遵命了:
|
|
截图如下:
%o和%O在普通对象上的表现是一样的,但是在DOM上就有区别了:
|
|
随意打开的一个页面测试,firefox总是可以查看可展开的DOM节点,即行为是%o;IE不支持%o和%O ;chrome显示正常,截图如下
使用%c占位符时,对应的后面的参数必须是CSS语句,用来对输出内容进行CSS渲染。于是,利用%c配合CSS可以做出吊炸天的效果,比如背景色、字体颜色渐变、字体3D效果、图片等,情况允许再用颜文字、emoji卖个萌,萌萌哒~
什么,竟然也支持图片?!log一个图片试试:
|
|
firebug截图如下:
不过要注意了:
学习了以上的东东,就是为了这个的出世,铛铛铛~~~:
firebug截图如下:
点击 此处 可查看例子啦~~
那字符画是怎么做到的呢?臣妾可以做到,哈哈哈哈。
把下面的代码粘贴在console控制台可查看效果哟~~
|
|
没错了,效果是长这样子的:
你不会天真地认为,我是手打这个京东狗出来的吧?!
不不不,有神器相助!
这里推荐三个ASCII字符画制作工具:
在线工具picascii
在线工具img2txt
ASCII Generator功能比较齐全,不过需要下载使用噢~ 下载参考地址:ASCII Generator Portable(将图片转为字符画) v2.0下载
ASCII Generator使用方法如下:
首先载入图片,然后调节大小、字体、亮度对比度、抖动程度,直到自己满意后,将其复制出来:
复制到sublime中,将每行开头的换行删除,且替换成\n。最后只有一行代码,即保证没有换行。
最后再丢到console.log(“”)代码中即可,当然,也可以添加结合%c做出更酷炫的效果。
关于兼容性总结:
最后想说的: 前端人对chrome情有独钟,那招聘信息就只在chrome或者webkit浏览器下显示吧,哈哈。
Diagnose and Log to Console | Web Tools - Google Developers
]]>假如
HTTP/2已经普及,我们前端跟现在会有哪些不同,也许你会觉得太操之过急,没必要这么早开始讨论,然而回看历史,各种技术总会在我们不经意间闯入我们的工作,更新我们的生活,与其措手不及不如提早部署,只有心怀远方我们才能走的更远。
如果不清楚什么是 HTTP/2 的可以先了解下,前面有一篇图文并茂的介绍HTTP/2的文章 传送门
去年(2015年5月)HTTP/2标准正式发布,各大浏览器,服务器厂商都开始大面积支持这一标准,国内外各大应用网站也都开始纷纷踏入HTTP/2的阵营,Facebook、Google、Twitter、国外亚马逊(部分请求)、天猫(部分请求)、淘宝(部分请求),还有一些小型站点比如 ISUX 等。
一些大型公司,因为架构体系原因导致迁移带来的阵痛,拖累了升级HTTP/2的进度,有心无力,反而一些小型网站,架构合理的公司升级起来更迅速一些,提早体验到了HTTP/2带来的快感。
现在软硬件都已基本到位,“趟雷”的已经探好了路,就等后续大军杀到。买房子的时候,都说早买早享受,HTTP/2也一样,房子买晚了,HTTP/2你还要再等么,你要做的其实就是尽早升级尽快享受。
也许HTTP/2真正全面普及,可能还得一两年,或者是三四年,那么我就直接穿越到未来的一天,假设
HTTP/2已经普及了,那么很快会有很多问题摆在我们面前:以前的架构还需要么?如何组织代码更能合理支持HTTP/2?我们十几年的优化总结还有用么?雅虎军规还是我们的优化的标准么?
在讨论这之前我们再来回顾下,HTTP/2给我们提供了什么。
概念网上大把,我们直接用几个例子来分析,把概念直接体现在实例中:
示例分别用169张图片拼合成一整张大图,第一组图片请求为HTTP1.1,第二组请求为HTTP/2,下面我截取的加载过程的动画。
HTTP1.1 vs HTTP/2 请求速度
同时导出了 http1.1-images.har 和 http2-images.har 文件,我们借助第三方服务HAR Viewer 来看下,请求细节:
HAR文件 是以.har结尾的JSON文件,用于记录了HTTP请求的详细信息。这里有详细介绍,可以在Chrome中开发者工具的Network中点右键导出.har文件。
下图就是HAR文件分析的截图:
HTTP1.1
上图为HTTP1.1的图片请求,请求基本上是6个一组,然后6个完成后再 串行请求 下一组。
HTTP/2
从上面的部分截图,可以清晰看到,所有请求基本都是并行请求,由于数据发送量较大,所以会有“等待”,这里的等待应该是数据流在客户端或服务器端重新组合的过程,正是因为这样所以单个请求时间相对更长。但是就整体速度来说 HTTP/2 为1.53s, HTTP1.1为 2.47s。速度快了近40%。
上面这张图也是HTTP/2的一部分,从另一面也可以体现出,并行请求的短板,就是木桶理论。所以请求尽量做到细粒度,能更快返回数据。
PS:当然我只截取了比较能突出差别的图,具体的完整版可以点进HAR Viewer,然后拖进去我们上面提供的har文件。
这就是HTTP/2为我们带来的最大的好处多路重复。
服务器推送(Server Push)
示例分别用Server Push推送,和传统的加载,带来的性能上的差异,同时我也导出了两个HAR文件,如果需要可自行下载 serverpush.har 和 nomalrequest.har。
下面是这两个文件的请求截图(下面是Server Push, 上面是普通HTTP/2的请求):
HTTP/2 Server Push 和 普通请求相比,去掉了请求阶段,直接返回数据(Content Download),数据获取速度更快,而且push中可以嵌入逻辑,并且请求还可以进行缓存。
贴一小段代码,下面的代码为请求接口的时候,主动推送zepto代码给客户端的核心代码:
|
|
关于优先级和依赖性(Priority)
优先级的设置可以看下这一篇文章,通过设置请求资源的Pripority,达到资源获取的优先级。
没有设置Pripority
设置CSS 和 JS 的 Pripority后
![pripority]//img.aotu.io/Fr9ScV9DSWoyn8yr0RWvbpWhQq30)
设置Pripority后,CSS 和 JS 明显速度更快了,但是代价是牺牲了图片的部分请求的速度。
通过上面的示例,牛B闪闪的HTTP/2已经在屏幕上熠熠生辉了。
那么回过头来再看看我们以前针对HTTP1.1的优化,我觉得很多其实都是应对HTTP1.1不足的HACK,HTTP/2中这些都已经不是问题了,所以HACK可以去掉了,比如下面这些。
这里同样我写了一个测试示例 Demo 雪碧图 & Demo 没有雪碧图,如下图上面为使用雪碧图的页面请求, 下面是普通的请求页面。
由于木桶理论,在非雪碧图请求中,由于最后返回速度决定于那个最慢的请求,所以非雪碧图单张524B的文件速度,跟雪碧图6.9K速度比,还慢一些,虽然如此,我们再看onload事件触发时间,因为多路复用的特性,虽然请求超过4倍但是请求总时长并不是多4倍,而是多了119ms(1-2张图片的请求时长),而且根据请求更多其实差距不会体现在请求的多少,而只会体现在请求的响应时间和下载数据的大小,而雪碧图占用的请求应该都是很小的,所以合并与否其实不明显,再考虑到雪碧图的维护成本,其实就HTTP/2来说并不推荐再使用雪碧图了。(不过为了兼容HTTP1.1,其实现阶段多版本并存的时候还是建议保留,不用再单独处理逻辑)
HTTP1.1中,所有为了减少请求而做出的HACK,在HTTP/2中都已经不再是性能优化考虑的主要点了。
HTTP1.1时代,我们经常会用多个域名来做请求优化,因为浏览器同域名下会有并行请求数限制(根据浏览器不同2-8个,比如IE6只有两个),然而DNS解析又得额外花时间,所以以前对域名的个数还需要根据各自网站找一个平衡点。HTTP/2就不用理会这个了,因为多路复用,并行请求不再是瓶颈,收敛了域名后还能减少DNS解析时间,所以HTTP/2中我们不用再细分域名了。
HTTP1.1的时候,我们经常会根据当前的页面,将请求合并成一个。HTTP/2中可以更细粒度的组合你的接口,不用再根据某个页面所需数据,来组合一个专门的无意义的接口了(不用合并请求),不怕请求多,就怕单个请求太慢。
有人说Server Push就是另外一种形式的内联,其实不是,内联太Low了,完全无法跟它来比较。
首先我们来回顾下,HTTP1.1时代,我们为什么要内联,因为我们希望减少请求,我们为了加快首页的渲染速度,甚至会把首页第一屏的样式内联到HTML中,一起返回,加速首屏渲染。然而当有人想改动首屏任何内容,无论多小都得重新替换掉整个页面。
在HTTP/2下我们可以通过推送的方式给你想要的资源,跟你的HTML请求一块儿返回给你,不仅如此,push的内容还可以进行缓存,多页面共享。
先来看下兼容和各版本客户端占有率统计
数据来自caniuse
PC浏览器占有率
数据来自百度统计
IOS & Andorid 操作系统版本占有率
数据来自友盟 2016年 6月5日统计数据,红色为支持版本
结论:PC 端 Chrome 占有率已经近 40%,移动 Android5.0 以上占 26%,IOS9.2 占 60% 以上,所以保守估计,至少现阶段升级HTTP/2,就已经能保证 1/3 的人能享受到HTTP/2带来的快感,而且这个数字随着时间会快速增长。
同时HTTP是支持向前兼容的,如果你的浏览器不支持HTTP/2可以降级成HTTP1.1,而且服务端也可以通过请求来判断客户端是不是支持HTTP/2,如此一来我们可以通过请求来返回HTTP/2版本的网站。这样就能尽量让更多人体验到HTTP/2的一些特性了,并且不影响其他人的使用。
以前当我们无法快速提升网络速度,无法改变一些硬件上的性能,能做的可能只有代码阶段,然而现在有了一种强劲的性能提升方案,那就是HTTP/2,也是目前性价比最高的性能提升方案了。
当然,如果你还是很纠结HTTP/2的兼容性,推荐另一个HTTP协议 SPDY,虽然HTTP/2的出现,迟早会替换掉SPDY,但是作为HTTP/2的前身,兼容性会更好,比如淘宝,天猫都已经支持SPDY了,个人觉得替换SPDY就是时间问题,所以还是推荐从HTTP/2开始吧。
不过既然提到就先看看兼容性:
SPDY不是本文重点就推荐一些资料:
SPDY介绍
nginx 配置SPDY
node-spdy 一个node库支持HTTP/2 和 SPDY
HTTP/2 is here! Goodbye SPDY? Not quite yet
nghttp2.org
很棒的介绍HTTP/2的文章
HAR Viewer 最棒的har预览服务
Jerry Qu 的博客
HTTP/2版本发布页
世人尽知Adobe之名,源于其客户端软件之研发,尤长于Photoshop,显于Flash、After Effects… 后来诸类产品悉数归于大全-Creative Suite,当今设计类软件以它为尊,所向无敌。即便后来的Creative Cloud,也只是产品运营方式的改变、数据层面的云服务化,并没有脱离传统客户端软件的实质范畴,谈不上多终端产品。
而随着云服务、HTML5、CSS3等越来越高端的移动开发技术的成熟与广泛应用,以及浏览器体验与性能的迅猛发展,Web产品与传统客户端产品的使用体验差距正在迅速缩小。也许在移动互联网的年代,整合云服务以浏览器为客户端宿主的Web应用结合APP客户端的多终端产品才是未来。风吹花动,花动花落,如此背景下,Adobe探索自家传统软件产品Web化、多终端化,以及研发新的多终端产品形式也在情理之中。而看似突然的Spark,兴许只是这个过程中的一小步。这个世界上很多事情就是这样的,你来了,我也来了,不管它该不该来,终究都已经来了!
Spark也许是Adobe真正意义上的第一款多终端产品。
它包括三部分功能:Post(海报制作)、Page(页面制作)以及Video(视频制作),三者之间其实是相互独立的,在PC端可以通过统一的Web应用入口spark.adobe.com访问使用,每部分功能对应一个独立的可视化编辑器,但各自又有单独的IOS APP客户端。看上去更像是一个可视化媒体内容创作的套装,哪天说不定会增加其他的独立功能,例如Slide(幻灯片)。
更多关于Spark的介绍,可以查看官方落地页(移动端扫描二维码,PC端点击二维码)。
鉴于Spark的IOS客户端目前只有Spark Page能够在国内下载使用,我们这次试用围绕它的Web应用(spark.adobe.com)进行,另依据大数据统计的文章篇幅长度建议,本次试用仅针对其中的一个功能-Spark Post。
迎合了用户一种或多种生活或工作习惯的产品才是好滴产品,大多数产品深谙此道。
Spark自然也不例外,看看它首页的Slogan:『分分钟将你的想法变成极具冲击力的社交图形、网页故事和动态映像』(Turn your ideas into impactful social graphics, web stories and animated videos—in minutes.)。老外的网络社交习惯离不开facebook和google,Sparks的登录界面在刻意引导用户使用Facebook或Google第三方账号登录,弱化自身的账号体系,其实就是要安利用户:小姐呀,快来用我呀,我制作的漂漂的图片、网页、视频可以一键分享给你Facebook里的好友,视频还可以保存到google家的youtube哟!。
可惜的是,Facebook和Google对于国内用户来说,在凛冬之城墙的另一边。而我们的社交习惯是微信朋友圈,是不是约等于『Spark在国内火不起』了呢。
没有FQ经验的童鞋仍可以注册adobe账号来试用Spark。
Spark Post让我想到2、3年前Bucket Labs出的一款海报制作APP - Phoster,其实核心功能差不多:基于既定的模板,组合拖放更换背景图、文字,再配合各种样式调整,最后生成美美的海报图片。Post是否强大关键要看它的模板是否好用,元素零件的组合配置是否足够灵活智能。国内也有很多具有类似功能的产品,只是不叫『Post』罢了,美图秀秀、Pitu等等。
Web版的Spark Post操作界面如下面的图示,非常简洁大气上档次,操作栏集中在头部和右侧,和PS的设计基本一致。交互傻瓜式的人性化,所以灰常容易上手,第一次用完后不直接奇经八脉全部打通进阶炉火纯青阶段的童鞋都可以自我怀疑一下天赋加点是否恰当,符文是否带错,点燃换成疾跑再试一次吧。
浸淫设计图形制作软件研发多年的Adobe,其Spark海报制作功能当然不会仅落于『界面简洁大气上档次,交互傻瓜人性化』而已。Spark Post的5大核心编辑功能兼具实用和新意。
可以快速制作适用于第三方社交平台的海报图片,instagram(默认)、facebook、twitter、pinterest。。。应用尽有。如果它开源,我倒愿意卖鹅厂一个人情把微信的900x500以及200x200两个公众号图片尺寸加进去。
模板的功能和同类软件的模板功能差不多,傻瓜实用,特别适合没有设计基础的童鞋,这里就不多提了。
Spark Post的色板功能算是同类软件中的首创。也许因为我还算年轻貌美经验眼光不够粗大,至今在美图、批图等国产软件里面还没有碰到过。利用模板,只给用户解决了基本的元素构图需求,对于缺乏设计审美基础的人来说,元素间的颜色搭配也是一大难题,而Spark Post恰好迎难而解之!
Post只提供了8个滤镜效果,少但是减少用户犹豫抉择的时间,个人感觉比Instagram的滤镜有特点,大部分滤镜效果偏向浓郁的大海报感觉。
Post能根据背景图色板智能推荐文字的配色。这个功能已经完爆许多同类应用了吧~
总的来说,Post的使用体验感觉灰常顺畅良好,简单的英文海报制作基本上可以不用找视觉设计师了哎。遗憾的是目前不支持自定义中文字体,也没有打通微信、手Q等社交渠道,本大大猜测国内同行应该很快会有对应的中文高仿版出现了吧。
有一个问题留给大家讨论,如果你是产品经理,Post该如何加入怎样的盈利手段?欢迎留言YY。
出于好奇和学习的态度,最后顺手瞄了下Spark站点涉及的一些技术点,不局限于前端。
原型设计 - Marvel
这个只是猜测的,在源码中有看到Marvel ui,也许Spark站点使用Marvel做的原型设计。这个我要试用了Marvel之后才能下定论。
Webfont - Typekit
用了Adobe Typekit的服务。
Material Design
不少地方应用了Google的Material设计风格。
资源构建-Webpack
Web worker
worker-color.js
SVG
好多图标都直接用的SVG。
今天的Spark试用手记就此为止,我是『凹凸实验室/阿尔法突击队/LV主唱大人』。请期待我们Alpha下一期的互联网新品试用手记。
]]>要了解 CSS3 逐帧动画,首先要明确什么是逐帧动画。
看一下维基百科中的定义:
定格动画,又名逐帧动画,是一种动画技术,其原理即将每帧不同的图像连续播放,从而产生动画效果。
简而言之,实现逐帧动画需要两个条件:(1)相关联的不同图像,即动画帧;(2)连续播放。
我们儿时的记忆,手翻书,他所实现的就是逐帧动画:
(图片来源:知乎)
在细聊 css3 逐帧动画之前,我们先大致了解下前端实现逐帧动画有哪些方案。
其实不外乎三种技术(视频可以实现所有类型的动画,暂不纳入):gif、JavaScript、CSS3 Animation。
前文提到,实现逐帧动画需要两个条件:(1)动画帧;(2)连续播放。
下面我们仔细自己分析下这三种技术是怎么实现上述条件的:
在触屏页中,gif 常被用来做加载动画。如《陌陌不孤独饭局》的加载动画:
gif 可以有多个动画帧,连续播放是其自身属性,是否循环也是由其本身决定的。它往往用来实现小细节动画,成本较低、使用方便。
但其缺点也是很明显的:
JS 与 CSS3,一般是将动画帧放到背景图中。
不同的是, JS 是使用脚本来控制动画的连续播放的:
background-image
background-position
来实现还是《陌陌不孤独饭局》的例子:
其中有一个伸手取饭盒的动画,一共有19帧,且在第11帧处有一个交互,将雪碧图放入背景中,通过不同的样式实现不同的 background-position
,使用 JS 改变样式名:
|
|
|
|
使用 JS 的优点是兼容性佳,交互灵活。
CSS3 实际上是使用 animation-timing-function
的阶梯函数 steps(number_of_steps, direction)
来实现逐帧动画的连续播放的。
在移动端,CSS3 Animation 兼容性良好,相对于 JS,CSS3 逐帧动画使用简单,且效率更高,因为许多优化都在浏览器底层完成。
因此在触屏页面中 CSS3 逐帧动画使用广泛,下文将对其进行详细介绍。
在触屏页面中,动画往往承担页面样式实现的角色(即不需要替换),因此我们会将图片放到元素的背景中(background-image
)。
逐帧动画有不同的动画帧,我们可以通过更改 background-image
的值实现帧的切换,但多张图片会带来多个 HTTP 请求,且不利于文件的管理。
比较合适的做法,是将所有的动画帧合并成一张雪碧图(sprite),通过改变 background-position
的值来实现动画帧切换。因此,逐帧动画也被称为“精灵动画(sprite animation)”。
以京东到家的触屏页面《年货送到家》为例:
这个动画一个有三帧,将3个动画帧合并,并放到 .p8 .page_key
的背景中:
|
|
steps 指定了一个阶梯函数,包含两个参数:
(参考自W3C)
通过W3C中的这张图片来理解 steps 的工作机制:
回到上述的例子,我们在 keyframes 中定义好每个动画帧:
|
|
然后,给他加上 animation
:
|
|
为什么第一个参数是1?
前文中提到,steps 是 animation-timing-function
的一个属性值,在 W3C 中有如下说明:
For a keyframed animation, the ‘animation-timing-function’ applies between keyframes, not over the entire animation.
也就是说,animation-timing-function
应该于两个 keyframes 之间,而非整个动画。在上面的 keyframes 中,我们已经把每个帧都写出来了,所以两个 keyframes 之间的间隔是1。
更加简便的写法?
既然说 steps 第一个参数是指函数的间隔数量,那么我们就可以把 keyframes 的计算直接交给 steps 来完成。
|
|
以上两种写法效果是等同的。
除了 steps
函数,animation-timing-function
还有两个与逐帧动画相关的属性值 step-start
与 step-end
:
step-start
等同于 steps(1,start)
:动画执行时以开始端点为开始;step-end
等同于 steps(1,end)
:动画执行时以结尾端点为开始。
|
|
我们知道,rem 的计算会存在误差,因此使用雪碧图时我们并不推荐用 rem。如果是逐帧动画的话,由于计算的误差,会出现抖动的情况。
那么在触屏页中,如何实现页面的适配?
这里小编提供一个思路:
rem
做单位;px
做单位,再结合 js
对动画部分使用 scale
进行缩放。补间动画是动画的基础形式之一,指的是人为设定动画的关键状态,也就是关键帧,而关键帧之间的过渡过程只需要由计算机处理渲染的一种动画形式。
在触屏页面中,常见的实现补间动画以下几种形式:
第一,CSS3 Animation。
通过animation(除steps()以外的时间函数)属性在每个关键帧之间插入补间动画。
第二,CSS3 Transition。
区别于animation,transition只能设定初始和结束时刻的两个关键帧状态。
第三,利用JavaScript实现动画,例如JavaScript动画库或框架,著名的TweenJS,它是CreateJS的其中一个套件。另外,在Flash业界久负盛名的GreenSock推出的GSAP(GreenSock Animation Platform)也新引入了对Javascript动画的支持。
第四,SVG 动画。
基于移动端对SVG技术的友好的支持性,利用SVG技术实现动画也是一种可行的方案。
对于利用Transition实现的动画而言,是有一定局限的。
引述阮一峰老师的文章《CSS动画简介》里的总结,
transition的优点在于简单易用,但是它有几个很大的局限。
(1)transition需要事件触发,所以没法在网页加载时自动发生。
(2)transition是一次性的,不能重复发生,除非一再触发。
(3)transition只能定义开始状态和结束状态,不能定义中间状态,也就是说只有两个状态。
(4)一条transition规则,只能定义一个属性的变化,不能涉及多个属性。
囿于这样的局限,在触屏页面中很少见到Transition动画的身影,但是并不意味着没有,譬如翻页动画的实现可以利用Javascript脚本配合transition与transform属性来实现。
案例截图来源于《京东:2015JDC燃爆事件》
对应的 Chrome Dev Tool 代码调试截图
所以,利用CSS3实现动画的重头戏都在于Animation的运用。
然而想写好CSS3 Animation动画需要花费一些力气,这是看似简单实则需要把握好细节的活儿。
可以是一张把与设计师沟通的结果加之分析输出一张动画属性分解表。
动画属性分解表示例,来源于《常见动效制作手法》
又或者是根据沟通分析规划出来的动画时间轴。
动画时间轴,来源于《CSS3动画实践》
不管是在影视动画界,还是前端动画界里遵循的都是同一套配方,追求同样的味道——「迪士尼九老」总结的十二黄金动画法则(以下简称“十二法则”),一直沿用至今、备受推崇不是没有道理的。
在一些优秀的触屏页面案例里,可以追寻到它的踪迹。
案例截图来源于《腾讯:微众银行》中的摩托车demo
作者对轮子和摩托车的处理就体现出“挤压和拉伸”的法则,带出颠簸的现实感。
小编是非常推荐大家去认真研究这个案例的,因为作者陈在真就此说明过他的这部作品就完全是遵循迪士尼动画十二原则所码出来的。
但是很可惜,案例已经下线了。
不过,你还可以欣赏到另一位对迪士尼十二法则同样有心得的大神EC的作品《拍拍小店全新上线》。
案例截图来源于《拍拍小店全新上线》
盒子的打开过程就带有一个往上展开预备动作(ANTICIPATION),并且展开撒开的碎彩纸带有慢出(SLOW OUT)的效果,拍拍小店的logo弹出符合弧形(ARCS)的运动轨迹。
除此之外弧形运动轨迹最为明显的地方就是页面切换的过程。
整个案例处处都非常生动自然、利落感满满,令人赞叹。
如果你想了解怎么去实现才能够符合十二法则,可以进一步阅读这篇文章《The Guide To CSS Animation: Principles and Examples》喔,不谢。
另外,在把控十二法则时为了动画更加自然,时间函数(animation-timing-function)的设计绝对是举足轻重的一环,因为动画可以说是一种关于时间函数的运动演变过程。要码好动画,这篇关注介绍缓动函数的《让界面动画更自然》说不定能够助你一臂之力。
Google在有关动画性能渲染优化的文章《动画 | Web Fundamentals - Google Developers》(对不起,这里有道墙)中提出建议:
避免为开销大的属性设置动画,要让每次在设置动画时必须注意保持 60fps。
那么,哪些是开销大的属性呢?
(下面是科普环节,清楚的童鞋可以跳过)。
页面渲染的一般过程为JS > CSS > 计算样式 > 布局 > 绘制 > 渲染层合并。
其中,Layout(重排)和Paint(重绘)是整个环节中最为耗时的两环,所以我们尽量避免着这两个环节。从性能方面考虑,最理想的渲染流水线是没有布局和绘制环节的,只需要做渲染层的合并即可。
那怎么知道哪些CSS属性的改变是会影响这两个环节的呢?诺,下面就是各CSS属性与其影响的环节。
截图来源于CSS Triggers,更为详细地翻墙去拿吧
在实际的应用里,最为简单的一个注意点就是,触发动画的开始不要用diaplay:none属性值,因为它会引起Layout、Paint环节,通过切换类名就已经是一种很好的办法。
|
|
还有就是,translate属性值来替换top/left/right/bottom的切换,scale属性值替换width/height,opacity属性替换display/visibility等等。
除此之外,对动画渲染的优化还有其他方式,上面贴出Google文章链接就有一系列的文章解读,小编就不卖弄按下不表了,这里顺便抛出前辈对这方面相关的总结:
总结来源于@登平登平的《H5动画60fps之路》
最后总结下要点就是
搞定!
]]>最后的最后,由于本文主推的是CSS3 Animation,其余的实现方式不在讨论范围内:P,拜拜,下次再见。
3D Transforms要怎么写?能写翻牌效果吗?能写翻书效果吗?能写出立体书的效果吗?点进来,答案都在这里面。
很多时候,仅仅将元素进行二维层面的变换显然不是人类的终点,毕竟十二维空间都可能不是极限(视频: 从一维空间到十二维空间)。
Intro to 3D Transforms 的作者David DeSandro 说,现在可是21世纪,可我们竟然还在跟三十年前的二维空间界面扯皮。所幸2011年,我们有了CSS3,我们还有了3D Transforms,真是一个值得奔走相告的大事件。
3D变换相较2D变换,坐标系中多了Z轴,也就意味着物体除了上下左右,还可以前后移动。而rotate在2D中的旋转方式,在3D中与rotateZ相当。
那么,单纯地将transform中的参数扩展出Z维度,就能实现3D效果了吗?我看见CSS3笑了。
什么是perspective?词典中翻译为观点、远景、透视图。这是一个非常抽象的概念,需要一点空间想象力。
我们先抛开这个概念,尝试使用刚才说到的知识点进行翻牌(咦)效果的尝试,聪明的你一定分分钟码出来:
|
|
|
|
但是放浏览器一看,这不对呀,为什么用3D的代码写出了2D的效果。
这个时候有请我们的perspective透视君。
学过素描的人一定对透视的概念不陌生,透视是保证素描写生真实合理的基础。
视频:透视学之一点透视法
CSS3中的perspetive在这样一个体系里就代表着元素与观者之间的距离,形象点说,就是元素3D效果的强度。CSS3中的3D效果消失点固定,变化的是观者与元素之间的距离。不过perspective数值与3D效果强度是成反比的,数值越大,元素的3D效果越不明显——2000px的视点意味着你看的是远方的物体,而100px则意味着这个物体就在你眼前。
这里有个图或许能帮助我们想象3D效果强度这个概念——
(图片来源:维基百科)
如果还是不懂,还有一个办法,就是在浏览器中边调整perspective数值边观察3D效果。
(图片来源:Intro to CSS 3D transforms - Perspective)
左图与右图的元素均绕Y轴旋转了45度,但差别很明显,右图更容易让人想到一个画面中集体开启的窗户。左图的问题就在于,每个元素的消失点各自为政,都在元素的中心点位置,而右图的消失点则统一在实线方框的中心位置。实现方法就是将元素的perspetive设置转移至元素父容器上。
讲到这里,这种简单的3D其实在很多非立体设计的触屏页中已经够用,稍微使用一点就能产生非凡的效果,比如第1期中介绍过的D Rose触屏案例。
明眼人会说,这样子可以画个正方体出来了也。我看见CSS3又笑了。
现实总是乳齿残酷
有了perspetive属性,我们顶多是一群会在纸上画素描的家伙,要想徒手造模型,还是太嫩。就拿刚才的翻牌效果来说,如果你翻滚card父容器,无论怎么翻,能看到的只有正面的卡片,因为现在的体系就是一张素描绘画,你拿着再逼真的素描画翻到背面,也是看不到真实物体的背面的对吧。超越平面3D的关隘就在于transform-style: preserve-3d的属性设置,默认值为flat,即“素描作品”。这个属性的设置旨在告诉子元素需要遵循怎样的空间体系规则。这个属性不能继承,因此只要有子元素需要设置空间体系规则,就得在父元素声明这个属性。
有了浏览器为我们处理空间体系规则,可以省不少事,不需要你担心层级问题、不需要你操心哪个元素转到哪里要消失哪个元素转到哪里要出现,嗯,小编从没自己这么干过,从没。
翻牌那是皇帝干的事儿,我们文化人得翻书。刚才的翻牌都是在方块的中部为轴进行的变换,我们把变换原点一换,就变成书页在翻了。
一本合上的书正常来说是在Y轴右侧,每一页都包含两面,也就是说一本书是由若干个翻页效果组合而成,每一页的变换原点在元素左侧。由此可以在翻牌的基础上迅速整出一个翻书demo(猛戳查看翻书demo)。
阴影的使用能让翻书效果变得更真实
(猛戳查看demo)
立体书在外国叫Pop-Up Book,满满的“Surprise!”感。这种超越传统平面书籍的阅读模式常被用于儿童书籍。
(图片来源:A Guided Tour of THE MEL BIRNKRANT COLLECTION)
要用CSS3实现这种效果,想想还有点小激动。
首先建立一个立体书规则:
书开,元素起
元素竖起速度小于等于书页开启速度
元素折叠后不可露出书边
元素层叠关系不可反自然
剩下的事也就水到渠成,无非是在每一页建立3D体系、立体元素从rotateX(90deg)转换到rotateX(0deg)的事儿。
小编曾做过一个丧心病狂的立体书触屏页,由于立体书左右两页互相关联的特性,翻牌的方式不太适合用在这里,这里使用的是另一种较为麻烦的方式——不像翻牌方式中的前后两页捆绑,这里的书页左右两页属于一个3D体系,通过translateZ值的变换控制层级关系,因为在3D体系里,z-index已被抛弃。
由于截至目前为止,CSS3的3D功能还止于炫技的阶段,安卓机与iOS的支持效果存在差异且难以调和,从上面那个案例中肉眼可见的坑就能看出,因此除了简单的3D转换,不建议在大项目中大面积使用3D深层功能。
坊间流传这这样一个传说:一旦使用3D属性,就能触发设备的硬件加速,从而使得浏览器的表现更佳。但这句话也得看情境——
想象使用GPU加速的动画就像是Vin Diesel(速度与激情的主角)开着Dominic标志性的汽车 —— Dodge Charger。它的定制900 hp引擎可以让它在一瞬间从0加速到60码。但是如果你开着它在拥挤的高速公路上又有什么用呢?这种情况下你选择的车辆Charger是正确的。但是问题是你还在一个拥堵的高速公路上。——《CSS硬件加速的好与坏》
因此千万别贪心,将3D效果数量控制在一定范围内,页面性能才是重中之重。——来自得到惨痛教训的小编的忠告
Intro to CSS3 3D transforms by David DeSandro ——详尽又新鲜的3D Transformers手册,包含许多一看就懂的小Demo,妈妈再也不用担心我的3D了。
Perspective (graphical)——对透视学还一知半解的可以看看维基的详细说明。
Unfolding the Box Model: Exploring CSS 3D Transforms by Chris Ruppel ——非常赞的3D Transforms介绍,从2D到3D过渡,启动联想学习法,一看就明白,就怕你不看。
CSS硬件加速的好与坏——很多事情都不是一两句能讲清楚的,但是只要深入了解原理,一两句都不用讲就清楚了。
拇指期刊·第8期·CSS3动画开发手册
《CSS3动画之补间动画》
《CSS3动画之逐帧动画》
《CSS3动画之3D动画》
目前,SPM 还处于早期阶段,现在仅支持 OS X 和 Linux 系统,尚不支持 iOS, watchOS 以及 tvOS 平台,但未来很大希望会支持上述平台。
本文使用苹果官方的例子来讲解 SPM 的使用,希望这篇文章能够帮到有意进行尝试的人! :)
这个章节我们先来了解一下关于 SPM 功能的一些基本概念。
在 Swift 中我们使用模块来管理代码,每个模块指定一个命名空间并强制指定模块外哪些部分的代码是可以被访问控制的。
一个程序可以将它所有代码聚合在一个模块中,也可以将它作为依赖关系导入到其他模块。除了少量系统提供的模块,像 OS X 中的 Darwin 或者 Linux 中的 Glibc 等的大多数依赖需要代码被下载或者内置才能被使用。
当你将编写的解决特定问题的代码独立成一个模块时,这段代码可以在其他情况下被重新利用。例如,一个模块提供了发起网络请求的功能,在一个照片分享的 app 或者 一个天气的 app 里它都是可以使用的。使用模块可以让你的代码建立在其他开发者的代码之上,而不是你自己去重复实现相同的功能。
一个包由 Swift 源文件和一个清单文件组成。这个清单文件称为 Package.swift,定义包名或者它的内容使用 PackageDescription
模块。
一个包有一个或者多个目标,每个目标指定一个产品并且可能声明一个或者多个依赖。
一个目标可能构建一个库或者一个可执行文件作为其产品。库是包含可以被其他 Swift 代码导入的模块。可执行文件是一段可以被操作系统运行的程序。
目标依赖是指包中代码必须添加的模块。依赖由包资源的绝对或者相对 URL 和一些可以被使用的包的版本要求所组成。包管理器的作用是通过自动为工程下载和编译所有依赖的过程中,减少协调的成本。这是一个递归的过程:依赖能有自己的依赖,其中每一个也可以具有依赖,形成了一个依赖相关图。包管理器下载和编译所需要满足整个依赖相关图的一切。
下一个章节可能需要你了解一些 Swift 的基本知识,如果你是 Swift 的新手,可以先进这个传送门学习一下 Swift 的基本知识: Swift 入门教程
接下来,我们还需要了解一下开源 Swift 的一些入门知识。
由于本文重点不在这里,所以我们就简单介绍下如何下载和安装 Swift 以及如何使用编译系统, 关于 使用 REPL 和 LLDB 调试器的内容具体可以参阅官方文档 使用 REPL 和 使用 LLDB 调试器
刚开始使用 Swift 需要下载并安装编译器和其他必备组件。进入到 https://swift.org/download/#releases 按目标平台的说明进行。
下载的时候需要注意下,由于 SPM 还不是特别的成熟,我们在这个教程中将不使用 Releases 的 Swift,我们需要下载开发版的 Snapshots。如下图所示,我们使用日期为 April 12, 2016 的 Snapshots,另外千万不要点击下载 Debugging Symbols 或者 Signature 的安装包,因为那样会缺失一些内置的依赖库!!
下载完成后点击按步骤安装就可以了!
在 OS X 上下载工具链默认的地址是 /Library/Developer/Toolchains
。接着我们输入以下命令导出编译路径
|
|
首先需要安装 clang :
|
|
如果你在 Linux 上安装的 Swift 工具链在系统根目录以外的目录,你需要使用你安装 Swift 的实际路径来运行下面的命令:
|
|
导出路径之后,你可以通过输入 swift
命令并传入 --version
标志来校验你是否运行了 Swift 的预期版本
|
|
在版本号的后缀-dev
用来表明它是一个开发的编译,而不是一个发布的版本。
Swift 编译系统为编译库、可执行文件和在不同工程之间共享代码提供了基本的约定。
创建一个新的 Swift 包,首先创建并进入到一个新的目录命名为 Hello
:
|
|
每个包在其根目录下都必须拥有一个命名为 Package.swift
清单文件。如果清单文件为空,那包管理器将会使用常规默认的方式来编译包。创建一个空的清空文件使用命令:
|
|
当使用默认方式时,包管理器预计将包含在 Sources/
子目录下的所有源代码。创建方式:
|
|
默认方式下,目录中包含一个文件称为 main.swift
将会将文件编译成与包名称相同的二进制可执行文件。
在这个例子中,包将生成一个可以输出 Hello, world!
的可执行文件命名为 Hello
。
在 Sources/
目录下创建一个命名为 main.swift
的文件,并使用你喜欢的任意一种编辑器输入如下代码:
|
|
返回到 Hello
目录中,通过运行 swift build
命令来编译包:
|
|
当命令完成之后,编译产品将会出现在 .build
目录中。通过如下命令运行 Hello
程序:
|
|
下一步,让我们在新的资源文件里定义一个新的方法 sayHello(_:)
然后直接用 print(_:)
替换执行调用的内容。
在 Sources/
目录下创建一个新文件命名为 Greeter.swift
然后输入如下代码:
|
|
sayHello(_:)
方法带一个单一的字符串参数,然后在前面打印一个 “Hello”,后面跟着函数参数单词 “World”。
现在打开 main.swift
, 然后替换原来的内容为下面代码:
|
|
跟之前的硬编码不同,main.swift
现在从命令行参数中读取。替代之前直接调用 print(_:)
, main.swift
现在调用 sayHello(_:)
方法。因为这个方法是 Hello
模块的一部分,所以不需要使用到 import
语句。
运行 swift build
并尝试 Hello
的新版本:
|
|
目前为止,你已经能够运用开源 Swift 来运行一些你想要的程序了。接下来我们就可以进入正题开始入手 SPM 。
在 开源 Swift 入门 章节中,我们简单地学会了编译一个 “Hello world!” 程序。
为了了解 SPM 究竟能够做什么,我们来看一下下面这个由4个独立的包组成的例子:
O2PlayingCard
, O2Suit
, O2Rank
, 3个类型。shuffle()
和 shuffleInPlace()
方法实现的扩展。O2Deck
类型对 O2PlayingCard
值得数据进行洗牌和抽牌。O2DeckOfPlayingCards
进行洗牌和抽出前10个卡片的可执行文件。你可以从 O2Dealer from GitHub 编译并运行完整例子,然后运行如下命令:
123 $ cd O2Dealer$ swift build$ .build/debug/O2Dealer
我们将从创建一个代表一副标准的52张扑克牌的模块开始。O2PlayingCard
模块定义了 由 O2Suit
枚举值(Clubs, Diamonds, Hearts, spades)和 O2Rank
枚举值(Ace, Two, Three, …, Jack, Queen, King)组成的 O2PlayingCard
类。各个类的核心代码如下:
|
|
一般来说, 一个包包括任何位于 Sources/
的源文件。
|
|
由于 O2PlayingCard
模块并不会生成可执行文件,这里应该称为库。 库表示被编译成一个可以被其他包导入的模块的包。默认情况下,库模块公开所有位于 Sources/
目录下源代码中声明的公共类型和方法。
运行 swift build
开始启动 Swift 编译的过程。如果一切进行顺利,将会在 .build/debug
目录下生成 O2PlayingCard.build
目录。
接下来我们在 Package.swift
文件中定义包名,代码如下:
|
|
然后我们只要将 O2PlayingCard
提交到 Github 上,并且给他发布一个 Release 版本即可完成该库包。这里可以自己手动添加一个 .gitignore
文件,忽略掉 /.build
,因为我们的包是不需要包括生成的编译结果的内容的。
完整
O2PlayingCard
代码可以在 https://github.com/marklin2012/O2PlayingCard.git 查看。
下一个即将编译的模块是 O2FisherYates
。跟之前 O2PlayingCard
有所不同,该模块没有定义新的类。取而代之的是该模块拓展了一个已经存在的特殊的 CollectionType
和 MutableCollectionType
接口协议, 用来添加 shuffle()
方法和对应的 shuffleInPlace()
方法。
shuffleInPlace()
方法的实现使用了 Fisher-Yates 经典洗牌算法来随机交换集合中的元素。由于 Swift 标准库没有提供一个随机数生成方法,该方法需要调用一个从系统模块导入的函数。为了使这个方法能够同时适配 OSX 和 Linux, 代码将要用到编译配置语句。
在 OS X 中,系统模块是 Darwin
, 提供的函数是 arc4random_uniform(_:)
。在 Linux 中, 系统模块是 Glibc
, 提供的函数是 random()
:
|
|
剩下的步骤和前面的类似,编译通过后上传到 Github , 发布 Release 版本。
完整的
O2FisherYates
代码可以在如下地址找到 https://github.com/marklin2012/O2FisherYates.git。
O2DeckOfPlayingCards
包把前两个包聚合到一起:它定义了一个在 O2PlayingCard
数组中使用 O2FisherYates
的 shuffle()
方法的 Deck
类型。
为了使用 O2FisherYates
和 O2PlayingCards
模块, O2DeckOfPlayingCards
包必须在 Package.Swift
清单中将上述模块声明为依赖。
|
|
每个依赖都需要制定一个源 URL 和版本号。源 URL 是指允许当前用户解析到对应的 Git 仓库。版本号遵循 语义化版本号 2.0.0 的约定,用来决定检出或者使用哪个 Git 标签版本来建立依赖。对于 FisherYates
和 PlayingCard
这两个依赖来说, 最新的将要被使用的主版本号为 1
(例如: 1.0.0)。
当你运行 swift build
命令时,包管理器将会下载所有的依赖, 并将他们编译成静态库,再把它们链接到包模块中。这样将会使 O2DeckOfPlayingCards
可以访问依赖 import
语句的模块的公共成员。
你可以看到这些资源被下载到你工程根目录的 Packages
目录下,并且会生成编译产品在你工程根目录的 .build
目录下。
123456789101112131415161718192021 O2DeckOfPlayingcards├── .build│ └── debug│ ├── O2DeckOfPlayingCards.build│ ├── O2DeckOfPlayingCards.swiftdoc│ ├── O2DeckOfPlayingCards.swiftmodule│ ├── O2FisherYates.build│ ├── O2FisherYates.swiftdoc│ ├── O2FisherYates.swiftmodule│ ├── O2PlayingCard.build│ ├── O2PlayingCard.swiftdoc│ └── O2PlayingCard.swiftmodule└── Packages└── O2FisherYates-1.0.0│ ├── Package.swift│ ├── README.md│ └── Sources└── O2Playingcard-1.0.1├── Package.swift├── README.md└── Sources
Packages
目录包含了被复制的包依赖的所有仓库。这样将使你能修改源代码并直接推送这些修改到他们的源,而不需要再对每个包在单独进行复制。剩下的步骤参考前面内容。
完整的
O2DeckOfPlayingCards
包可以在如下地址查看: https://github.com/marklin2012/O2DeckOfPlayingCards.git。
到这一步,你已经可以建立 O2Dealer
模块了。 O2Dealer
模块依赖于 O2DeckOfPlayingCards
包,而该模块又依赖于 O2PlayingCard
和 O2FisherYates
包。然而,由于 SPM 会自动解决子依赖,所以你需要声明 O2DeckOfPlayingCards
包作为依赖即可。
|
|
对于在代码中引用任意类型, Swift 需要在源文件中导入模块。在 O2Dealer
模块的 main.swift
文件中, O2DeckOfPlayingCards
中的 O2Deck
类型和 O2PlayingCard
的 O2PlayingCard
类型是引用。虽然 O2Deck
类型中的 shuffle()
方法使用在 O2FisherYates
模块内,但是 O2FisherYates
模块并不需要在 main.swift
中被导入。
核心代码如下:
|
|
一般来说,一个包在根目录中包含一个命名为 main.swift
的文件生成一个可执行文件。
运行 swift build
命令开始用 Swift 编译来生成可以被运行在 .build/debug
目录下的可执行文件 O2Dealer
。
注:这里测试的时候会报错没有子模块,需要再运行
swift build
就可以通过了。原因不知道是不是因为没有加证书,这里我们先编译,后续再查找原因。
|
|
完整关于
O2Dealer
代码可以在如下地址查阅 https://github.com/marklin2012/O2Dealer.git。
更多关于 SPM 信息查看文档: Swift package Manager project on GitHub
到此,我们已经完整地对 SPM 有了开发的经验了!如果你有过 NodeJS 的开发经历,你也许会有似曾相似的感觉,但我们用的可是 Swift!
Swift 是一门先进的语言, SPM 的社区也在不断地完善中。在 Swift 开源之后,我们很容易可以看到它的潜力,看来掌握这门语言必将是一个大趋势。而实践完这篇文章,我们已经快人一步啦!
]]>其中比较复杂的是multi/formdata类型的,可混合提交文本数据与二进制数据(图片、zip等),下面主要介绍一下这种编码的规律以及在js以及nodejs的构造的方法。
以下是一个formdata的格式(chrome中):
分析一下这种编码格式的特点:
1、request header里声明Content-Type,并且在其后加上数据分界符:boundary,即:Content-Type:multipart/form-data; boundary=—-WebKitFormBoundary0yB3cIYoABZUBzEm。
boundary的字符应该是随机的,防止提交的数据里有相同字符而影响服务器的数据解析。
2、request body的部分,规律可看下面的图解:
可以看出:
以上介绍完了multi/formdata的编码结构,下面说说在js里面是怎么构造的这样的一个请求的。
有同学问了,为啥不用new FormData()直接构造数据呢?嗯。。。
1)第一种情况比较简单,就是表单字段只有文本(数字)的情况。我们可以按照上面的结构,进行简单的字符串拼接,并设置好header,最后xhr.send(form_str);
2)这种是混合了文本与二进制数据的情况,即上图表示的情况,这时候需要结合xhr2、ArrayBuffer、FileReader等api。
请看以下代码以及注释:
在上面的代码里,由于暂时没能在官方文档找到拷贝或concat整段buffer的方法,所以合并字符数据与图片数据的方法并不十分高效,需要转为普通数组,合并数据后再次转为typed array以获得buffer数据。经过一番转换后计算效率在大文件下或者手机端这种方法的效率还待验证,因为处理普通数组会比直接操作二进制慢不少。
注意,以上代码并不兼容发送中文的情况,大概就是思路用合适字节长度去存储经过encodeURIComponent(或charCode)后的字符,字符code与二进制编码之间的转换这不在本文的讨论范围,有兴趣的同学可参考文后的参考资料。
FormData的api使我们可以处理blob(buffer)、text等数据的提交,平常开发已足够。但是通过了解formdata的数据底层组装方式,或许有助于我们在浏览器端更灵活的处理一些二进制数据、提供一些新思路。
最后再附上nodejs版的构造方式的主要代码:
有兴趣的同学可以clone这个git地址获取代码:https://github.com/cos2004/formrequest_app.git
代码介绍:
首先npm install,然后npm start启动服务器
http://127.0.0.1:3000/ ,可演示js版的提交,请选择png格式文件
http://127.0.0.1:3000/formdata 演示nodejs版提交,在terminal可以看到输出结果
http://127.0.0.1:3000/submit 表单数据提交接口
reduce() 方法接收一个函数作为累加器,数组中的每个值(从左到右)开始缩减,最终为一个值,是ES5中新增的又一个数组逐项处理方法,那reduce方法跟foreach、map等数组方法又有啥区别呢。
arr.reduce(callback[, initialValue]) —More From MDN
抛开上面晦涩难懂的语法介绍,下面我们直接上实例:
数组 arr = [1,2,3,4] 求数组的和
|
|
|
|
|
|
没错,reduce 专为累加这种操作而设计,为累加这类操作而设计的参数,十分方便。
那么问题来了,reduce方法的参数到底有哪些用法呢?
|
|
探查Arguments数组得到:
|
|
|
|
|
|
可以看出传入initialValue 会多递归一次,而initialValue的值的作用大家应该也明了了:为累加等操作传入起始值(额外的加值)。
而callbackfn中的四个参数也可以在debugger的动态变化中查看出具有的特性。
那么利用reduce方法还可以做哪些事情呢?
|
|
求乘积
求最大值
另外,如果你在NodeJs的环境中使用reduce几乎没有任何问题,但是如果你在客户端的浏览器使用reduce方法,那可能就要兼容IE8以下的浏览器了。
当然,我们可以引入库来解决这个问题,有趣的是,在jQuery官网中,有一个对reduce长达8年的讨论Add jQuery.reduce() 而Jquery官方至今仍然坚持reduce适合作为Jquery的一个插件存在。即使后来ES5中加入了reduce的实现。
This won’t be useful in the core, it can always be included in a plugin. —- by flesler
因此,如果在Jquery中使用reduce方法,需引入reduce插件jQuery-reduce-plugin
underscorejs 中早已实现reduce方法 http://underscorejs.org/#reduce
至此,我们可以很形象的归纳出来forEach、map以及reduce的不同点:
reduce方法在数组对象中的运用:
搬砖工小王拿到了这样的数据格式:
var arr = [ {name: 'brick1'}, {name: 'brick2'}, {name: 'brick3'} ]
希望得到这样的数据格式:'brick1, brick2 & brick3'
当然数组异常流:[ {name: ‘brick1’} ] 和 空数组传入得到'brick1'
和 空
|
|
返回结果:brick11, brick12 & brick13
此时进一步延伸如果原来有一堆砖已经堆好,传入 initialValue值:
|
|
返回结果:brick1, brick2, brick3, brick11, brick12 & brick13
-EOF-
]]> CodeWars上有这么个题目: 《Don't rely on luck》
的奇葩题目,先看一下题目描述:
The test fixture I use for this kata is pre-populated.
It will compare your guess to a random number generated in JavaScript by:Math.floor(Math.random() * 100 + 1)
You can pass by relying on luck or skill but try not to rely on luck.
“The power to define the situation is the ultimate power.” - Jerry Rubin
Good luck!
|
|
出题者大约是想让每次随机数与你guess到的数字相同,So ‘Don’t rely on luck’.
这是一个大坑,原本读完题后百思不得其解,当UnSolution后,心里真的是万马奔腾。
This is solution
或者
是的,你没看错。就是将Math.random 或者 Math.floor 重写。
看到有个老外估计也是UnLock solution后心里愤恨写了这么一个答案:
然而这道题目明显是想让你了解伪随机数产生的原理以及种子随机数在JavaScript实现方法。
代码虐我千百遍,我待代码如初恋。
在C或者Ruby中都有低层的重置seed的方法,比如C中的seed值当计算机正常开机后,这个种子的值是定了的,C提供了 srand()
函数,它的原形是void srand( int a)
。而Ruby中默认是根据系统时间、进程id、一个可升序的数字来生成随机数种子。然而JavaScript中并没有类似上面底层语言提供的seedRandomR函数,我的第一反应就是自己实现一个,也就是重写Math.random()
方法。
在开始我的重写
Math.random()
方法前还是需要做一些知识储备工作:
实现随机函数的方法很多,如斐波那契法、线性同余法、梅森旋转算法Mersenne twister, 现在最好的随机数产生算法是梅森旋转算法Mersenne twister。维基百科
chrome v8 引擎使用的随机函数算法:(每个浏览器厂商实现Math.random并不相同)
From: chrome v8引擎随机数实现方法
可以看得出V8引擎中的seed 值是通过MathImul
方法创造出来的。所以并没有为我们预留开发者传入seed值的参数。
那我们要想实时掌握每次随机产生的值相同(预留seed参数),只能自己重写Math.random
方法了。 ##### 比较经典的获取随机数的写法:
打开Node终端跑一下:
seed值始终为1时始终得到:0.709848078965
至此我们可以探索种子随机数的用途:
比如我们在开发京东的H5活动页面的小游戏时,可能需要随机产生一些背景、随机掉落道具、小怪物;当用户中途退出(微信不小心右滑,一定有不少人被这个恶心到[当然现在已经优化]),当用户重新打开小游戏时,用户整体进度、积分我们可以很容易记录到本地或者存储到微信ID建立的存储机制中从而得以恢复,但是随机产生的场景、随机掉落道具、小怪物等并不是那么容易存储恢复,重新获取场景、小怪物那未免用户体验太差!!!把画布上的所有物件、怪物属性全部存储下来更是没有必要。此时我们就可以利用我们重写的
Math.random()
(此时可不重写内置方法,可另起别名)。只要我们保存下来一个随机数seed值,利用seed值来恢复所有的场景就好了。
另外 David Bau提供了一个seedrandom库以供开发者调用:
|
|
|
|
|
|
当然不管我们如何优化种子随机数,产生的随机数都是伪随机数也就是假的随机数,它是根据某种已知的方法获取随机数,本质上已经去除了真正随机的可能。这种方法一旦重复,则获取到的随机数也会重复。
那么计算机能否产生真的随机数呢?
GitHub上有一个叫RealRand 的项目。包装了3个真正的随机数服务生成服务:(基于Ruby)
跑偏了,再回到JavaScript中种子随机数的话题:
可以看出来:随机数种子的存在可以让随机数在开发者手中实现可控。而实现随机数“种子的随机”可以来实现H5小游戏道具的随机掉落的可控性更甚至真随机数的可能性(像上文中提到的通过大气噪声、放射性衰变等物理随机坏境来产生随机数种子)。
-EOF-
]]>有一天阿婆主接到了一个触屏页需求。
让我们上上下下打量打量这个需求。
loading跟正片背景完全不一样!
整个需求还分成三个阶段:导语部分(开片)、职位介绍部分(正片)、结束部分(字幕);每个阶段之间的元素少则三四个,多则四五个,如果一个一个地transition,想想都替浏览器觉得卡。
这个时候,一个童年的久远记忆浮出水面——好像有个类似舞台聚光灯的转场来着……
于是阿婆主开始了漫长的考据征途。
不知大家注意到没有,凡是有presentation的地方就有转场的存在。
利器PPT以及更为高端的Keynote都为我们提供了现成的五花八门的转场效果。
再往人生前半段推,小学老师偶尔会用黑白幻灯片来展示一些图案内容,即便是这种形式也有着其独特的转场效果。
最原始的知识海洋黑板,你可能会说,这可没有转场效果了吧?其实,擦黑板也是一种转场呀。
转场这个概念最先出现于影视界。在影像由动态照片的简单功能一直发展到现在可商业可艺术的多元电影行业的过程中,经历了数代影视人的探索与创新,才有了现在各式各样的转场形式的沉淀。
维基百科中对转场的定义——
影片转场是一种用于影片、视频剪辑后期制作的技术,用于连接场景或镜头。常见的转场方式是生切(normal cut)。大多数影片会选择性地使用其他方式的转场,以便更好地传达一种情绪、指明一个时间段,或者将一个故事段落化。转场方式包含溶解(dissolves)、L型剪辑(L cuts)、渐出(fades,常见方式是渐渐黑屏)、对称剪辑(match cuts)、消除(wipes)。
所有的这些转场方式都能在PPT中一一映射。
能和PPT一一映射的还有一种东西,那就是就是九十年代的MV……
仔细想想,浮夸的消除转场似乎大多数是在presentation的场合见到,不过最近除了电子幻灯片这样的载体,又增加了一种常见载体——触屏页。没错,触屏页也是一种presentation的途径。
在触屏页飞速发展的这几年,业界的能人挖掘出不少宝藏,转场效果也是乱花渐欲迷人眼,包括但不止于生切,滑动,覆盖,元素过渡。其中的覆盖转场,就是上面所说的消除转场的一种。
在遍历了维基中罗列的转场类型之后,初步认定阿婆主脑内的转场属于消除转场。
再次请出我们的维基君——
一个镜头从画面的一头消除到画面的另一头,或者以一个特殊的形状消除,显示下一个镜头的方式叫做消除转场。如果消除的方式是从画面的两侧往中心轴或从中心往两侧消除,这种消除称为仓门消除(a barn door wipe),因为效果类似打开或者关闭仓门。
看看维基君给出的浮夸例子
眼见着胜利就在前方,这就是我要的转场方式,只是形状换成了圆形。
继续往下阅读,哎我去,众里寻他千百度啊——
出来吧,维基神兽——
虹膜转场是消除转场的一种,画面中一个逐渐变大或变小的圆圈负责画面间的切换。常见于动画短片,比如兔八哥(头一回知道原来八哥叫Looney Tunes)啊、(阿婆主没听说过的)Merrie Melodies等动画系列啊,用于表示故事结束。在这种情景下,虹膜消除可以用于聚焦在某个特定的点,还可以作为一个道具让人物来个结束语,或者作为第四面墙任由人物钻进钻出,以及其他。
消除转场借鉴了舞台表演中幕布的转场形式,并利用电脑剪辑比较自由的优势在幕布上增加了一些形状。在视频这个特殊的媒介中,屏幕被看作银幕世界中的第四面墙。
有些影视作品中的角色会与观众对话,称为打破第四面墙,这种做法出现在了美剧《纸牌屋》中,鼻祖是莎翁的哈姆莱特。
触屏页作为一种类幻灯片的展示手段,市面上的作品中也几乎涵盖了上面提到的转场方式,除了那些较为高级的需要与音频配合紧密的转场方式。但虹膜转场却很少见,嗯,阿婆主此生未见。
虽说这种简陋的转场在现代影视作品中已经销声匿迹,但并不妨碍它在触屏页中的应用。使用这种转场,说好听了叫复古,说不好听了叫偷懒。因为一旦使用了这中一黑到底的转场,你完全不用考虑前后两屏之间有何视觉联系,这个元素怎么消失那个元素怎么出现,简直是懒癌患者的福音。
根据虹膜转场的历史特点与视觉特点,虹膜转场的应用场景如下:
都说授人以鱼不如授人以渔,接下来咱们就先来探讨一下实现虹膜消除转场的思路(明明是代码写得烂不敢放出来)。
在各大制图工具中,如果你是高级玩家,你一定对这个图标不陌生。
这个图标在大部分的制图软件中都是一个功能,减去上层形状。
这个功能是实现虹膜消除转场乃至所有消除转场的基本思路。顺着这条路,可以设想出无数种实现技术:
显然,Canvas的globalCompositeOperation属性最接近我们的需求。
这个属性拥有11个值,为矢量图绘制打开了无数扇大门。
有了这样一个方法,搞出虹膜消除转场简直是分分钟的事。
首先我们来撸一撸我们需要哪些值来进行一个虹膜消除转场的绘制。
首先是画布尺寸。这就不用多说了,没有画布再大的虹膜也什么都不是。
接着确定虹膜的圆心位置。这直接决定着转场中圆圈的最大半径值。聪明的你一定知道这个值怎么算(勾股定理)。
然后就是虹膜的收缩速度。
有了这几个值,就可以叫来RAF一起搞一搞了。
整体代码思路就是:
由于触屏页的转场相比动画短片中出现的频率要高得多,且触屏页基本没有黑屏退场的场景需求,因此单纯的放大或者缩小圆圈显然不太现实。
我们可以在调用绘制画布的方法中加入一个标志符,用于判定是否还有下一个转场,如果有,则设为1,并在第4个参数传入下一个转场的圆心位置以及计算好的最大半径(避免每次迭代都计算一次)。
具体代码参见https://github.com/lyxuncle/iriswipe
好啦,健康又卫生的虹膜消除转场就完成啦,放到需求里就是这样哒~
最后,按照惯例要装逼,放上考据过程中看到的一句话,干了这杯鸡汤:
Fades, wipes, cuts and dissolves are always great tools for telling stories on video, but true video artists use editing techniques that cannot be selected from a pull-down menu.
by Don Dollins in Computer Editing: Split Man
Transition,维基百科
Wipe (transition)),维基百科
COMPUTER EDITING: SPLIT MAN,Don Collins
J-Cuts & L-Cuts,Cameron Christopher
Compositing and clipping,MDN
CanvasRenderingContext2D.globalCompositeOperation,MDN
记得前百度工程师张云龙说过,页面前端优化问题绝对不仅仅是为页面提速的问题,更是工程的问题,有兴趣的同学可以阅读《前端工程与性能优化》。里面有提到根据雅虎14条优化原则,《高性能网站建设指南》以及《高性能网站建设进阶指南》中提到的优化点梳理出来的优化方向:
张云龙先生提及到的优化方向从提出到现在虽已相隔两年时间,前端技术也在飞速发展,但其提到的工程化思想仍是前端优化的一个大方向,亦有很大的指导意义。
当然这是另外更大的话题了,这篇文章并不是将本次的优化点按照萌妹子暖暖写的《前端优化不完全指南》一一列出(当然并没有那么多,前端优化永远写不完,写不完:- O),而是重点结合项目总结这一次优化中如何寻找优化点以及收益比较大的常见方法,希望可以对遇到相似问题的同学有帮助,前端大神可轻轻淡笑而过。
我们所说的高清图一般是指至少具有 Retina 屏级别精度的图片,就是平时所说的『2x』图。对于高清图的适配,一般会根据图片特点以及项目实际情况去制定适配方案。
纯色图一般应用到装饰性的小 icon,如侧导航的标题 icon:
此类图标由单色组成,可以根据一定的绘制规则制成 iconfont 图标,iconfont 的图标具有矢量性,其大小和颜色可以都可以通过样式来控制。
新版首页出现了 33 个单色图标,这些图标复用性很强,同一个图标在不同页面都有出现,而且同一个图标还有不一样的尺寸,如果用传统方法做成图片的话图片数量会很多,即使全部合并成 sprite 图,图片的 K 数也会很大,而且后期如果有修改的话还得重新合并 sprite 图,因此这次首页改版所有的纯色图标的高清适配全部使用 iconfont 图标:
改版所用到的 ICONFONT 图标生成以及管理选择了『阿里巴巴矢量图标库』线上服务,在上面通过上传图标的 SVG 文件生成对应的字体文件,还可以根据图标分项目管理:
图标生成后,该服务还会自动打包好所需文件,并制成 DEMO 网页,供本地预览查找图标对应的字体编码以及使用方法:
相信前端的同学很早就使用过 ICONFONT 服务,笔者衷心感谢提供 ICONFONT 服务的 THX 组织,除了 ICONFONT 服务,THX 还提供了不少业界良心的前端精品工具服务,感谢他们为业界作出的贡献。
非纯色图通常用『2X』图适配,适配方案可以有很多选择:媒体查询、srcset属性、image-set属性、脚本控制。
媒体查询、srcset属性 和 image-set属性成功匹配的基本是高端浏览器,兼容性略差,脚本控制兼容性更佳,项目具体用哪一种要看『国情』了:
上图是我国PC端操作系统市场份额的大概分布情况,可以看出 95% 以上的用户都是使用 Windows 系统的,使用 Windows 系统的用户设备屏幕大部分都是普清屏,而使用高清屏的用户基本都是使用 Mac OS 系统,Mac OS 系统的浏览器又以『高富帅』Chrome 和 Safari 为主,因此只考虑适配 Mac OS 设备,最终选择比媒体查询更为方便的『srcset属性』和『image-set属性』方案:内容图使用 srcset 属性适配,背景图使用 image-set 属性适配高清图:
|
|
|
|
新版京东云出现了很多设计类字体,也就是我们平时所说的非系统字体,如新版京东云首页版块标题用的字体 —-『方正兰亭超细黑体』
对于设计类字体,前端和视觉会达成共识不会大面积使用,因为该类字体的实现只能用图片或通过样式 @font-face
属性去实现:
图片方案:虽然可以高精度还原,兼容性强,但是每改动一处地方都需要换图,不方便维护,内容扩展性差,而且如果要适配高清设备,又得多一套图。
@font-face
属性方案:字体具有矢量性,高清设备可以轻松适配,内容扩展性强,但是不同的浏览器存在渲染的差异,兼容性略弱,@font-face
字体文件大小一般又是 M 级别,会不同程度影响页面加载体验。
如果非系统字体应用的地方只有几个标题,而且不常改动的话,用图片方案较优,但是新版京东云在其它频道的首页也会应用到,内容较多,而且要适配高清图,所以图片方案并不适用,@font-face
属性方案更合适。
针对 『@font-face
属性方案』文件体积大和浏览器渲染差异的两个不足之处,采取了一个折中的方案,也就是浏览器的渲染差异在视觉可以接受的范围下,只抽取要用到的字体生成体积相对较小的字体文件。
Junmer 出品的 Fontmin 工具可以大大满足这个需求,只需要将用到了字体源以及需要生成的文字内容加入到工具中,就可以生成相应的字体文件:
原来 2MB 的字体
生成的字体文件只有 11KB,字体文件体积减少达到了 99%
旧版首页加载的时候,一共有40个请求,其中图片的请求就有 31 个,占总请求数的 77%
有些可以合并的图片并没有做处理:
其中至少有 11 张图片是可以合并成一张图片的,也就是至少多了 27% 的额外请求数
首屏的图片资源加载了 31个,但其可见的图片只有 2 张,加载了 100% 的图片资源,首屏图片资源利用率只有 6%
只要用户没有完全浏览完网页就跳到其它页面的话,都会造成资源浪费。
首屏耗时较长的大图加载过程并没有做 Loading占位图 提示,有机会出现轮播图区域空白时间过长:
上图显示页面加载 1.8s 后,Banner 背景图还是没有出来,虽然网速飞快的用户有可能不出现这种情况,但是不排除网络慢的用户会碰上。
除此之外,图片加载失败的时候也没有做容错处理,就有机会出现图片加载失败的系统默认图标样式,会影响页面的美观性:
虽然新版首页的图片资源的排版和内容有所不同,但至少可以针对旧版额外请求数多、资源浪费、加载体验差这三个方向去改进。
减少图片额外请求数,收益比较明显的一般有三个方法:图片合并、iconfont 图标、Base64,三个方法都有各自的优缺点:
图片合并
优点:兼容性强可缓存可提前加载多态图可提升图片加载显示体验
缺点:维护性差、合并图片类型以及大小控制限制高、有可能造成资源浪费
适合:修改更新少的常驻型低色位的装饰小图
Iconfont
优点:可缓存矢量性可控性强
缺点:存在浏览器渲染差异性、只能纯色、文件体积略大
适合:纯色图标
Base64
优点:无额外请求
缺点:不可缓存、兼容性差、代码冗余、可读性差、维护不便、CPU内存耗损大
适合:体积小复用率低的背景装饰图标
新版首页一共有 70 个图片资源,其中有 49 个是纯色图标,16 个是低色位非纯色图,5 个是高色位图。
49 个纯色图标全部使用了 Iconfont 方法处理,13 个低色位非纯色图使用了合并方法,一共有 62 个图片做了减少额外请求处理,最终图片资源请求数一共只有 14 个,其中纯色图的请求数占 2 个,低色位非纯色图请求数占 6 个,图片总请求数减少了 80% ,图片合并和 Iconfont 的额外请求处理率分别达到了 56% 和 96%
可以看到 Iconfont 的额外请求处理率相当出色,因为适合应用他的对象特点比较简单,而图片合并会受到合并图片的格式、资源分布、模块分布等情况影响,其额外请求处理率会相对低于 Iconfont。
我们可以得到一个优化图片额外请求的小结论:纯色图标优先考虑 Iconfont,低色位非纯色图片根据项目实际需要来做合并优化,Base64非特殊图片不使用
新版首页需要加载的图片资源一共有 14个,其中首屏的图片资源有 8 个,可见图片有 5 个,如果不作处理,那么首屏图片资源的利用率只有 35%
如果进行资源按需加载,在非首屏的图片资源实行懒加载,将轮播图不可见的两张图片做触发加载处理,这样首屏的加载图片资源只有 8 个,首屏图片资源利用率则可达到 60%,提高了 70% 的图片资源利用率,资源按需加载不失为一种避免资源浪费的最挂实践方法
图片加载的时间长短由很多因素决定,如服务器响应时间、用户所用网络带宽、图片大小等,但无论是哪一种情况,总有一个等待的过程,在这过程总会有一个空白时间,特别是占屏面积比较大的首屏轮播大图和采取懒加载的图片,即使图片空白时间很短,用户也会有不同程度的感知,会给用户带来一种唐突或漫长等待的感觉,如果加载过程给图片加上体积比较小的占位提示图,则会让用户有一个图片加载预知,当图片加载完成后再呈现给用户看,这样用户在图片加载过程中看到的都是完整的图片
当图片加载失败的时候,展示占位图,避免系统默认的图片加载失败图标出现
渐进增强是指从最基本的功能出发,在保证系统在任何环境中的可用性基础上,逐步增加功能,提高用户体验,
出现在页面比较重要位置的模块,如轮播图、导航等,如果需要做动画效果的话,在高低端浏览器上都应该能统一实现出来,新旧版首页首屏都以轮播图为主,轮播图切换都使用了渐隐渐现的动画效果。
旧版的动画实现在高低端浏览器都使用了 JQ 第三方动画库
|
|
其实渐隐渐现的效果 CSS3 动画也能实现。新版首页的轮播图动画设置了 CSS3 动画后,再利用脚本控制样变化以触发 CSS3 动画,这样支持动画属性的浏览器就能以 CSS3 动画实现效果,而不支持的浏览器则通过脚本的属性判断,用 JQ 动画实现:
|
|
|
|
JQ 动画虽然兼容性好,但其动画性能远远不及 CSS3 动画,因此我们可以用以下的方法对动画性能实现渐进增强:高端浏览器可以通过触发 CSS3 动画实现效果,低端浏览器则使用 JQ 动画实现。
视觉渐进增强通常可以通过 CSS3 属性和增加 CSS3 动画来实现,现主流的网站基本都会对视觉做渐进增强处理。本次首页改版主要在多态元素、切换元素上做了处理
支持 CSS3 动画的 SexyGuy
不支持 CSS3 动画的 PoorGuy
浏览页面的时候,通过 Tab 键可以聚焦页面上的链接锚点,这时候浏览器会在锚点增加一个系统默认边框样式告诉用户锚点已选中,按 Enter
就可以打开选中的锚点,如 Chrome 浏览器上 google 首页的语音搜索按钮:
即使用户在浏览页面的时候鼠标突然失灵了也可以通过键盘操作继续完成浏览网页,这样的设计显然是为了增强页面的可用性。
但很多时候,在一些重要位置的内容,如全站的导航,产品经理或视觉设计师会要求将这个系统的样式去掉,于是很多同学可能会选择设置outline:none
去掉边框样式,有些甚至会在全局 a 标签上设置,如旧版的京东云首页:
outline:none
设置之后,页面上的所有链接虽然能通过Tab
键聚焦,但链接并没有被选中的样式,没有办法直观辨出选中的链接
虽然并非所有用户都会用到 Tab 键,但还是会有少数用户会用到,如键盘党,而这种降低可用性的体验存在表明页面并没有健全,因此并不建议去掉outline
样式。
如果真的有去掉 outline
样式的需求怎么办?其实,页面链接一般都会被设计为多态的,利用链接的多态样式,为链接加上:focus
伪类选中样式,Tab 选中链接后就会展示 :focus
伪类样式了,如新版首页的导航:
可以为链接加上:focus
伪类样式
|
|
当选中链接还绑定有事件的时候,也应该为之绑定相应事件
|
|
处理完,虽然 outline
样式去掉了,但依然可以用 Tab 键完成链接的选中
旧版首页所有的静态资源的更新发布方式都是采用覆盖式更新:
|
|
覆盖式更新发布有机会遇到缓存问题以及在发布的时候导致页面错乱问题,详情可以看一下张云龙前辈在知乎对问题『大公司里怎样开发和部署前端代码?』的回答,解决覆盖式更新产生的问题,现主流方法就是使用 MD5 文件名进行非覆盖式发布,京东云新版首页所有的静态资源的更新发布都采用了这种方式。
|
|
OK,优化永远说不完的,以上所说的只是前端优化的冰山一角,业界绝不缺高大上的优秀优化方案,但从业务实际规模出发的话,这些小优化在本次改版中已得到很明显的收益,期待以后有更具规模的项目可以挥霍高大上的优化方案,最后把新旧版的页面都放到预览服务器上了
]]>本文介绍用 Node.js 中的依赖库来处理 Excel 文件,深入分析对比常见npm库处理Excel 文件存在的优缺点,主要阐述用js-xlsx、excel-export 库来处理 Excel 文件。
通过npm搜索,支持读写excel文件的模块有很多,但是都各有忧缺点,有些仅支持xls/xlsx的一种格式,有些仅支持读取数据,有些仅支持导出文件,有些需要依赖python解析。常见的npm依赖模块如下:
- js-xlsx: 目前 Github 上 star 数量最多的处理 Excel 的库,支持解析多种格式表格XLSX / XLSM / XLSB / XLS / CSV,解析采用纯js实现,写入需要依赖nodejs或者FileSaver.js实现生成写入Excel,可以生成子表Excel,功能强大,但上手难度稍大。不提供基础设置Excel表格api例单元格宽度,文档有些乱,不适合快速上手;
- node-xlsx: 基于Node.js解析excel文件数据及生成excel文件,仅支持xlsx格式文件;
- excel-parser: 基于Node.js解析excel文件数据,支持xls及xlsx格式文件,需要依赖python,太重不太实用;
- excel-export : 基于Node.js将数据生成导出excel文件,生成文件格式为xlsx,可以设置单元格宽度,API容易上手,无法生成worksheet字表,比较单一,基本功能可以基本满足;
- node-xlrd: 基于node.js从excel文件中提取数据,仅支持xls格式文件,不支持xlsx,有点过时,常用的都是XLSX 格式。
通过以上分析对比,本人比较推崇js-xlsx
、excel-export
来读写Excel文件,可以结合使用js-xlsx
解析Excel、excel-export
生成,效果更加,接下来分别实践js-xlsx
、excel-export
。
node中使用通过npm:
浏览器使用:
通过bower安装:
注意
,在客户端使用时,建议使用dist/xlsx.full.min.js
,包含了js-xlsx所有模块。
在使用这个库之前,先介绍库中的一些概念。
- workbook 对象,指的是整份 Excel 文档。我们在使用 js-xlsx 读取 Excel 文档之后就会获得 workbook 对象。
- worksheet 对象,指的是 Excel 文档中的表。我们知道一份 Excel 文档中可以包含很多张表,而每张表对应的就是 worksheet 对象。
- cell 对象,指的就是 worksheet 中的单元格,一个单元格就是一个 cell 对象。
它们的关系如下:
基本用法
1.用 XLSX.read
读取获取到的 Excel 数据,返回 workbook
2.用 XLSX.readFile
打开 Excel 文件,返回 workbook
3.用 workbook.SheetNames
获取表名
4.用 workbook.Sheets[xxx]
通过表名获取表格
5.用 worksheet[address]
操作单元格
6.用XLSX.utils.sheet_to_json
针对单个表获取表格数据转换为json格式
7.用XLSX.writeFile(wb, 'output.xlsx')
生成新的 Excel 文件
具体用法
读取 Excel 文件
获取 Excel 文件中的表
通过 worksheet[address]
来操作表格,以 ! 开头的 key 是特殊的字段。
获取 Excel 文件中的表转换为json数据
生成新的 Excel 文件
解析 Excel 生成 JSON
导出表格
1.构建特定的数据结构,通过new Blob如下。
2.调用 XLSX.write, 借助FileSaver中new Blob生成即可。
实践Demo:RD快速生成excel表
excel-export模块,上手起来就比较容易了,其中原理是通过修改,修改header 信息、拼接字符串、修改字符集、输出字符串的形式实现的,在部分firefox低版本下载中文名会出现乱码情况。我们只需要按照API设置好数据参数,通过nodeExcel.execute调用执行,系统调用模版”styles.xml”就可以生成Excel文件,比较好的就是,它可以设置单元格的宽度,类型。
我们先看看,官方提供的例子:
分析生成excel流程:
1.配置excel文件名conf.name
2.设置表caption,每列单元格数据类型,宽度
3.填充表中每行数据conf.rows,nodeExcel.execute生成数据结构,设置头部,拼接生成表
写在最后,以上仅为个人观点,如有纰漏之处,欢迎各位大侠拍砖!
参考资料:
]]>老生常谈,我们先从最基础的编码说起吧!好的编码规范不仅仅能够提升代码的可读性与可维护性,提高团队的工作效率,也能够避开一些低级的错误,减少bug的隐患,提升程序员的自我修养。编码虽小,但却是万丈高楼的基础,对于编写清晰连贯的代码来说,每一个字符都是非常重要的。以下部分编码规范参考自凹凸实验室。
通常使用四个空格进行代码缩进,有些也用tab来缩进,这主要根据团队的风格跟个人喜好
这是一个容易被大家忽略的点,但它所带来的效果是毋庸置疑的!通常一段代码的语义和另一段代码不相关,就应该用空行隔开,避免一大段的代码揉在一起,比如
有一位大师曾说过,计算机科学只存在两个难题:缓存和命名。由此可见命名不仅是一门科学,也是一门技术。
通常情况下,变量与函数一般使用驼峰大小写命名法,其中为了区分变量与函数,变量命名前缀应当是名词,函数前缀应当是动词,也就是说我们应当让命名承载一定的含义,因此要避免使用没有意义的命名。
通常我们在编写完一段代码的短时间内,会清楚这段代码的工作原理。但是当过一段时间再次回到代码中,可能会花很长的时间才能读懂。这种情况下,编写注释就变得尤为重要了。
首先说一说全局变量存在哪些的问题吧!命名冲突、测试难度大、深耦合等等。在创建变量的时候,我们应该注意以下几个方面
什么是隐性的全局变量呢?官方的回答是:任何变量,如果未经声明,就为全局对象所有。啥意思呢?其实就是没有加var声明的,请看下面的例子
|
|
另外一种容易创建隐形全局变量的情况就是var声明的链式赋值,如下代码所示
以上这段代码的执行结果是:a是局部变量,b是全局变量,主要原因是从右至左的操作符优先级,它实际执行的结果等同于
综上所述,隐式全局变量并不是我们平时用var声明的变量,而是全局对象的属性,既然是属性,那么它可以通过delete操作符删除,但变量不可以,且在ES5 strict以上会抛出错误。
在javascript中,声明变量有一个“提升”的概念,即无论在函数哪里声明,效果都等同于在函数顶部进行声明。所以我们统一把变量在函数顶部声明,既有利于可读性与可维护行,也不易出错。
|
|
这样声明的变量不仅可读性好,而且可以防止变量在定义前就被使用的逻辑错误,且编码更少。
虽然全局变量的容易污染命名空间,但有些功能的需要,难以避免使用,关键是我们应该做到避免全局变量超出我们的掌控,最佳的方法是依赖尽可能少的全局变量。我们可以使用单全局变量的方式来开启我们的项目,这种方式在许多的javascript类库中都有这样使用。如jQuery,它定义了两个全局变量$和jQuery。
什么是松耦合?当修改一个组件的逻辑,而对另一个组件没有影响,就说这叫松耦合。通常一个大型的web应用,都是由多人共同开发维护,这时候松耦合显得至关重要,假如你修改了某一处的代码而影响了团队其他人的功能,这是非常不友好的。通常我们主要注意以下几点
在javascript开发中,总是会悄无声息的出现一些超出我们预期的,携带的信息稀少的,隐晦含糊的bug,让我们措手不及,大大增加了我们调试错误、定位错误的难度,影响开发效率。假设错误中包含这样的信息:“由于某某情况,导致某某函数执行错误”,那么是不是马上就可以开始调试而不用花大量的时候去定位错误?
主要是辨识代码中哪些部分在特定的情况下最后可能导致错误,这里的错误通常都是我们在思考的过程中的一些可预期的错误。
将可能引发错误的代码放在try块中,处理错误的代码放在catch中,如
也可以增加一个finally块,这里需注意的是finally块中的代码块不管是否有错误发生,最后都会被执行。
当我们能清晰的捕捉到错误的时候,最好的做法就是抛出这个错误,避免在不经意的时候又遇到它,让大家尴尬。这里需注意的是当遇到throw操作符时,代码会立即停止执行
也可以自定义一个错误类型,总之就是尽可能用最短的字符描述清楚
所谓的对象字面量其实就是我们通常所说的键值对哈希表,这种方式不仅富有表现力,可读性好,且字符更短,没有作用域解析。它的语法规则如下
|
|
以上例子的name、job属性都是可直接访问的。有些时候我们可能想实现一些私有的属性,然后提供一个公有的接口来对外访问。虽然javascript并没有特殊的语法来表示私有、公共属性和方法,但是可以通过匿名闭包来实现,内部的任意变量都不会暴露,来看以下代码
更优雅的写法
这种写法也是模块模式的基础框架,后续会有详细介绍。
熟悉了这种模式之后它还有很多种玩法,比如可以像jQuery这样链式调用:“$(‘#id’).siblings(‘ul’).find(“li”).addClass();
我们先来看看构造函数的基础框架
在使用new方式实例化构造函数通常会经历以下几个步骤
当然我们有时候会忘记使用new操作符的实例化的情况,然而这并不会导致语法错误,但构造函数的this指向了全局对象,可能会发生逻辑错误或者意外,来看下面执行的结果
为了避免这种意外发生,我们也可以在构造函数中检查this是否为构造函数的一个实例,强制使用new操作符,继续看下面的例子
|
|
再看执行的结果
在javascript中,并没有特殊的语法来表示静态成员,但我们可以为构造函数添加属性这种方式来实现这种语法,请看下面的例子
这里大家需要注意调用静态方法的方式,若以实例对象调用一个静态方法是无法正常运行的,反之同理。
在以上例子中构造函数的属性与方法都属于公有方法,我们也可以给构造函数添加私有方法与私有属性
构造函数的主要问题就是当多次实例化这个构造函数的时候,每个方法都会重新创建一遍,这样就等于在内存中的拷贝。解决问题的第一种思路就是将函数中的方法通过函数定义转移到函数外面,并将指针传递给构造函数,来看下面的例子
虽然也解决了以上的问题,但并没有达到封装的效果。接下来我们引入原型prototype的概念。
每一个构造函数都有一个原型prototype,原型对象包含一个指向构造函数的指针,这个指针指向一个可以由特定类型的所有实例共享的属性和方法,所以使用原型对象可以让所有对象实例共享它的属性和方法,来看下面的例子
由此可见obj1 和 obj2 访问的是同一个getName函数
我们可以将所有的原型都写在一个对象字面量里,这样整个代码看起来更加简洁清晰,继续往下看
在使用这种字面量的方式的时候需注意以下两点
1.将prototype设置为等于一个对象字面量形式创建的对象,它本质上已经完全重写了默认的prototype对象,最终结果虽然相同但是其constructor属性不再指向该对象。
constructor是个什么鬼?在默认情况下,所有原型对象都会自动获得一个constructor,它指向prototype属性所在函数的指针,换句话说这个constructor就是指这个构造函数。以上代码执行结果如下所示
我们可以在重写prototype的时候给constructor指定构造函数,接着往下看
2.当我们重写整个原型的时候如果先创建了实例,就会切断构造函数与原型之间的联系,因为实例的指针仅仅指向原型,而不是构造函数,在实际的操作过程中,应该尽量避免这种错误
在我们的具体应用中,通常比较多的是组合使用构造函数模式与原型模式。构造函数用于定义实例属性,原型用于定于共享的属性和方法,这样能够最大限度的节省内存。以下是一个基本的组合使用构造函数与原型的例子
模块模式是一种非常通用的模式,也是使用频率比较高的模式,它具有以下几个特点
我们先看看模块模式的基础框架
这种方式看起来比较清晰、简洁,但就是每次调用的时候都需要用new来实例化,我们知道每个实例在内存里都是一份拷贝。如何解决这个问题呢?…我们可以采用一个匿名闭包来完美的解决这个问题。
接下来我们将它应用到具体的实例中,以下就是一个基本的Module模式
通常在一个大型的项目中,会有多人共同开发一个功能的情况,这个时候我们可以运用这种模式将全局变量当作参数传递,然后通过变量返回,从而达到多人协作的目的。
|
|
我们也可以通过这个模式将私有的对象或者属性保护起来,然后设置一些公共接口对外访问,继续来看下面的代码
以上几种方式仅仅只是一些创建对象的基础,通过灵活运用这些基础,可以变换出传说中各种各样的模式,如迭代器模式、工厂模式、装饰者模式等,对于后续学习其他的技术也是极有帮助的,如React:
Vue:
以上就是本期的所有内容,如有错漏,恳请指正,大家共同进步!在下一期中,会继续跟大家探讨更多好玩的东西,敬请期待~~~
《编写可维护的JavaScript》[美] Nicholas C. Zakas 著
《JavaScript设计模式》[美] Addy Osmani 著
《JavaScript高级程序设计(第3版)》
博文:深入理解JavaScript系列
对于代码党来说,很简单的加上一行代码就能够搞定:
|
|
对于 Storyboard 狂魔,一般情况下,很多人会先把视图拉一个 IBOutlet 然后再到 awakeFromeNib:
或者 viewDidLoad:
方法中去设置圆角,代码如下:
|
|
但是对代码有一定洁癖的强迫症患者来说,这样的做法经常是要命的!实在无法忍受一个小小圆角都不能在IB中设置,需要另外单独加一行代码来完成,违背了低耦合,高内聚的原则。有人马上提出建议,那就使用IB的运行时属性(Runtime Attributes),有些新手可能对它还不太熟悉:
这的确也是个不错的方法,可以达到高内聚的效果。不过用过的人都知道,很容易就把 keyPath 拼写错,而且由于这个设置和其他属性的设置分开,可读性可以说很差很差。那有没有什么好的方法呢?
Xcode6之后运行时属性升级到了 @IBInspectable ,利用这个我们可以给 UIView 添加一个属性,然后就可以在IB中进行设置,例如我们想给 ViewController 添加一个数值到IB中设置,在上述代码的最前面插入代码:
|
|
然后我们就能在属性检查器上看到如图所示内容,很容易地对数值进行设置:
@IBInspectable 还支持以下类型属性:
- Boolean
- Number
- String
- Point
- Size
- Rect
- Range
- Color
- Image
- nil
回到正题,我们视图的圆角该怎么实现呢?也许你们马上想到了继承,实现一个 UIView 的基类,基类中添加圆角的 @IBInspectable 属性。但这样你马上嗅到了不好的味道,你所有想要使用该属性的视图都要继承自该基类,那岂不是更加麻烦!
其实最好的解决方法你应该心里有数,如果说在 Object-C 中给已有的类添加方法,你肯定马上能想到 Category !不过可能有些人还没不清楚应该如何在 Category 中添加属性。由于这里我们用的是 Swift ,稍后我们再说OC中应该如何实现。 Swift 中应该使用 extension 来对 UIView 进行扩展,并且我们需要添加 @IBInspectable 来扩展属性,所以我们需要同时实现 setter & getter ,创建一个命名为 UIView+O2CornerRadius.swift
的文件,代码如下:
|
|
只需要如此简单地添加一个扩展,不需要 import ,不必任何多余代码,我们就可以非常非常方便地在任意IB的属性检查器中对圆角进行设置了!这不就是我们梦寐以求的解耦吗?!!:)
实际上, @IBInspectable 是对运行时的一种扩展,你所有的设置都会在上述提到的运行时属性(Runtime Attributes)有所体现。
我们还可以增加很多内容的扩展,例如阴影、边框、边框颜色等等!学会了这样的奇淫技巧,还不赶紧到你的项目中去实践!
说说 Object-C 的代码实现,我们使用 Category 同样需要同时实现 setter & getter :
|
|
|
|
HTTP/2 是万维网(WWW)基础协议 HTTP 16年来的首次重大升级。共经历了18版草案(00-17),于2015年2月18日正式定稿,2015年5月14日 HTTP/2 正式版发布,正式版 HTTP/2 规格标准叫做 RFC7540 。
好吧,我相信你一转身就忘了上面提到的这一长串你内容,特别是这个复杂的规范名称。恩~没关系,要了解 HTTP/2,还是先要了解它的新特性以及实现原理。
作为一个前端小白,前些天我做了一个简单的活动,本地调试完后我把它放到服务器(假设域名为:jdc.jd.com)上,这时候你通过在客户端(浏览器)访问以下:jdc.jd.com/act/index.html,就可以看到我的活动页面了(当然,要经过一小段时间的等待)。
那么客户端是如何取得服务端的这些资源的?它们之间的通信是怎样的?
侧重讲讲三个过程:
当然,接下来还有浏览器解析渲染的过程~巴拉巴拉~我们才能最终看到页面~~
着重看下 HTTP/1.0 和 HTTP/1.1 这三个的过程不同:
在 HTTP/1.0 下,每完成一次请求和响应,TCP 连接就会断开。但我们知道,客户端发送一个请求只能请求一个资源,而我们的网站不可能只有单单一个 HTML 文件吧?至少还要有 CSS 吧?还要有图片吧?于是又要一次 TCP 连接,然后请求和响应。
下图展示了 HTTP/1.0 请求一个 HTML 和一个 CSS 需要经历的两次 TCP 连接:
要知道,TCP 连接有 RTT(Round Trip Time,即往返时延)的,每请求一个资源就要有一次 RTT ,用户可是等不得这种慢节奏的响应的。于是到了 HTTP/1.1 ,TCP 可以持久连接了,也就是说,一次 TCP 连接要等到同域名下的所有资源请求/响应完毕了连接才会断开。恩!听起来情况好像好了很多,请求同域名下的 n 个资源,可以节约 (n-1)*RTT 的时间。
下图展示了 HTTP/1.1 时请求一个 HTML 和一个 CSS 只需要经历一次 TCP 连接:
但前面提到了,客户端发送一个请求只能请求一个资源,那么我们会产生如下疑问:
Q:为什么不一次发送多个请求?
事实上,HTTP/1.x 多次请求必须严格满足先进先出(FIFO)的队列顺序:发送请求,等待响应完成,再发送客户端队伍中的下一个请求。也就是说,每个 TCP 连接上只能同时有一个请求/响应。这样一来,服务器在完成请求开始回传到收到下一个请求之间的时间段处于空闲状态。
Q:有什么办法去改变吗?
“ HTTP 管道”技术实现了客户端向服务器并行发送多个请求。而服务器也是可以并行处理多个请求的。这么一来,不就可以多路复用了吗?但是, HTTP/1.x 有严格的串行返回响应机制,通过 TCP 连接返回响应时,就是必须 one by one ,前一个响应没有完成,下一个响应就不能返回。所以使用“ HTTP 管道”技术时,万一第一个响应时间很长,那么后面的响应处理完了也无法发送,只能被缓存起来,占用服务器内存,这就是传说中的“队首阻塞”。
Q:既然一个 TCP 连接解决不了问题,那么可以开多个吗?
既然一条通道(TCP 连接)通信效率低,那么就开多条通道呗!的确,HTTP/1.1 下,浏览器是支持同时打开多个 TCP 会话的(一般为6个)。一个 TCP 只能响应一个请求,那么六个 TCP 岂不就能达到六倍速?想想还有点儿小激动!但事情往往不是这么简单。开启多个 TCP 会话,无疑会给客户端和服务器都带来负担,比如缓存、CPU 时钟周期等,而且并行的 TCP 也会竞争带宽,并行能力也是受限制的,往往无法达到理想状态下的六倍速。
可见,我们采取了许多方法,希望可以并行处理请求/响应,但都不能从根本上解决问题。况且,很多方法与 HTTP/1.x 的设计理念是背道而驰的,在 HTTP/1.x 下,却没有正确利用好 HTTP/1.x 的特性。
于是, HTTP/2 带着提高性能的使命,应运而生。
先对 HTTP/2 产生的影响有一个直观的认识:
这是Akamai公司(全球最大的CDN服务商)的一个官方演示,HTTP/1.1 和 HTTP/2 请求300+张图片的对比:
明显可以看出, HTTP/2 下加载时间为 HTTP/1.1 的 1/5 不到,那么 HTTP/2 到底为什么这么快?我们还是从它的新特性来进行全面的了解。
以下着重介绍五个特性:二进制分帧层、多向请求与响应、优先级和依赖性、首部压缩、服务器推送。
指的是位于套接字接口与应用可见的高层 HTTP API 之间的一个新机制:HTTP 的语义,包括各种动词、方法、首部,都不受影响,不同的是传输期间对它们的编码方式变了。
在新引进的二进制分帧层上,HTTP/2 将所有传输的信息分割为更小的消息和帧,且都采用二进制格式的编码。如下图所示:
从图上可以看到:在高层 HTTP API 和低层 TCP 连接中引入了一个二进制分帧层;在二进制分帧层上,HTTP/1.1 的一个 POST 请求(起始行、首部、实体正文),被分割成了更小的 HEADERS 帧和 DATA 帧。起始行、首部被分割到 HEADERS 帧,实体正文被分割到 DATA 帧。
接下来,我们再深入地了解下这些被分割后的二进制帧是怎么工作的:
HTTP/2 同域名的所有通信都是在一个 TCP 连接上完成,这个连接可以承载任意数量的双向数据流。而每个数据流都是以消息的形式发送的,消息由一个帧或多个帧组成。
- 流:已建立的连接上的双向字节流
- 消息:与逻辑消息对应的完整的一系列数据帧
- 帧:HTTP/2 通信的最小单位,每个帧包含帧首部
好像很复杂的样子,咱们来捋一捋:
TCP 连接在客户端和服务器间建立了一条运输的通道,可以双向通行,当一端要向另一端发送消息时,会先把这个消息拆分成几部分(帧),然后通过发起一个流对这些帧进行发送,最后在另一端将同一个流的帧重新组合。
这个过程就好像我们在搬家的时候,会把一个桌子先拆散成零部件,然后通过几次的搬运,到了新家后,再把桌子重新拼装起来。
下图展示了流、消息与帧的关系(注意到没,HEADERS 帧总是在最前面的):
HTTP/2 规范一共规定了 10 种不同的帧, 其中最基础的两种分别对应于 HTTP/1.1 的 DATA 帧 和 HEADERS 帧 。
多路复用允许同时通过单一的 TCP 连接发起多重的请求/响应消息,客户端和服务器可以把 HTTP 消息分解为互不依赖的帧,然后乱序发送,最后再在另一端根据 Stream ID 把它们重新组合起来。
前面提到的一端发送消息会先对消息进行拆分,与此同时,也会给同一个消息拆分出来的帧带上一个编号(Stream ID),这样在另一端接收这些帧后就可以根据编号对它们进行组合。
也正是有了这种编号的方式,当某一端发送消息时,可以发送多个消息拆分出来的多个帧(发起多个流),且这些帧可以乱序发送,因为这些帧都有自己的编号,它们之间互不影响。
下图展示了单一的 TCP 连接上有多个请求/响应并行交换:
从图上可以看出,服务器向客户端发送 stream1 的多个 DATA 帧(说明 HEADERS 帧已发送完毕)与 stream3 的 HEADERS 帧和 DATA 帧 ,客户端正在向服务器发送 stream5 的 DATA 帧,可见,帧的发送是乱序的,且请求/响应是并行的。
细心的你会发现,stream1 中有多个 DATA 帧,这是为什么呢?因为有 DATA 帧有长度的控制(2的14次方-1 字节,约 16383 个字节),应用数据过大时,会被拆分成多个 DATA 帧(还记得讲二进制分帧层展示的 HTTP/1.1 的请求被分割成更小的帧吗?DATA 帧就是用来携带应用数据的)。
新建流的终端可以在报头帧中包含优先级信息来对流标记优先级。
优先级的目的是允许终端表达它如何让对等端管理并发流时分配资源。更重要的是,在发送容量有限时优先级能用来选择流来传输帧。
HTTP/2 中,流可以有一个优先级属性(即“权重”):
流的优先级用于发起流的终端(客户端/服务器)向对端(接收的一方)表达需要多大比重的资源支持,但这只是一个建议,不能强制要求对端一定会遵守。
借助于 PRIORITY 帧,客户端同样可以告知服务器当前的流依赖于其他哪个流。该功能让客户端能建立一个优先级“树”,所有“子流”会依赖于“父流”的传输完成情况。
不依赖任何流的流的流依赖为 0x0。换句话说,不存在的流标识 0 组成了树的根。
我们通过以下几个例子来理解下优先级“树”:
前面说到,“可以单独通过 PRIORITY 帧专门设置流的优先级属性”,也就是说可以对原本没有优先级属性(包括依赖关系)的流进行设置,也可以对原本已有优先级属性的流进行修改。因此,优先级可以在传输过程中被动态的改变。
HPACK 是专门为 HTTP/2 量身定制的压缩技术。
在服务器和客户端各维护一个“首部表”,表中用索引代表首部名,或者首部键 - 值对,上一次发送两端都会记住已发送过哪些首部,下一次发送只需要传输差异的数据,相同的数据直接用索引表示即可。
具体实现如下图所示:
这个过程比较容易理解:通过索引表的对应关系,来标记首部表中的不同信息。
同一个域名下的请求/响应的首部往往有很多重复的信息,当客户端要向服务器发送某个请求时,通过查找索引表,发现该信息的首部已经发送过,此时服务器端的索引表也应该有对应的信息,则不需要再次发送;若查找发现部分首部信息不在索引表中,则发送该部分信首部息即可。
如在上图的示例中,第二个请求只需要发送变化了的路径首部(:path),其他首部没有变化,就不用再发送了。
服务器可以对一个客户端请求发送多个响应。也就是说,除了对最初请求的响应外,服务器还可以额外向客户端推送资源。
在了解“二进制分帧层”的时候我们提到,“HTTP/2 规范规定了10种不同的帧”,其中有一种名为“PUSH_PROMISE”,就是在服务器推送的时候发送的。当客户端解析帧时,发现它是一个 PUSH_PROMISE 类型,便会准备接收服务端要推送的流。
从上图可以看出,当服务器响应了 HTML 请求后,可以知道客户端接下来要发送 JS 请求、CSS 请求,于是服务器通过推送的方式(主动发起新流,而不是等客户端请求然后再响应),向客户端发出要约(PUSH_PROMISE)。当然,客户端可以选择缓存这个资源,也可以拒绝这个资源。
这个过程有点类似于我们常用的资源内嵌的手段:将一个图片资源转为 base64 编码嵌入 CSS 文件中,当客户端发起 CSS 请求时,也会请求该图片。因此在响应 CSS 请求后,服务器会强制(客户端是无法拒绝的)向客户端发送图片响应。但内嵌资源是无法被单独缓存的,而服务器推送的资源是可以被缓存的。
需要注意,服务器必须遵循请求-响应的循环,只能借着请求的响应来推送资源,也就是说,如果客户端没有发送请求,服务器是没法先手推送的。而且,如上图中 stream4 ,PUSH_PROMISE 帧必须在返回响应(DATA 帧)之前发送,因为万一客户端请求的恰好是服务器打算推送的资源,那传输过程就会混乱了。
注:由客户端发起的流 Stream ID 为奇数,由服务器发起的流 Stream ID 为偶数,回顾上面的图就能发现啦!
可见,HTTP/2 在 HTTP/1.1 的基础上有了一个较大的性能提升。这时候你会发现,我们针对 HTTP/1.x 的一些优化手段(如上文提到的资源内嵌)似乎有点不适用了。
下一次的文章《前端开发与 HTTP/2 的羁绊——性能篇》,将和大家一起深入探讨 HTTP/2 下的性能优化问题。
文章撰写参考:
图片来源:
如有错漏,欢迎斧正。
]]>历时144000000毫秒出山的前端优化篇,若你问我有什么感悟?
那我告诉你,看到毫秒啊,火箭啊,这些与优化相关的词,都有莫名的亲切感。
本文主要从工作效率、速度性能、稳定性、响应式、兼容性、搜索SEO、信息无障碍等方面进行讲解。
前端优化是一个让人技术提升的point,希望你也能从这里学到一些东西。
你是否经常处于这样的场景:从早上忙到晚上八九点,一会与产品经理沟通,一会在部门群聊一下新奇的东西,一会被设计美眉纠缠住拖不了身,有时还开不了部门的会议因为页面急着上线,然后继续加班~~~
怎么提高我们的工作效率?下面从四个方面讲解:
凡是时间管理,都会联想到计划这个词。我们先看看别人家的月计划表和周计划表,之所以周计划表为空,是希望你能把它下载并打印出来,行动从计划开始:
月计划表:
周计划表:
当然计划不要做得过于琐碎,且不要占用自己太多时间。做好计划之余,在执行过程中需要注意几点:
第一样工具,比如程序员杯子:
利用工具有什么好处呢?
选择好一个前端编辑器是比较重要的。目前sublime、webstorm和vim是比较常见的,建议不使用Dreamweaver。
sublime目前还是不错的选择,可以安装插件,比如BracketHighlighter 高亮显示、JsFormat、Emmet html/CSS快速编辑以及DocBlockr插件,快速输入jsDoc注释等,还可以自定义代码段snippets。
无论你使用哪种编辑器,你需要的是熟悉这个编辑器并熟练它的快捷键。
作为前端人员,首选的浏览器当然是chrome。推荐阅读Chrome开发者工具不完全指南一系列文章,它从一些基础的功能开始到它的一些高级性能分析器(Timeline、Profiles),熟悉chrome对我们的开发工作有很大的作用。
切图工具:photoshop cc切图之智能切图、 cutterman
量色、测距工具:FastStone Capture、马克鳗 - 设计稿标注
图片压缩:tinypng、智图
生成雪碧图:spritebox、CSS Sprite Generator、cssgaga
调试工具:Fiddler 、weinre 、微信调试工具;
凡是重复的,必须使用工具自动完成。
工具众多,我们就有一种想法,能不能有一种工具能帮我们自动生成雪碧图、 css压缩、图片压缩等等,然后就出现了前端工程化。前端工程化一般可分为五个步骤:
(1) 初始,生成基础目录结构和样式库。
(2) 开发,实时预览、预编译。
(3) 构建,预编译、合并、压缩。
(4) 发布,将构建后静态文件发布上线。
(5) 打包,资源路径转换,源码打包 。
这里推荐一个工具fis,解决前端开发中自动化工具、性能优化、模块化框架、开发规范、代码部署、开发流程等问题。还有凹凸实验室研发的athena,O2Team构建项目流程工具,可以生成相应目录和代码,同时对项目进行编译, 一次安装,到处运行。
我所理解的程序员兼并聪明以及“懒惰”精神,推崇懒惰式开发,即把问题理解清楚,确保将要写的代码能真正的解决问题,这将会避免之后写出大量无用的代码,正所谓“懒”出效率。
我们的阅历和经验可以大大提高开发效率,思考代码的时间增加从而选出最优方案,因此写代码速度更快以及代码长度更短,对问题的透彻理解使调试代码的速度也更快。
根据阅历和经验,或借助其他人的,我们进行整理从而形成自己或团队的规范,这可大大提高我们的写码速度。
使用新技术如何提高我们的工作效率。一贯我们都使用我们熟悉的技术去开发一个技术处理方案,毕竟学习新技术的时间成本还是存在的。但是还是不能忽略一些新技术的存在,一般新技术包含了一些很棒的新特性,可以更加方便的实现很多复杂的操作,提高开发人员的效率,比如ES6。用你的慧眼去积累新技术,会派上用场的。
为什么需要前端性能优化?性能优化可以从哪几个方面入手?
遇到一个页面,5秒还没加载完成,那个菊花转啊转,或者页面完全白屏,那简直把人逼疯了。从用户体验的角度看,前端性能优化是非常有必要的。网页最长加载时间一般不能超过3秒。
首先我们需要确定网页的性能指标,可量化的目标以及可持续跟踪的优化数据是性能优化工作得以持续进行的保障,同时也是源动力!比如:
我们一般通过三种方式来检验我们的网页性能:
可喜可贺,W3C推出了一套性能API标准,目的是简化开发者对网站性能进行精确分析与控制的过程,最终实现性能的提高。比如通过Navigation Timing记录的关键时间点来统计页面完成所用的时间,部分使用方法:
|
|
持续追踪性能数据,要选择合适的页面性能测量工具或API,一旦选定后,不再更换,以保证历史数据的可参照性。我们还要形成一种意识,达成性能联盟小组,对于重要的业务或者页面,一定要从性能的角度考虑问题,有理有据地拒绝有损于前端性能的业务需求或改动。
人人都知道雅虎军规,那我就来个截图吧!
以下,我们从服务端、网络、客户端三个方面来一一突破速度性能的提升。
通过在现有的Internet中增加一层新的网络架构,将网站的内容发布到最接近用户的 cache服务器内,通过DNS负载均衡的技术,判断用户来源就近访问cache服务器取得所需的内容。深圳用户访问遥远的美国服务器,当然不理想了。把静态内容分布到CDN可以减少用户响应时间20%或更多。
如果可以减少服务端的负担,在应用离线时可使用资源或加载资源更快,岂不乐哉?
缓存利用可包括:添加 Expires 头,配置 ETag,使 Ajax 可缓存等。其实,恰当的缓存设置可以大大的减少 HTTP请求,也可以节省带宽 。
AppCache:
AppCache主要利用manifest 文本文件,告知浏览器被缓存的内容以及不缓存的内容。
manifest 文件可分为三个部分:
(1) CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存,等价于CACHE
(2) NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
(3) FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面
使用AppCache方案的步骤:
(1) 整理出需要缓存的静态文件列表,如juqery.js和gb.css。
(2) 配置服务器支持。
(3) 确定内容更新机制和浏览器兼容方案。
可通过以下方式减少请求数:
减少请求数对于速度优化来说最重要最有效的,特别是网络差的用户。一个完整的请求需要经过域名解析以及DNS寻址、与服务器建立连接、发送数据、等待服务器响应、接收数据的过程;每个请求都需要携带数据,因此每个请求都需要占用带宽;浏览器进行并发请求的请求数是有上限的。请求多了的情况,明显增加了网页的响应时间。一个页面由多个模块拼接而成,几个模块中请求了同样的资源时,就会导致资源的重复请求。
域名的要求是短小且独立。
短小可以减少头部开销,因为域名越短请求头起始行的 URI 就越短。之所以要求独立,因为独立域名不会共享主域的 Cookie,可以有效减小请求头大小,这个策略一般称之为 Cookie-Free Domain;另外一个原因是浏览器对相同域名的并发连接数限制,一般允许同域名并发 6~8 个连接,域名不是越多越好,每个域名的第一个连接都要经历 DNS 查询(DNS Lookup),导致会耗费一定的时间,控制域名使用在2-4个之间。另外注意:同一静态资源在不同页面被散列到不同子域下,会导致无法利用 HTTP 缓存。
HTTP 2 相比 HTTP 1.1 的更新大部分集中于:
稳定性的第一要求是可用。最起码的要求是页面得出来,要不然没法用了。
其次讲究的是页面的可维护性,假如页面挂了,多久可以恢复过来,另外考虑页面挂的期间是否可以采取静态页面处理等方式。
页面的稳定性其实和前端安全挂钩,即使页面可以出来了,但是不能保证不会被黑掉,下文从前端安全的方面讲解。
XSS (Cross Site Script) ,跨站脚本攻击,往Web页面里插入恶意html代码。特点是攻击者的代码必须能获取用户浏览器端的执行权限,要杜绝此类攻击出现可以在入口和出口进行严格的过滤。
三种类型:
(1) 反射型XSS:一次性;将包含注入脚本的恶意链接发送给受害者。
(2) 持久型XSS:用户输入的数据“存储”在服务器端,比如一条包含XSS代码的留言。
(3) DOM XSS:使用一些eval等有输出的语句意味着多了一份被XSS的风险。
应对策略:
CSRF(Cross Site Request Forgery),跨站点伪造请求,通过伪造连接请求在用户不知情的情况下,让用户以自己的身份来完成攻击者需要达到的一些目的。
cookie劫持,通过获取页面的权限,在页面中写一个简单的到恶意站点的请求,并获取用户的cookie登录某些站点。
对于crsf 和cookie 劫持的策略:
国内的众多网站都没有实现全站HTTPS。这是目前为止最重要的一步,所有的数据在发送之前就会被加密,攻击者无法查看或篡改数据包的内容。HTTPS可以理解为HTTP+SSL/TLS,通过数据加密、校验数据完整性和身份认证三种机制来保障安全。HTTPS的缺点是网站在加上TLS证书时,可能导致RTT往返时延增加,并且 HTTPS通信过程的非对称和对称加解密计算会产生更多的服务器性能和时间上的消耗,但是这是可以优化的,这里就不细说了。
首先了解一下同源策略:
不建议使用JSONP,因为JSONP通常在脚本中写一个回调函数,然后把回调函数的名字写在请求的URL中,因此如果请求数据的服务器被黑了,那么黑客就能在返回的数据中植入恶意代码,从而窃取用户的隐私信息。
跨域资源共享CORS允许资源提供方在响应头中加入一个特殊的标记,使你能通过XHR来获取、解析并验证数据。这样就能避免恶意代码在你的应用中执行。在响应头中加入的标记如下:
|
|
如果对Access–Control-Allow-Origin设置为*其实是比较危险的,如果没有携带会话认证意味着信息被公开在全网,建议设置具体的域名,而且跨域的时候记得带上session id;严格审查请求信息,比如请求参数,还有http头信息,因为 http头可以伪造。
CSP指定网站上所有脚本和图片等资源的源站点,也能阻止所有内联(inline)的脚本和样式。即使有人在页面评论或者留言中嵌入了脚本标签,这些脚本代码也不会被执行。可通过两种方式设置,如果 HTTP 头与 Meta 定义同时存在,则优先采用 HTTP 头中的定义:
通过HTML的Meta标签,比如只允许脚本从本源加载:
|
|
其他策略:
缺点:
默认情况下,所有的内联JavaScript脚本都不会被执行,因为浏览器无法区分自己的内联脚本和黑客注入的脚本。
CSP还会默认阻止所有eval()风格的代码的执行,包括setInterval/setTimeout中的字符串和类似于new Function(‘return false’)之类的代码。
利用iframe进行跨源:HTML5为iframe提供了安全属性 sandbox,iframe的能力将会被限制。
Secure能确保cookie的内容只能通过SSL连接进行传输。Secure和HttpOnly属性告诉浏览器cookie的内容只能分别通过HTTP(S)协议进行访问,从而避免了被轻易窃取,比如禁止从JavaScript中的document.cookie访问,因此cookie在浏览器document中不可见了。如果单独使用的话,无法全面抵御跨站点脚本攻击,通常和其他技术组合使用。使用方法如下:
X-Content-Type-Options 告诉浏览器相信此服务器下发的资源的类型,防止类型嗅探攻击。
HPKP(Public Key Pinning) Public Key Pinning 是一个response 头,用来检测一个证书的公钥是否发生了改变,防止中间人攻击。
HSTS (HTTP Strict-Transport-Security) 强制使用TSL作为数据通道。
html5有很多新的特性能力,然而能力越大,被攻破后的危险就越大。
HTML5 对xss的影响主要体现在:
<video>,<audio>,<canvas>
等;比如localstorage只能通过js设置和获取,导致的结果是不能像cookie一样设置httponly等属性,所以localstorage中不能存放敏感信息,最好能够在服务端进行加密,可以配合CORS来获取网站的localstorage的信息。
响应式布局简而言之,就是一个网站能够兼容多个终端,可以为不同终端的用户提供更加舒适的界面和更好的用户体验。
比如凹凸实验室博客页面在PC端、iPad、手机端的排版:
PC端:
iPad:
手机端:
估计很多人对这句话都有体会:IE虐我千百遍,我待IE如初恋。当然,除了 IE 上有兼容性问题,其他浏览器比如 Android 上的低版本浏览器也有较多问题。
是否继续保持对低端浏览器的兼容性,我们可以用数据跟产品经理或者老板说话,减少我们的工作量,最好在项目之前就定下来支持最低支持的版本是什么,然后设计一个对应兼容方案。以下是百度统计的2015年的浏览器市场份额数据:
兼容性的原则:渐进增强与平稳退化。就是说,在低级浏览器能够保证其可用性和可访问性;渐进增强,在保证代码、页面在低级浏览器中的可用性及可访问性的基础上,逐步增加功能及用户体验。
如果出现兼容性问题了,怎么处理:
淘宝首页在兼容性上做了一个小创新:Html钩子
在html上加上操作系统、浏览器内核、浏览器类型、CSS3动画支持、IE各版本类,好处在于:
淘宝首页html钩子:
兼容性问题是老生常谈的问题了,团队之间共同努力形成一个bug兼容性积累文档,是最好不过的了。
网站需要有一个良好的导航,控制根目录和各子目录的关键,通过sitemap可以帮助网站主了解网站结构,也方便搜索引擎收录整个站点。
信息无障碍一般可以从以下几点入手:
具体可参考无障碍阅读
通过前端动画技术给页面进行优化,比如:
requireJs框架特性:
场景如下:
页面一:去一个网站买东西,未登录状态下,进入首页;
页面二:然后新窗口打开任意页面,登录并成功返回。
再次访问页面一,发现页面还是未登录状态,实际上用户已经登录了,这种体验是很差的。我们是不是有什么办法可以实现多标签状态同步呢?有的,利用Page Visibility:
能提高前端工作效率的那些事
基于Gulp的前端自动化
繁星网的前端性能优化之路
前端性能优化—-yahoo前端性能团队总结的35条黄金定律
前端性能数据之采集和分析
Web性能API——帮你分析Web前端性能
前端工程师如何系统地整理和累积兼容性相关的知识?
玩转HTML5移动页面(优化篇)
HTTP/2 与 WEB 性能优化(一)
HTTP/2 与 WEB 性能优化(二)
HTTP/2 与 WEB 性能优化(三)
HTTP/2 头部压缩技术介绍
从零开始学web安全(1)
关于Web安全,99%的网站都忽略了这些
网页前端常见的攻击方式和预防攻击的方法
Web客户端安全性最佳实践
HTML5 安全问题解析
10步大幅提升网站可访问性
Page Visibility(页面可见性)API介绍、微拓展
这次京东云改版的项目, 除了需要搭建一个放置京东云文档的平台, 还包括了文档录入的工作. 之前使用Hexo做过HaloJS的文档平台, 在文档录入的时候并没有碰到太多问题. 本以为搭建文档页面也就仅此而已, 直到碰到了长达104页的doc文档…
不同于其他零散的页面, 文档页面是一整个系列的, 具有相同的页面结构. 页面头部, 底部, 导航等组件. 如果像平时制作静态页面那样每个页面都从一个空白的模板html文件开始下手, 在开发量达到一定程度的时候, 页面整体的维护就会非常困难. 首先是无法很痛快地进行某些公共模块的修改: 就算只是在底部加多一个链接, 或者在导航中删除一个条目, 我们都需要对每个页面都进行手动修改. 另外, 页面的资源管理会越来越难. 每个页面都需要引用整体页面的样式文件与脚本文件, 还都有页面私有的样式文件, 脚本文件, 还有图片文件等. 如果这些资源共用一个文件夹, 有时候就会面临文件命名的难题.
如上面说, 文档页面可谓是重复工作最严重的一种页面. 好在搭建框架的小伙伴非常给力, 使用了同样很给力的开源的前端工程化开发工具Athena,自带支持代码热更新的预览服务, 自带压缩代码, 编译sass/less的工具, 自带上传sftp的工具等等. 而对于这个需求来说, 最好用的莫过于ejs模板语法的支持了. 有了模板, 公共的组件在不同页面都可以随意引用, 工作量减少了非常多.
每个页面都由组件组成, 平时写页面的时候只需要点名引用需要的组件. 搭建完成后, 使用Athena工具的build命令, 就可以将页面的静态文件输出. 对于这批文档的页面, 每个页面会被拆分为头部组件, 底部组件, 导航组件. 如果开发过程中某个组件产生变化, 只需要直接修改组件, 再重新运行build命令, 就可以使每个引用了这个组件的页面都一起变过来, 可谓十分方便.
录入文档可能是这一整个需求最痛苦的工作了. 一百多页A4纸大小的DOC文档, 还需要依据设计稿重新设置样式, 是十分机械化的工作. 在这里, 我的小伙伴将文档中的不同样式建立为不同的组件, 比如一级标题组件, 或是二级标题组件等等. 这些组件使用起来就像下面这样:
|
|
组件本身是一个ejs模板:
|
|
这样一来, 我们写页面的工作, 变成了写ejs组件, 再用widget.load函数加载自定义的组件, 用getCSS/getJS函数调用对应的css/js文件资源. 把页面中比较通用, 复杂的组件拆分出来, 有利于将页面模块化, 代码复用可以更爽快.
具体到这个项目, 文档录入是个非常痛苦的活. 按照目前的方案, 文档里面的一级标题, 二级标题, 正文, 图片等, 都是由组件构成, 像<h3 class=“xxxx”>xxxx</h3>
这样的简单代码, 也被做成了组件. 个人认为这样的做法不太科学. 这种太过细小的组件反而使录入文档变成了十分痛苦的事情. 上面所说的情况, 一个标签可以解决的问题, 变成引用组件的工作后, 代码量并没有多少差别:
|
|
左边是引用组件的写法, 右边是组件内部的代码. 可以看见, 过度模块化导致了细小组件的产生. 这里应该有更加方便的做法.
Python中有个第三方模块docx2html, 实现了将docx文件转化为html代码的功能, 并且转化完的代码十分纯净, 只有html结构与内容, 不带样式, 很适合在导出后再重置样式. 针对这个项目, 我们可以做个小改造, 每个页面依然由头部, 导航还有底部组成, 而文档内容则可以定义一个css类doc_mod_content
, 表示其中是文档的内容, 而文档的各种样式, 则写在这个类的后代选择器上. 如果标签的种类无法满足区分特定节点的需要, 还可以通过编辑器的搜索替换功能去批量添加特定的类. 这样的话, 首先是少去了开发组件的工作, 文档的录入也会轻松很多. 遗憾的是, 由于时间紧迫, 这个需求并没有使用这种方式. 过后有机会可以尝试. 文档录入本来就该是个轻松的活.
对于普通的页面, 侧导航跟页面主体并不需要分开, 直接放置即可, 就像hexo的文档页面一样. 但前面说过, 文档页是非常长的. 对于这种超长的页面, 使用上述的实现方式, 有可能会出现滚动到页面内容中间, 看不见导航的窘迫局面.
看见这种情况, 产品的小伙伴就该找我们了. 重构哥哥, 我想让导航始终在视野里面. 你能实现不?
Of Course! 只需要把导航栏设置为position: fixed就ok了. 不过对于这种页面中同时还存在头部与底部的页面, 会比较麻烦. 跟交互的童鞋商量过后, 我们把方案确定了下来. 具体而言, 侧导航共有三种状态:
position: static
position: fixed
position: absolute; bottom: 0
, 或者使用JS控制导航条的底部对齐页面内容底部. 再具体一点说, 这三种状态的触发条件分别是:
就算如此, 这样的实现还是会有一些问题存在, 比如在跳转到某个页面之后, 导航的高亮条目不一定会在视野中, 这会导致在不同页面间跳转的过程中, 导航条不固定, 找不到高亮条目的尴尬.
在当前实现方式下, 并没有特别好的办法能解决这种情况. 不同页面的长度不是相同的, 导航的位置跟页面的位置关系处于上面三种状态中的哪一种也是难以预测的, 就更没办法将导航移入视野中了. 说到底, 这种情况都是由于页面和导航太长导致的. 腾讯云的文档页面也是这样处理的.
那么, 在不缩短页面导航的情况下, 有没有办法做好这样的页面的效果呢? 答案是有的, 并且不止一种.
首先, 有一种比较折中的方案(当然, 要先问问产品的小伙伴同不同意), 把导航中的高亮条目抽出来放到导航的第一位. 参考各种基于JSDoc3的文档页面. 下面是PIXI的文档:
放到项目中的话, 这种办法大有曲线救国的味道: 不惜修改几十个页面中的导航顺序来达成高亮条目的固定. 这种机械化的工作, 做起来已经很累人. 如果做完后产品的小伙伴对效果不满意…. 是的, 还得几十个页面一个一个的改回来. 当然, 我们也可以预先把老版本备份一下, 不满意了就直接覆盖文件完事. 这一切做起来是那么的麻烦. 但如果是搭配着Athena工具使用, 就简单多了. 导航栏组件中用数组menu来储存导航栏条目信息, 只需要用forEach就可以输出所有的菜单项. 如果要将高亮条目与第一项对调, 只需要一句话就完事:
|
|
是不是有点爽?
上面这种方法, 说实话是有点不完美的. 用户当然不希望侧导航的位置一直不固定. 在这里, 我们可以使用页面局部刷新技术来规避这个问题. 如果能够在跳转到其他文档的时候不刷新页面, 而是在脚本中通过异步请求获取到新的文档的内容. 这个内容可以是json, 也可以是一个html文档, 甚至是一个html片段. 过后我们再将其用合适的姿势展示在右侧内容区. 结果上看, 页面仅仅是刷新了内容区, 导航位置不会改变, 比起之前的版本, 效率还会更高.
这个项目做下来, 最后还是留下了一些遗憾的地方. 迫于时间, 感觉手头还有很多的技术没用上, 或者还有许多的方案没实现. 要做好一个项目, 还是需要从项目一开始就与产品, 交互, 视觉等业务上游团队保持良好的沟通, 对整个项目拥有相当的了解, 闭门造车不可取, 用户体验才是王道.
]]> 在本文正式开始之前,笔者先提提图片优化的一般方法,了解图片优化的方法有助于理解后面图片加载实现形式的多样化。
一般常见的图片优化方法有:
可以从
两个途径来进行对体积大小的优化。
这里推荐阅读《Web性能优化:图片优化》,博主在文中讨论如何更优地选择图片的恰当格式以及推荐了优化图片大小的工具。
减少图片资源请求数(合并HTTP请求)的途径除了最为普遍的
还有
这里特别提下,采用DataURL这种方式将图片被转换成base64编码的字符串形式的,如果单纯地“嵌”入HTML中是不会被缓存的,但是加在CSS或JS文件中,通过缓存CSS或JS则达到了间接缓存以base64编码的图片。
这里推荐一个将图片转换成Base64编码的字符串的在线工具: DataURLMaker在线工具
因为本文的重点不在于讨论图片优化的内容,所以这里就简单总结下对图片进行优化的方法,对于更详细的内容,笔者则按下不表了。
对于触屏页面中常见的的图片资源加载方式,笔者归纳为三类:正常加载、预加载、懒加载。
所谓正常加载,则是开发者不采用人为地方式去干扰,按照浏览器正常加载的方式去加载渲染页面。
适合采用正常加载的方式的情景是图片数量较少以及图片体积较小,对触屏页面呈现的用户体验不影响或影响较少。
但是在现有的大环境中,限制于网络宽带等客观的因素,而且触屏页面的华丽炫目的设计都需要依靠图片,单纯的CSS是无法满足要求的,对图片的应用还是有一定量。
所以,能够毫无顾忌地采用正常加载的方式的案例还是比较少的,目前笔者暂时是没有见过。
在触屏页面处理中最为常见的可以是说预加载,几乎每个触屏页面的案例都使用到了这种方式。
为了完整地呈现页面给用户,开发者会通过一定的技术预先加载图片资源(以及页面其它资源),等加载渲染完毕再把页面呈现给用户。
而根据这个预加载的过程是否有明显的进度提示,笔者把常见的预加载方式划分为两种形式:
显性预加载指的则是处于预加载过程时页面有明确的加载提示,比如进度条或者是Loading图标。
譬如,我厂的触屏页面案例《点燃你心中的野兽》,在预加载过程提示加载的进度,让用户有个心理预期,减少等待的烦躁感。
以下截图来源自京东的《点燃你心中的野兽》
这个案例下的预加载处理技术是,先把页面DOM结构放入JS模版中,预加载完成后再把页面从JS模版拎出来,页面渲染完后能够完整地呈现页面给用户。
其中,图片资源相关预加载处理代码段为:
通过创建image元素,设置其src,就简单方便可以预先发起HTTP请求,实现预加载图片。
除了对png/jpg等格式图片能够利用以上方法实现预加载,对于DataURL转换图片为base64编码的字符串也能够实现预加载。
在Adidas的《罗斯·决不凋谢》中,利用DataURL协议将图片转换成base64编码的字符串写入JS中,预加载JS文件完成后通过JS来进行内联在DOM元素中。
以下截图来自案例《Adidas:罗斯·决不凋谢》![img](//img.aotu.io/Tingglelaoo/20160302/rose.png ‘示例图片’ ‘{“style”:”display:inline-block;max-width:200px;max-height:200px;width:100%;”}’ %}
这里贴出该案例中部分预加载处理代码代码,有兴趣的可以详见0.main.js、2.main.js、3.main.js处深入研究。
以下为Chrome Dev Tool 对案例资源查看的界面截图
虽然,业界有一种说法是不建议在移动端开发中使用DataURL转换图片为base64编码的做法,因为对大量的base64图片解码比较损耗内存和性能,但是使用base64在一定条件下是有助于页面加载速度提升,具体的原由可以前往《图片资源Base64化在H5页面里有用武之地吗?》阅读。
除了明确的预加载提示,还有一种是通过推进触屏页面进度的趣味互动的方式,笔者称之此种类似的情况为隐性预加载。
截图来自案例《大众点评:阿惠故事1 - 吃饭别带姥爷》![img](//img.aotu.io/Tingglelaoo/20160302/ahui.png ‘示例图片’ ‘{“style”:”display:inline-block;max-width:200px;max-height:200px;width:100%;”}’ %}
在大众点评的《阿惠故事1 - 吃饭别带姥爷》的预加载中,先是显性预加载(钱币小图标Loding)快速地呈现部分画面(截图右部分)给用户,再通过互动(向上滑动数钱)来继续分段加载资源。这样的做法一改用户往日沉闷的等待,能够给予用户更好的体验。
还有一个比较特别的案例是大众点评和欧莱雅合作的《好年从头开始》,笔者也将之归纳为隐性预加载。
截图来源于案例《大众点评:好年从头开始》
在这里,开发者先预加载了部分图片以呈现首屏画面给用户,同时图片预加载依然在进行,当用户触发的页面内的图片资源还未加载完时则会显示进度条,加载完毕则可以进入下一个画面。
笔者觉得采用这种方式的原因有二,第一,页面内容需要加载的资源过多,若等待全部加载完毕所需时间耗费过长,容易导致流失用户;第二,页面内容足够吸引,即时在交互过程中插入等待过程,用户也愿意去等待。
懒加载,又称为延迟加载、按需加载。指的是图片在页面渲染的时候先不加载,页面渲染完成后在指定动作触发后再加载图片。
这种方式通常比较合适于篇幅较长的页面,并且图片内容的重要性低于页面信息内容,能够快速地先将重要的页面信息呈现给用户。
譬如,这种方式则应用于电商商品信息集合页面,同时也常用于文章阅读的情景下。除了在移动端的应用场景之外,在PC端懒加载也是应用最广泛的一种图片加载优化方式。
最为常见的一种懒加载技术是以可见距离作为触发点。当页面滑动到屏幕可见区域时,则进行加载图片。实现的原理是:
具体可以前往拇指期刊点击文章阅读体验喔。
希望这篇文章能够带给读者一些启发。并不是某一种案例只使用一种加载方式。同时,也不是触屏页面的加载方式仅限于以上方法之一。
这里仅是列举并讨论常见的加载方式,凡事讲究实践出真知,在实践的时候必然会有更加切合具体案例的好方法。
Keep Moving,在触屏页面的开发中,兴许还有更多更优秀的图片资源加载方式等着我们去发掘。
]]>文中案例搜集均来源于数英网:)
通过Navigation Timing记录的关键时间点来统计页面完成所用的时间,并通过Chrome开发工具来跟踪细节
|
|
以上定义来自chrome官方文档,在其它环境下也许会有差异,从测试结果看,下面的build时间在android+微信环境中一直是0,对此可能是因为渲染机制差别,此处不做深入测试。除osx+chrome之外环境的数据仅作参考。
|
|
一张大小为50kb的jpg格式图片,应用到9x15=135个dom做背景图,模拟雪碧图的模式,多个节点引用同一张图片做背景,(示例)如图。测试环境
:Mac OS X EI Capitan 10.xx + Chrome 48.xx其它辅助测试机器
: iPhone 6 plus iOS 9.xx; 魅族Note Android 4.xx
实际使用过程中,其它版本和机型的Android手机还有待测试
关闭缓存状态下,build:150ms | complete: 200ms(总时间受网络状态等因素影响,数据做比较用)
开启缓存状态下,build: 7ms | complete: 59ms(包括以下稳定状态下多次测试的平均值,截图为最接近平均值的状态,默认数据来自Mac+Chrome[48.XX版本])
测试环境 | build(单位:ms) | complete(单位:ms) |
---|---|---|
OS X+Chrome | 7 | 59 |
iOS+微信 | 45 | 90 |
OS X+Safari | 50 | 100 |
Android+微信 | 0 | 120 |
将上面50kb大小的jpg图片转换为base64格式,加在css文件中。
关闭缓存状态下,build:80ms | complete: 280ms
开启缓存状态下,build: 160ms | complete: 210ms
测试环境 | build(单位:ms) | complete(单位:ms) |
---|---|---|
OS X+chrome | 160 | 210 |
iOS+微信 | 35 | 100 |
OS X+Safari | 9 | 90 |
Android+微信 | 12 | 150 |
调整上面图片的(压缩品质)体积,base64化后,对应的css文件大小也跟着改变
图片大小 | 10kb | 20kb | 45kb | 100kb | 180kb |
---|---|---|---|---|---|
对应css文件大小 | 27kb | 42kb | 76kb | 150kb | 260kb |
Rendering时间 | 30ms | 46ms | 81ms | 156ms | 258ms |
50kb大小的图片,base64化后,调整引用图片做背景图的dom的个数
引用次数 | 10 | 20 | 50 | 100 | 135 |
---|---|---|---|---|---|
Rendering时间 | 15ms | 19ms | 44ms | 74ms | 83ms |
在OSX+Chrome环境下,将50kb的图片base64后放入样式中,build过程拉长了约20倍,使用Timeline工具可以看到,计算样式阻塞了整个过程。
大小10~70kb共9张图片。总大小约300kb
关闭缓存:build: 300ms | complete: 310ms
开启缓存:build: 110ms | complete: 120ms
测试环境 | build(单位:ms) | complete(单位:ms) |
---|---|---|
OS X+Chrome | 110 | 120 |
iOS+微信 | 50 | 100 |
OS X+Safari | 148 | 150 |
Android+微信 | 50 | 100 |
将上面的图片转成base64后,放在js文件中,加载进来。
关闭缓存:build: 0ms | complete: 400ms
开启缓存:build: 0ms | complete: 80ms
测试环境 | build(单位:ms) | complete(单位:ms) |
---|---|---|
OSX+Chrome | 110 | 120 |
iOS+微信 | 0 | 35 |
OS X+Safari | 7 | 70 |
Android+微信 | 0 | 250 |
使用上述1、2的测试demo分别在3G、4G网速条件下测试结果如下:
测试环境 | 图片直接加载 complete(单位:ms) | base64合并请求 complete(单位:ms) |
---|---|---|
3G | 6000 | 4500 |
4G | 450 | 400 |
WIFI | 320 | 340 |
base64后的的js资源达381kb,在一个线程里加载,消耗大量时间,从统计结果看,在渲染性能差异上并没有场景1那么明显。
但有缓存的情况下,页面渲染完成的速度甚至更快。
从Timeline里看到细节,解析这个近400kb的js文件对整个渲染过程造成了一定压力,不过总共40ms的解析时间是完全可以接受的。
图片资源的base64编码进css文件会带来一定的性能消耗,需谨慎使用。
将图片资源编码进js文件中,管理和预加载H5应用的图片资源,合理的合并请求可以大大提高页面体验。
PreloadJS是一个用来管理和协调相关资源加载的类库,它可以方便的帮助你预先加载相关资源。
LoadQueue是一个加载管理器,可以预先加载一个文件或者一个文件队列。
LoadQueue包含了几个可以订阅的事件:
LoadQueue支持相关文件类型如下:
|
|
|
|
|
|
|
|
通常进度条可以通过fileload
和complete
来实现。
一个音频播放引擎,能够根据浏览器性能选择音频播放方式。将音频文件作为模块,可随时加载和卸载。
|
|
registerSounds
|
|
|
|
|
|
FFmpeg and the ogg codecs on OSX using brew:
|
|
|
|
可选参数可以设置是否自动播放等,具体请参考这里
把生成好的json文件,导入到以下链接,才能供soundjs使用
http://jsfiddle.net/bharat_battu/g8fFP/12/
TweenJS类库主要用来调整和动画HTML5和Javascript属性。提供了简单并且强大的tweening接口。支持数字对象属性和CSS样式属性,允许你使用链式语法来生成复杂的过程
|
|
get
获取目标元素to
执行动画set
设置属性wait
队列等待call
执行回调函数createjs.Ease.bounceInOut
缓动效果
|
|
EaselJS 是一个用以与 HTML5 canvas 协作的库。它包含一个完整的分层展示列表、一个核心交互模型以及一些辅助类,通过其来使与 Canvas 的协作更简单。
下面介绍这个例子的制作过程
人物精灵图会用到flash+zoe来生成
打开flash,新建一个新的as3项目,导入图片,只要文件名是有序的,会自动导入到每一帧,如下图
导入完之后,新建一个图层,来放动作标签,如上面的run
和jump
最后再新建一个新的图层用来放动作脚本,这里只需要写stop()
就可以了,这样整个动画是处于静止状态。
然后用快捷键Ctrl + enter
发布成swf格式,并把它导入到zoe工具,效果如下
tab选项卡切换到Animations
可以预览和设置刚才在flash设置的动作。
最后导出json代码供后续使用
createjs中,元素都是放到舞台中
|
|
|
|
Shape
类,通常用于画图,例如画矩形,圆形时会使用到
|
|
|
|
Bitmap
类,常用于位图处理
|
|
人物用到Sprite
类,精灵图存放在SpriteSheet
中,刚才用flash + zoe 生成的json,可以直接传入到SpriteSheet
初始化对象中。
还可以直接使用代码gotoAndPlay('run')
来进行动作间的切换。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
开启Docker学习之旅前,我们简单描述几个场景,应该很多人都有碰到过:
小凹同学开发了一个web应用,服务器环境是: centos 7
+ nginx
+ node4.6
+ mongodb3.2.3
最近要上线了。
【场景1】:刚好公司有一台服务器可以用,但是服务器上有一些其他服务,而且已经装了node3.31
和mongodb2.3
,小凹蒙了,到底是直接升级环境呢?还是改一个适配低版本的应用呢?
【场景2】:终于花了很久时间部署上了,慢慢的项目需求越来越多,小凹的同事小凸也准备一起迭代这个项目,小凸又要重新配置一遍应用环境到本地做测试,随着越来越多的同事参与进来,每个人都要配置一遍本地测试环境,重复工作,时间又白白浪费掉。
【场景3】:随着项目越做越大,现有服务器配置和带宽已经不能满足了,小凹需要把这个项目迁移出去并做水平扩展,然后又得重复配置环境到多台服务器,而且这些服务器有可能还是会重复前面的场景1。
【场景4】:运维要清理一些服务器,整理出来,把没有完全利用的服务器,分给新的项目用,要罗列出来每台服务器的服务,然后删除掉不需要的,这时候发现完全无从下手。
上面的场景经常发生,也许就发生在你我身边,而且无比头疼,但对Docker来说解决这些问题都易如反掌,下面我们就一步步来了解并使用Docker。
Docker是一个开源的引擎,可以轻松的为任何应用创建一个轻量级的、可移植的、自给自足的容器。开发者在笔记本上编译测试通过的容器可以批量地在生产环境中部署,包括VMs(虚拟机)、bare metal、OpenStack 集群和其他的基础应用平台。
官方的描述是:Build, Ship, and Run Any App, Anywhere 在任何地方部署,传输,运行任何应用。
其实很像虚拟机,但是跟虚拟机比起来 更灵活,速度更快,CPU/内存消耗更低,关键是更方便管理。
上图就是一张Docker层级图, 最下面是核心系统,文件系统等构成Dokcer底层,上面的是镜像(image),分为基础镜像和普通镜像,所有镜像可以直接启动生成一个实例(container),container我们可以理解为一个可以直接运行的虚拟机了。
其中基础image 启动后生成container,然后添加一些应用如 apache
,emacs
,可以通过提交操作直接生成普通的image。 我们可以共享这个image到任何地方,并启动它。
Docker 可以安装在 Linux
, Mac OS
, Windows
上,详细安装步骤可以参考 官方安装文档 。
注:本文就不一一举例所有的安装方法,就以操作系统 centos 为例。
由于docker 只支持 centos6
以上,64
位的版本操作系统,所以安装前可以用以下命令查看服务器系统。
上面是我使用的机器 centos7
64
位系统,我就以这个为例讲下面的使用步骤。
如果 安装失败 可以尝试使用阿里云的代理安装。
其他更详细的阿里云Docker代理加速器文档,可以点击这里
安装成功后启动Docker,并设置开机启动。
注:假定我需要部署一个基于centos
的应用,当然也可以换成别的,比如:Redis
或者 Ubuntu
,可以是任何镜像来当做基础镜像,跟本机原有系统无关,只与所要部署的应用有关。
首先搜索需要获取的 镜像,这是官方Docker Hub
提供的镜像资源。
比如,我选择获取 centos最新版本
|
|
使用命令 docker images
就能查到刚才获取的 centos
的镜像,如上图。
注:为了方便演示,假定我们的服务只需要安装一个zip
的centos
系统。
将上面获取的 centos
镜像,启动生成container,并在container中安装zip。
|
|
docker run
启动容器,-t
:为容器重新分配一个伪输入终端,通常与 -i 同时使用; -i
:以交互模式运行容器,通常与 -t 同时使用;centos
为镜像名, 镜像名通常以 镜像名:版本 来使用,因为centos没有版本所以省略, 后面 /bin/bash
是启动container后运行的程序。
使用container命令行安装zip
成功。
退出容器命令行,因为不是后台运行,所以退出后,container也随即关闭了。-d
可以让container在后台运行,并可以随时通过docker attach
命令进入容器,具体例子可以看这里,因为不是本文重点就不展开说明了。
docker ps
命令可以查看当前启动的 container, -a
启动和没启动的都会展示。
|
|
有一个id为 8f08b2b67380的container,这个就是我们刚才安装了zip,并退出的container。
docker commit
将container提交生成image, -m
:为描述, 后面紧接着的是container的id, test:1
是提交的镜像名称和版本。
|
|
再查看的时候已经多了一个image了,这个image就是我们安装了zip的image,到此我们已经生成一个新的image。
我们可以直接通过这个新的镜像启动容器,还是前面介绍过的命令docker run
并测试下zip,如下:
好了这个镜像已经制作完成,当然实际情况下,安装的肯定不仅仅只有zip这么简单。
但是在一个团队里,仅仅给我们image,我们可能不知道这个镜像到底做了什么,所以还有一种方法使用 配置文件Dockerfile
,build出来一个镜像,这样更易于团队协作,下面我将介绍一下这种方式。
开始之前顺便介绍删除image和container的命令,它们分别是docker rmi
删除image 和 docker rm
删除container,后面都是跟对应的id或者名称,为了后面的操作我们这里通过命令 docker rmi 8f1d192a4ea2
删除掉刚才建的image。
注:如果发现删不掉image,可能被某些容器引用了,可以通过上面介绍的docker ps -a
查看container,并用 docker rm
删除掉这个container。
接着我们创建Dockerfile
下面是 Dockerfile
中的内容
#
为注释,FROM centos
为基础镜像来源,MAINTAINER
为作者信息, RUN
则为 运行某些 命令,编辑完成后保存,然后我们就可以直接构建我们新的image,Dockerfile详细使用文档可以参看这里。
|
|
docker build
就是通过Dockfile来创建一个新的Image,其中 -t
:为新image的名字这里命名为test:1, "."
则会在当前的目录下找 Dockerfile
文件,当然这里也可以指定路径。
|
|
就这样,通过docker images
也能生成一个新的镜像了,这种用Dockerfile的方式更加适合团队使用,环境配置更清晰。
到这里,我们只需要管理我们的镜像就好了,比如同步镜像给其他人,或者其他机器。
其实docker就给我们提供了这样一整套的解决方案,我们可以把我们的镜像提交到 Docker Hub,类似 github
一样的远程仓库,当我们需要的时候只需要 pull下来启动就好了。
我们就把这个test镜像提交到Docker Hub
首先我们得先注册:https://hub.docker.com/
注册成功后就可以docker login
登录了
填完登录后就可以push了,注意这里push之前得确保名称是 youruser/xxx
比如我的用户名是 a569171010 所以我需要将刚才的image 重命名成 a569171010/test:1,这里可以用docker tag
命令重命名。
|
|
然后就发布出去了,在 Docker Hub 上就可以看到下面多了这一条记录,当我们要获取的时候就直接 docker pull a569171010/test
就可以了,因为是公用库所以任何人都可以获取并使用。
我们已经基本熟悉,了解了整个Docker使用的流程,回过头来看看开始我们抛出的那几个曾经很棘手的问题,是不是都能迎刃而解呢?因为image的可移植和隔离性,我们不仅可以轻松迁移扩展,还能轻松了解现在机器上各个服务运行情况。
下图为Docker整个使用流程:
Docker 官方文档
Docker 中文指南
dockone.io 国内比较活跃的Docker论坛
Docker 入门介绍
Docker github
阿里镜像使用文档
维基百科
这篇文章只是带领大家了解Docker的整个使用流程和体验,关于其他的一些细节问题比如:后台运行container,各container之间的通讯,端口映射,文件共享等都没有涉及到,最近docker1.1.0
发布又有很多新的特性和性能优化,如果有兴趣的同学可以参考这些:
npm scripts
替代gulp
现在前端自动化
的配套工具估计都离不开gulp
或者是grunt
,有一些或许会用上webpack
辅助用上最新的ES6
语法等;但是不知道大家在使用gulp
众多插件的时候有没有碰到过一些问题,比如:有一些插件你仅仅需要用到其中一点点的API、插件更新速度非常慢、有一些插件碰到bug的时候调试起来非常麻烦等。所以总结一下gulp
或者grunt
其实都会有以下问题:
而如果直接使用npm scripts
完全可以避免这些问题,在我们package.json
里面的scripts
属性直接定义需要执行的任务,比如npm start
和npm test
其实就是npm run start
和npm run test
的缩写,我们可以在scripts
里面定义各种需要的任务,举个最简单的例子(清除dist目录):
|
|
从上面示例代码可以看出明显直接用npm scripts
实现的同一个功能相对gulp
要简单得多,当然这个功能比较简单,如果碰到复杂的一些任务肯定就有反对的声音了。那我们将细细将上面三点来阐述。
当你需要使用到最新的或者不那么流行的技术时,根本就没有插件给你使用;或者一些插件已经过时了。最新Babel 6
已经发布,很多API明显修改了,所以很多gulp
插件根本不适用于最新版本。
这个时候你就必须等待作者来更新插件,或者你自己去fix
这些问题,这会导致你不能及时用上最新版本的工具。相反,当你直接使用npm scripts
的时候,你只需要直接找到可以实现的工具即可。这意味着当新版本的Mocha
、Babel
、Webpack
、Browserify
发布的时候,你就可以马上用上这些版本。
就目前插件数量来说,没有什么可以打败npm
包:
由于gulp
增加了一层抽象,所以会有潜在的bug
:
Grunt
/Gulp
插件崩溃了?而直接使用npm scripts
直接避免了第2点跟第3点,而由于不使用那么多插件,那么包相对较少,第4点也很少会碰到。
相比有用过很多插件的人都知道,一些核心的工具文档写得总比包装起来的Gulp
插件要清晰得多。举个简单的例子来说,如果我需要用到gulp-eslint
插件,那么就可能会不断在gulp-eslint
的文档跟ESLint
网站切换,必须对比看看两者存在些什么区别。
npm scripts
而更青睐于Gulp
Gulp
和Grunt
之所以这么流行,主要有下面4个点:
npm scripts
需要能写命令行的技能npm scripts
能处理的能力不足够Gulp
的流对于快速构建是很有必要的npm scripts
不能跨平台运行npm scripts
需要能写命令行的技能其实你完全不需要精通于Unix
或者Windows
的命令行脚本,比如你不知道在Unix
下面删除一个目录的命令是:rm -rf
,这其实没啥问题,你完全可以使用rimraf,同时它也是跨平台的。在这里推荐一个工具包资源网站:libraries.io
npm scripts
能处理的能力不足够npm scripts
其实比你想象中的要强大,主要依赖于预处理和后置处理钩子,比如下面例子:
|
|
正如上面例子一样,prebuild
定义的脚本会比build
任务先执行,而postbuild
定义的脚本会比build
任务后执行,因为相对于build
来说,增加了一个前缀pre
和post
,所以当我执行npm run build
的时候会自动地顺序执行prebuild -> build -> postbuild
。
同时你可以将一个大的任务不断拆分成小的任务,比如:
|
|
在上面例子中将clean
任务抽离出来了,当你执行npm run build
的时候,会先自动执行npm run prebuild
任务,那就相当于执行了npm run clean
任务了,注意上面的&&
表示先后顺序执行,区别于&
表示同时执行。
npm scripts
的一些缺点不得不承认,用npm scripts
来写自动化构建任务还是存在一些不足:不能在JSON文件里面写注释。有一些方法可以弥补这方面的不足:
推荐使用第一种,脚本名字本来就应该能够直接描述功能。
每个用户的请求,都会经过这几个阶段:
|
|
前三个过程都会消耗一定的时间,因此我们应该分析每个阶段的耗时,进行针对性优化。
假设你是用 Express 作为 API 服务器,你可以利用 Express 官方的 response-time 和 StatsD,
将每个中间层的请求数据都收集并统计起来。
但在 Express 世界之外,还有一个更专注于做 API 服务器的框架,叫 restify。
restify 是一个纯 Restful 的框架,它可以结合 DTrace 去记录一个用户请求中,每个环节消耗的时间。
图中高亮的部分是 restify 对于请求耗时的记录:
此外,restify 还有着更多强大功能,包括请求频率控制、内置 Ajax 错误类型、基于 bunyan 的日志。
参考以下几个步骤,通过可视化的角度,揪出消耗 CPU 的凶手。
启动 Node 项目时,增加 --perf-basic-prof-only-functions
参数,如:
|
|
用 perf 生成 Node 进程的栈信息文件(stack trace)
|
|
下载 FlameGraph 并生成可视化的栈信息火焰图
|
|
最后会生成类似这样的图片:
解释这种图片的含义:
如果方块横向越长,说明这个函数消耗的 CPU 时间越多。
这样,你就可以定位到这个函数,深入代码去定位问题了。
可以让 Node 在运行过程中,记录自身的运行状态,并崩溃的时候输出调试信息。
而这些调试信息被称为 Core Dump,会被保存在一个文件中,我们称之为 Core 文件。
Core 文件记录了进程运行时的一切状态,包括调用栈、内存变量、被调用的函数源码等。
有了这个文件,我们就可以最大化的还原出当时应用运行的过程。
下面我们利用 mdb_v8 工具,这个目前最好的 Node 命令行分析工具,结合一个简单案例来演示。
配置 Solaris 环境
由于 mdb_v8 只能运行在 Solaris 环境,因此你有两种选择:
使用 Joyent 公司收费的 Manta 服务,请参阅这里
笔者使用第一种方式,即通过虚拟机运行 mdb_v8,详见 Vagrant 安装 OmniOS 指南。
启动 Node 项目时,增加 --abort-on-uncaught-exception
参数,让应用在崩溃时输出 Core 文件
本文用以下会崩溃的代码测试,生成 Core 文件。
|
|
|
|
将 Core 文件和 Node 二进制程序打包,传到 Vagrant 虚拟机内
|
|
使用 mdb_v8 解析 Core 文件
|
|
分析崩溃原因
5.1. 使用 mdb_v8 的 jsstack 指令,查看最后的调用栈情况
|
|
5.2. 从上面信息得知,最后一个被调用的函数是 increment
,因此可以查下该函数的地址
|
|
5.3. 查到地址值后,就可以查出函数当时的局部变量
|
|
结合代码可知,由于 count
数值已经到达 1000,导致应用崩溃。
5.4. 当然,通过以下命令,还能查到 count
属性所在对象的起始状态和结束状态
|
|
对于 mdb_v8 更多的 Node 指令,请参阅这里。
上个问题中,我们用 Node 的 --abort-on-uncaught-exception
参数,让应用在崩溃后输出 Core 文件。
但如果应用一直在运行(即无崩溃),可以用 Linux 自带的 gcore 命令,导出 Core 文件并分析内存泄露的原因。
本文用以下代码测试,该代码会导致内存泄露
|
|
用 node xxx.js
命令运行代码即可。
每隔一段时间,用 gcore 对上述代码所在进程进行 Core Dump
|
|
利用 mdb_v8 提供的工具 dumpjsobjects,提取 Core 文件中的 JS 对象,并输出文件
|
|
利用 mdb_v8 提供的工具 mdbv8diff,进行 JS 对象对比
比较两个时期的 JS 对象的异同,即可获得未被释放的对象地址。
|
|
打印出来的结果为:
|
|
用 mdb_v8 打印 135f38df83d9
内存地址对应的对象
|
|
从结果可以发现,该对象的实例一直在内存里未被释放。
本文参考了 Netflix 工程师 Yunong Xiao 的演讲分享,在此感谢。
希望该文章可以给读者更多解决 Node 生产环境调试的思路。但对于生产环境中面临的各种复杂问题,也许需要更多的手段才能解决。
References
]]>做前端开发的童鞋或许不会太陌生。
如果你完全不知道它是什么东西,可以在它的github项目上了解下:https://github.com/inconshreveable/ngrok
这里只提下它的核心功能:能够将你本机的HTTP服务(站点)或TCP服务,通过部署有ngrok服务的外网伺服器暴露给外网访问!
如上封面图所示,举一个栗子。
由此可见,除了Weinre、browsersync 这些惯用的手段外,借助ngrok,也一样可以解决前端开发过程经常遇到的“本地开发,外网调试”老大难题。
囧的是:ngrok.com被墙了,我们已无法用它官方的服务~
国内虽然有一些第三方的ngrok服务,但是也无法保证其稳定性。
还好ngrok是开源的,我们可以通过它的源码在自己的外网服务器上搭建自己的ngrok服务。
前提条件是:一台外网可访问的主机,且有域名解析至该主机上。
ngrok是利用go语言开发的,所以先要在服务器上安装go语言开发环境。
以CentOS的服务器示例,安装Go语言很简单的:
|
|
安装完毕后,利用go version来验证是否安装成功。
go安装好后,我们再设置下go的环境变量:
在~/.zshrc
或~/.bash_profile
文件内,加入以下环境变量配置内容:
|
|
保存后,重新给shell加载下配置文件:source ~/.zshrc
最后可通过go env查看是否配置成功。
安装过程略。后面我们需要利用git拉取源码。
下面编译过程需要改官方的部分源码,所以最好fork一份源码至自己的github账户。
|
|
源码拉取下来后,需要修改一个地方:
打开src/ngrok/log/logger.go
文件
将code.google.com/p/log4go
修改为:github.com/alecthomas/log4go
googlecode已经寿终了,我们将依赖的log4go替换成github的版本。
在编译ngrok的源码之前,我们还需要改下官方源码用到的签名证书。
使用ngrok.com官方服务时,我们使用的是官方的SSL证书。自建ngrokd服务,如果不想买SSL证书,我们需要生成自己的自签名证书,并编译一个携带该证书的ngrok客户端。
证书生成过程需要一个NGROK_BASE_DOMAIN。 以ngrok官方随机生成的地址693c358d.ngrok.com为例,其NGROK_BASE_DOMAIN就是”ngrok.com”,如果你要 提供服务的地址为”example.ngrok.xxx.com”,那NGROK_BASE_DOMAIN就应该 是”ngrok.xxx.com”。
我们这里以NGROK_BASE_DOMAIN=“ngrok.fex.im”为例,生成证书的命令如下:
|
|
执行完以上命令,在ngrok目录下就会新生成6个文件:
|
|
ngrok通过bindata将ngrok源码目录下的assets目录(资源文件)打包到可执行文件(ngrokd和ngrok)中 去,assets/client/tls和assets/server/tls下分别存放着用于ngrok和ngrokd的默认证书文件,我们需要将它们替换成我们自己生成的:(因此这一步务必放在编译可执行文件之前)
|
|
在ngrok目录下执行如下命令,编译ngrokd:
|
|
类似的,利用以下命令编译ngrok:
|
|
成功编译后,会在bin目录下找到ngrokd
和ngrok
这两个文件。
我们将ngrokd文件拷贝至~/go/bin
目录下,以方便在其他目录内也可以直接通过ngrokd来访问该执行程序。
|
|
添加两条A记录:ngrok.fex.im
和*.ngrok.fex.im
,指向fex.im所在的服务器ip。
至此为止,我们的ngrokd服务端搭建配置完成,同时我们在CentOS系统的服务器上编译了一份客户端的执行程序-一个ngrok文件。
如果你的开发机器系统也是CentOS,是可以直接将ngrok这个客户端执行文件拷贝到本地开发机器中来使用的。
但如果你的机器是Mac 或者windows,我们还需要在自己的电脑中编译一份相同签名文件的客户端程序!
注意:请记得提交已更改的源码至github,一会还要用到。
服务器是CentOS,自己的工作电脑是Mac,所以得在自己的电脑中编译一份相同签名文件的客户端程序!
与服务器的步骤类似,我们首先要安装go语言环境:
|
|
最后将编译好的ngrok文件,拷贝至$GOPATH/bin目录内,以便在命令行内任意目录内均可以直接通过ngrok运行程序。
ngrokd服务配置好了,客户端程序也有了,下面测试下ngrok是否能够正常使用。
创建一个ngrok配置文件:ngrok.cfg
|
|
运行客户端,暴露8080端口的站点
|
|
在8080端口下建一个测试站点
方便起见,我们拉取git@github.com:o2team/brand.git做测试:
|
|
在浏览器中输入demo.ngrok.fex.im:8088
bingo!
在浏览器中输入:localhost:4040
可以查看所有的请求情况!
客户端ngrok.cfg中server_addr后的值必须严格与-domain以及证书中的NGROK_BASE_DOMAIN
相同,否则Server端就会出现如下错误日志:
|
|
本文主要介绍了ngrok服务的自行搭建。同时为大家免费提供我搭建好的ngrok服务:ngrok.fex.im。
fex.im所在的机器是digitalocean的一个主机,虽然国内速度慢但是还算稳定。
Linux 下载:http://fex.im/files/ngrok
Mac OSX 下载:https://github.com/mamboer/ngrok/releases/download/1.7.2/ngrok
放在 /usr/local/bin
目录下
|
|
|
|
|
|
|
|
那么,接下来,我们从状态码出发,梳理一下3xx状态码(300-307),以及缓存一系列相关的东西。(注:HTTP/1.1版本的内容)
客户端请求一个实际指向多个资源的URL时会返回这个状态码。
比如说,一个站点提供了中文和英文两个版本,理想情况下,服务器应当向中文用户发送中文版,向英文用户发送英文版。HTTP提供了内容协商方法,允许客户端和服务器作这样的决定。内容协商包含客户端驱动的协商、服务器驱动的协商以及透明协商。
客户端驱动由客户端发起请求,服务器发送可选项的列表,客户端进行选择;服务器驱动是由服务器来检查客户端的请求首部集并决定提供提供哪个版本的页面(q值机制,Vary首部);透明协商是由某个中间设备(通常是缓存代理)代表客户端进行请求协商。
而300 Multiple Choices属于客户端驱动的协商。服务器发送回HTTP/1.1响应时,使用300 Multiple Choices响应代码。客户端浏览器收到这种响应时,可能会弹出对话窗口,让用户选择。
在请求的URL已被移除时使用。响应的Location首部中应该包含资源现在所处的URL。
301重定向是网页更改网址后对搜索引擎友好的最好方法,只要不是暂时搬移的情况,都建议使用301来做转址。比如说,我们将网页a的地址更改为b,采用301重定向,搜索引擎可以肯定网页a永久性改变网址,搜索引擎就会把网页b当做唯一有效目标,同时,网页a积累的页面权重将被传到网页b。(注:传递权重不同的搜索引擎不一样,Google会传递大部分权重,但不是百分之百权重)
与301状态码类似,但是客户端应该使用Location首部给出的URL来临时定位资源,将来的请求仍应使用老的URL。
一般302重定向是在一个网站或网页在短时间内临时移到其它位置的情况下使用,这时候就是做临时性的跳转了。
但是302跟网址“劫持”有着莫大的关系。大部分搜索引擎在大部分情况下,当收到302重定向时,一般只要去抓取目标网址即可。但是有时候搜索引擎(以Google为例)并不能总是抓取目标网址,比如说a网址很短,但是它做了302重定向到b网址,而b网址是一个很长的乱七八糟的URL网址,这时候Google很有可能仍显示网址a,这时候就造成了网址劫持的可能性。如果一个居心叵测的人将一个网址a通过302重定向到你的网址b,而Google搜索结果仍然是A,这种情况就是网址劫持。同时,还容易导致网站被降权,所以尽量不用。
用来告知客户端应该使用另一个URL来获取资源,新的URL位于响应报文的Location首部。
303主要目的是允许POST请求的响应将客户端定位到某个资源上。比如说,在文件上传完成后让客户端自动重定向到一个上传成功的结果页面。
如果客户端发送了一个带条件的GET请求且该请求已被允许,而文档的内容(自上次访问以来或者根据请求的条件)并没有改变,则服务器返回304状态码。304响应不包含消息体,因此以消息头后的第一个空行结尾。
在一条HTTP GET请求中,大致是如下的一个过程
在查看本地副本是否过期时,通过检查Cache-Control或者Expires首部即可获知。HTTP通过Cache-Control首部和Expires首部向每个文档附加了一个“过期日期”,在缓存文档过期之前,缓存可以以任何频率使用这些副本,而无需与服务器联系。
Expires首部是HTTP/1.0定义的字段,指定的是一个绝对的过期日期。而Cache-Control是HTTP/1.1定义的字段,max-age值定义了文档的最大使用值,是相对时间。因为绝对时间需要依赖于计算机时钟的正确设置,容易存在误差。所以,我们更倾向于使用比较新的Cache-Control首部。当同时存在Expires和Cache-Control:max-age字段时,Cache-Control:max-age字段会覆盖Expires字段。同时存在这两个字段还有一个好处,就是可以兼容HTTP/1.0。
本地缓存过期了并不意味着它和原始服务器上目前处于活跃状态的文档有实际的区别,这时候缓存需要询问原始服务器文档是否发生了改变,跟原始服务器做一次 新鲜度校验。
HTTP条件方法可以高效地实现再验证。通过If-Modified-Since或者If-None-Match首部来校验。
If-Modified-Since:Date再验证:If-Modified-Since首部可以和Last-Modified服务器响应首部配合工作。原始服务器会将最后的修改日期附加到所提供的文档上去。
If-None-Match: 实体标签再验证:有些文档可能被被周期性的重写、有些文档可能被修改了但所做修改不重要等等这类情况下,使用最后修改日期进行再验证是不够的,这时候HTTP允许用户对被称为实体标签ETag的“版本标识符”进行比较。
实体标签和最近修改日期都是缓存验证器,如果服务器回送了一个实体标签,HTTP/1.1客户端就必须使用实体标签验证器。如果只返回了一个Last-Modified值,客户端就可以使用If-Modified-Since验证。如果实体标签和最后修改日期都提供了,客户端就应该使用这两种再验证方案,这样HTTP/1.0和HTTP/1.1缓存就都可以正确响应了。如果HTTP/1.1服务器收到的请求既带有If-Modified-Since,又带有ETag条件首部,那么需要这两个条件都满足时,才能返回304 Not Modified响应。
用来说明必须通过一个代理来访问资源,代理的位置由Location首部给出。
客户端是相对某个特定资源来解析这条响应的,不能假定所有请求,甚至所有对特有所请求资源的服务器的请求都通过这个代理进行。如果客户端错误地让代理介入了某条请求,可能会引发破坏性行为,而且会造成安全漏洞。所以,305这个状态码也较少使用。
在HTTP1.1废弃掉了,不再使用,并且此状态码被保留。在HTTP1.1以前的版本中,306 Switch Proxy用于表示随后的请求应该使用指定的代理。
请求的资源临时从不同的URI响应请求,客户端应该使用Location首部给出的URL来临时定位资源。由于这样的重定向是临时的,客户端应当继续向原有地址发送以后的请求。
除非是一个HEAD请求,否则307响应的实体中应当包含指向新的URL的超链接及简短说明。因为部分浏览器不能识别307响应,添加URL超链接以便不能处理307状态码的用户有能力在新URL中发起重定向请求,也就是说,把重定向的页面展示给用户,让用户去点重定向URL链接。
我们可以看到Chrome对于hsts列表的域名,采用了307进行重定向。
以上内容讲述了301-307的状态码,对于这些状态码以及一些缓存的首部可能还有一些疑惑,以下简单地列出了几点。
303和307是HTTP/1.1新增加的状态码,它们是对HTTP1.0中的302状态码的细化。当HTTP/1.0客户端发起一个POST请求,并在响应中收到302重定向状态码时,它会接受Location首部的重定向URL,并向那个URL发起一个GET请求。透过以上的状态码介绍,我们可以发现HTTP/1.1规范使用303状态码来实现同样的行为。为了避开这个问题,对于HTTP/1.1客户端,用307状态码来代替302状态码进行临时重定向。
在HTTP/1.1中,302理论上是可以被303和307代替掉的,但为了兼容HTTP/1.0,依然还在广泛使用。
Cache-Control: no-cache 响应实际上是可以存储到本地缓存区中的,只是在与服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用。
如果需要本地彻底不缓存,需要使用Cache-Control: no-store,表示缓存应该尽快从存储器中删除文档的所有痕迹。
]]>CocoaPods 是托管在 github 上的,所有的 Pods 也都是托管在 github 上,因此我们首先需要创建一个属于自己的 github 仓库,如下图所示:
上图中标识出了6处地方
上面各项根据大家需要填写完毕后,点击 Create repository 按钮即可,创建成功后如下所示:
为了方便向仓库中删减内容,需要将仓库 clone 到本地,这里有多种操作方式,可以选择你喜欢的一种,但是为了方便这边选择使用命令行。首先需要切换到你想在本地存储的目录,然后再 clone ,假设你放在用户的根目录上:
|
|
完成后,我们进入到 ~/O2View
目录中应该可以看到目录结构如下:
|
|
其实还有一个隐藏的 .git 文件,后续我们的所有文件都在这个目录底下进行。
创建 Pods 依赖库就是为了方便别人使用我们的成果,比如我想共享给大家的O2View类,那这个类自然必不可少,我们把这个类放入一个 O2View 的目录中:
顺便看看 O2View.swift 主要内容:
|
|
每个 Pods 依赖库必须有且仅有一个名称和依赖库名保持一致,后缀名为 .podspec 的描述文件。这里我们依赖库的描述文件名称应该为 O2View.podspec
。
创建这个文件有两种途径:
我们使用命令行:
|
|
创建出 O2View.podspec 文件后,我们打开可以发现,该文件是 ruby 文件,里面有很多的内容,但是大多数都是我们不需要的,所以我们只需要根据项目的情况保留关键的一些内容就行:
|
|
为了快速教会别人使用我们的 Pods 依赖库,通常需要提供一个 demo 工程。我们创建了一个名为 O2ViewDemo 的工程来演示 O2View 的使用,如下图所示:
使用 github 的人应该都熟悉这个文件,它使一个成功的 github 仓库必不可少的一部分,使用 markdown 对仓库进行详细说明。
CocoaPods 强制要求所有的 Pods 依赖库都必须有 license 文件,否则验证不会通过。 license 文件有很多中,详情可以参考 tldrlegal。前面我们已经选择创建了一个 MIT 类型的 license。
以上的5个文件是创建 Pods 依赖库所需的基础文件,当然 Demo 工程没有添加也没关系。添加完这些内容后,我们本地仓库目录就变成这个样子:
经过前面步骤,我们已将在本地的 git 仓库添加了不少文件,现在我们只要将他们提交到 github 上就可以。在此之前我们需要对刚才添加的 pod 进行一下验证:
|
|
运行之后可能会得到下面的警告:
|
|
由于我们现在还没有正式生成 release 版本, github 上并没有任何 tag,所以我们刚才填写 .podspec 文件填写 git 地址的时候没有填写指定 tag (上面文件的注释中有提到),解决方法我们可以先执行忽略警告的命令:
|
|
如果成功会出现如下输出:
|
|
当调试完成了之后,我们需要在 github 上把我们的代码生成相应稳定的 release 版本,到时候我们再回来添加指定 tag 发布就 Ok 了。
验证成功之后,我们只要把代码提交到 github 仓库,就可以了,参考命令:
|
|
这里主要是 git 的范畴,不做过多叙述。如果前面操作都没有问题的话,github 上应该能看到类似如下内容:
经过前面的步骤,我们的 CocoaPods 依赖库就已经准备好了,但是现在你还不能在你的工程中使用它。如果你想成为一个真正可用的依赖库,还需要最后一步操作:将刚才生产的 podspec 文件提交到 官方的 CocoaPods Specs 中。
没错,我们平时用的能用 pod search
搜到的依赖库都会把它上传到这个仓库中, 也就是说只有将我们的 podspec 文件上传到这里,才能成为一个真正的依赖库,别人才能用!
按照 github 的规则,要想向别人的库中添加文件,就要先 fork 别人的仓库,做相应的修改,再 pullrequest 给仓库的原作者,等到作者审核通过,进行 meger 之后就可以了!
流程大概就是这个样子,具体可以参考:CocoaPods Guides,我们就先不展开详细的叙述了(后面会添加关于这部分的文章)。
除了官方的 specs 之外,我们还可以把 podspec 文件提交到私有的仓库中,详见我们博客的另一篇文章:创建私有的 CocoaPods repo spec 。
]]>打开隐藏目录,可以使用命令:
|
|
接着需要重启一下 Finder, 可以按住 option + 右键 Finder 图标选择重启。然后打开隐藏目录 .cocoapods (这个目录在你的个人目录底下, ~/.cocoapods
或 /Users/[username]/.cocoapods
)
可以看到上图, .cocoapods 目录下的 repos 其实是 repository (仓库)的缩写。 repos 中存放的时仓库的集合。这里的 master 就是 CocoaPods 官方建立的仓库,也就是我们所谓的公共库。
specs 目录下存放的就是所有提交到 CocoaPods 的开源库的 podspec 文件的集合。
其结构如下图所示:
其组成方式:
specName – version – specName.podspec
( master 里现在是 specName.podspec.json,这个 .json 文件就是 podspec 文件加了个后缀。我们可以采用 pod ipc spec
这个命令来将 spec 文件转换成 .json)
那这个文件夹是如何来得呢?其实,这个文件夹是在你安装了 CocoaPods 后,第一次执行 pod install
时, CocoaPods 通过命令 pod setup
来建立的(这条命令会自动执行)。
上述是官方的 repo ,私有库的话可以看下我们前面的翻译或者查看官方文档,但是官方文档有一些过程写的不是特别详细,自己实现的时候遇到了一些坑,特此梳理一下过程,按照官方文档内容实现一个私有的 spec repo 。根据官方的原理,我们只需要创建一个和 master 相类似结构的目录就可以实现私有的 spec repo,当然你可以 fork 一个官方的 repo,但是它包含了太多没有必要地库在里面,这在编译仓库的时候需要浪费大量的时间,所以我们只要自己重新创建一个就可以了。
在你的 git 服务器上创建一个 repo。这里你可以在 github 或者你自己的 git 服务器上实现。公司内部使用的话这里推荐大家使用私有的仓库,但是 github 的私有仓库是要收费的 $7/month (壕请无视 - -!)。大部分公司都是有自己的git服务器,不过一般只能在公司内网使用,或者可以选择Gitlab,Coding, Bitbucket任意一种。我们以在 github 上创建为例:
如图所示,我们在 github 上创建一个空的仓库,命名为 O2Specs ,这个仓库是用来存放我们自己所有的私有库的 spec 文件,就如同官方的 https://github.com/CocoaPods/Specs 是用来存放所有官方的 specs 文件一样。
然后我们就可以执行:
|
|
其中的 REPO_NAME 是我们要添加的私有 repo 的名称(这里我们待会填的是: O2Specs),后面是仓库的 git 地址。这里做的其实是创建的工作,也就是在 ~/.cocoapods/repo
目录下添加了一个以你的私有 repo 为名的文件夹,但是并没有添加 spec 文件。
在终端执行命令:
这里用的是 https 地址,当然你也可以用 git 地址,不过这样有可能失败。如果失败,很大因素是你 github 的 ssh 在本地没有配置好,配置方法可以看这里:Generating SSH keys,成功后可以看得到 .cocoapods 目录如下图所示:
至此,我们已经在本地得到我们自己的私有仓库 O2Specs ,这是一个空的仓库。
这个步骤需要我们事先完成 CocoaPods 依赖库的搭建,具体可以查看我们另外一篇教程:制作 CocoaPods 依赖库。如果这里你想先看看效果,也可以直接从 github 上 clone 示例工程继续下面的操作。
让我们进入到上面 clone 的示例工程目录中(或者你自己搭建的依赖库目录中):
打开工程的 podspec 文件:
|
|
这个文件本质上是一个 ruby 文件,这是我们事先已经实现好的依赖库的 podspec 文件,如果前面步骤都正常的话,也就是说我们认为这个 podspec 是合法的话,就可以直接将这些内容 push 到我们本地的 repo 中。为了避免错误,我们可以再验证一下,命令行输入:
|
|
运行之后可能会得到下面的警告:
|
|
由于我们的示例工程还没有生成正式的 release 版本, github 上并没有任何 tag,所以我们刚才填写 .podspec 文件填写 git 地址的时候没有填写指定 tag (上面文件的注释中有提到),解决方法我们可以先执行忽略警告的命令:
|
|
如果成功会出现如下输出:
|
|
到此,我们的 O2View.podspec 就符合规范了。
在前面验证通过的基础上,我们接着执行命令:
|
|
执行完,如果失败会有相对应的警告和错误提示,只要按照警告和错误的详细信息进行修改和完善即可。成功的情况应该可以在终端看到类似的输出:
|
|
再进入到 .cocoapods 的目录中,可以看到已经发生了变化:
同时,我们还发现 O2Specs 的 github 仓库也已经发生了变化:
按照平时使用 CocoaPods 的习惯,我们添加依赖库之前会先搜索一下库,让我们执行一下命令:
|
|
哈哈,可以看到已经建立成功了!
|
|
看到前面的搜索结果,小伙伴们是不是开始有点激动了?让我们建立一个普通的工程,命名为 TestPodDemo,然后在终端 cd 到其目录路径下,添加一个 Podfile 文件:
|
|
建立后修改 Podfile 文件内容如下:
|
|
执行:
|
|
我们得到如下提示:
|
|
找不到 O2View ? 额- -!。。。刚才 search 明明可以找到这个依赖库的,为什么现在找不到了呢?
别着急!其实原因是你在 Podfile 中没有指定路径,当你执行 pod install
的时候,CocoaPods 默认只会在 master 下搜索,而我们的 spec 是存在我们私有的 O2Specs 目录下的。所以解决方式很简单,就是引入搜索地址即可,在 Podfile 的顶部添加如下两行代码:
|
|
这里必须添加两个地址,默认情况下,你如果不添加 source ,系统会默认使用官方仓库地址,但是当你添加了 source 之后,系统就不会自动搜索官方地址,所以这两句都必须添加,否则其他基于官方的依赖库都不能使用了。
再次执行 pod install
,我们就可以看到很顺利的安装成功了:
|
|
恭喜!这样一来我们就可以使用这个私有库了!
前面我们提到过,我们的这个实例依赖库 O2View 没有生成稳定的 release 版本。当我们调试完内容之后,一般都是要发布稳定版本的,更新之后再继续发布新版本。我们可以使用命令行或者在 github 页面手动生成,这里为了方便我们使用命令行,首先在终端中 cd 到之前的依赖库 O2View 的目录中,然后输入如下命令:
|
|
这样我们就得到了一个稳定的 release 版本 0.0.1:
这里我用的版本号是 0.0.1 基于研发版本,关于版本号的一些规范可以参考:语义化版本 2.0.0
对于我们的 podspec 文件,我们也需要将 s.source
做一下小小的改动:
|
|
这样我们使用这个依赖库的时候就能对应上版本号,并且知道在 github 中使用稳定的 release 版本代码。太棒了!
最后只需要再重复 push
一下我们的 Podfile 就可以!
好了,到这里我们已经完全掌握如何创建一个本地私有的 CocoaPods 了!我们完全可以把我们项目中得代码拆出来封装成一些 pods, 好好使用这个黑魔法!
祝大家玩得愉快!
]]>我开始熟悉原代码并尝试在此基础上进行迭代开发,但是由于功能上有较大的变更,而我没有用过 Mongodb 和 php,这时我更倾向于使用我熟悉的 MySql 和 Nodejs,在得到 EC 同学的允许后,我决定从零开始搭建系统。本文选择其中几点进行阐述。
第一步是建表,下图是最初的表设计,基本上按照原 mongodb 中的数据关系进行定义,设计好数据表,再把它们写进数据字典,最后才开始建表。
(数据字典对开发和协作的作用非常大,它能让你和别人知道表中有哪些字段,某字段代表什么等信息,在接下来的编码工作中还会经常去查看它。)
随着开发的深入,数据不断拓展,有些字段被淘汰或被拆分到新表,有些字段新加进来,比如代表“文件”的数据,原来以字符串的形式写在 demand 表的 files 字段,作为经常被读写的数据,以字符串的形式保存显然不正确,我们需要把每个文件当作一个数据元,于是把 files 字段拆分成新表 demandfiles,并增加了上传人、上传时间和下载次数这些信息。
为了让数据库设计更加合理,使用了外键约束,确保在删除修改数据的同时能级联删除修改,外键统一命名为 fk_table_field
的形式。修改后的 ER 图如下所示:
第一阶段
起初,项目后端使用了 ejs
模板引擎,拆分头尾在后端渲染模板后发回给前端,前端用 jQuery 进行逻辑的处理。
第二阶段
前端开始使用 Angular 替代 jQuery,但是 ejs 跟 Angular 好像有点格格不入,前者意在后端渲染数据,后者通常在前端通过 $http 获取数据来填充,这时的 ejs 的存在主要是为了组装拆分出来的头尾,不好去掉它。但是,接着发现了 ng-include
,这让组装头尾的工作交给了前端,从而摒弃掉了 ejs。
第三阶段
接着,切换页面的时候头部闪烁的问题引起了注意,原来是 Angular 使用方式不对,我的每个页面都是一个单独的 Angular app,每次切换页面时都引入一次头尾导致闪烁,于是引入 ui-router 把系统修改成一个单页面应用,更新 ui-view
即可切换页面。
这又引出了一个新的问题,所有页面的入口都是 index.html,也就是说不能再简单地在后端通过路由来控制页面的访问权限了,这样另一个概念就登场了——本地身份验证。简单点说,就是在进入应用主入口 index.html 的时候,从服务器请求身份信息,身份信息将保存在 service
中(类似 session storage),然后在状态(页面)切换事件 stateChangeStart
或 stateChangeSuccess
中,对页面请求进行拦截或重定向等操作。
然而,网络请求都有一定的延迟,在身份信息返回来之前,某些页面的内容不应该被呈现出来,所以要在获取到身份信息后通知到页面,于是就加进了事件广播和接收器 $broadcast
和 $on
:获取到身份信息后通过 $broadcast
广播一个事件,在子页面中通过 $on
接收到该事件后执行相关逻辑。
v1 版
背景:需求排期时间不能冲突,排期由排期起始和结束时间决定。
从服务器获取的源数据形式如下,已按 startDate 排好序,每个用户数据中包含排期数据(data):
|
|
在前端计算好周起始时间 ws
和周结束时间 we
,对每条排期数据进行判断,如果排期时间跟本周的时间有交集,则计算该排期都出现在了哪几天,比如下图的情形,这里有两个排期:
数据将会按下面的形式(blocks)附加到用户数据中,其中 [0,1,2] 和 [4,5] 的数字就分别表示了两个排期在周排期表中占据的位置。
|
|
接着,处理排期表中的空闲的天,给用户数据附加以下数据:
|
|
然后在页面上由 schedule
循环输出 td,blocks[0] 和 blocks[1] 的长度就是 td 横跨的 td 个数。
v2 版
背景:v1 版的排期表设计中,由于排期的最小单位是“天”,而存在数据库中的 startDate 和 endDate 的最小单位却是毫秒,在计算上无疑增加了大量纷繁的计算,受移动组 task 系统的启发,把排期数据改为 startDate 和 days(所需天数),这样可以减少一半的计算,也更好理解。另外较大的改动,排期时间将允许有重叠(即同一天可以排多个需求)。
从服务器获取的源数据的形式不变,这次我在计算之前把时间都转化成了天(1970-01-01至今的天数)以方便计算,为了解决排期堆叠的问题,添加了两个关键的变量:
|
|
weekpile 原理如下图,每个排期排在第几层由它在排期表中的第一天所在层级决定。
层数记录附加到用户数据中,如下:
|
|
另外,上文提到的 wee
变量存储的是指向 blocks
索引的数字,把它也附加到用户数据中。如图,外层 div 的 margin-top
由 wee
所指向的第一个排期的 pilefloor
决定,比如下图假设这是星期六,那么外层 div 的 margin-top
由 blocks[wee[6][0]].pilefloor
决定。
因为已知排期的天数 n,给每个排期的宽度设置相应的 n*100% 即可。
然后在页面上由 wee
循环输出 td,blocks[n] 中的 weekdays 的 length 决定了排期的长度。
v2 版排期表如图所示:
最初 UFT 主要由我进行开发,后来另一个同事加入开发,变成我负责 UFT 的功能开发,她负责 UFT 的 UI 优化,项目源码放到 github 上了,我们要在 github 共同进行开发,我们两人琢磨了好久,面对冲突问题时开始还采取过删除后重新拉取暴力做法,几经尝试,我们最终探索出两种较好的协作方式:
在本地修改后,先从服务器上拉取更新合并:
git fetch origin master
然后进行合并
git merge FETCH_HEAD
一般情况下,合并会顺利进行,但难免会有冲突,一旦有冲突,就调用 mergetool 进行合并,或者直接在原文件上手动合并。最后,就是正常的提交过程了。
关于 mergetool,自从同事分享过一次,回去后自己装了 p4merge,配置如下:
|
|
但是,没有冲突是调用不了 mergetool 的。
临时文件的处理
临时文件指的是异步上传到 temp 文件夹的文件,用户上传文件后如果不打算进一步提交了,这些临时文件就成了“弃儿”,如果不对这些文件进行处理,temp 文件夹会越来越大。于是加进了定期任务,每隔一段时间对临时文件夹中的文件进行清理,为了防止把用户刚上传的文件清理掉了,被清理的文件的最后访问时间要在3天之前。
UFT 从无到有,很多东西边学边用:第一次尝试用 Express,配设置、配路由等过程遇到不少坑;第一次用 Angular,用得并不规范,多亏涛哥从中相助,解决我不少困惑;Angular 各种各样插件的学习使用过程,就是不断试错,不断筛选满意插件的过程。
除了知识上吸收,最大的收获是意识到项目规划的重要性。开始接手后就马上开始写代码,需要什么功能就写什么,以至于最后需要经常较大地改动代码,比如,列表之前用 Bootstrap-table 写,然后改成 Angular,又比如将页面合并成单页面 Angular 应用时,需要把大量的插件都换成 Angular 插件,还有项目的结构也几经变换。折腾这几番足见项目前期的规划还是非常重要且必要的。
UFT 原来的目标是供部门内部使用,用户访问系统需要先经过内部员工帐号的身份认证过程,然而,随着内部推出了新的需求管理平台并推广到部门,UFT 被闲置,为了让 UFT 重新焕发活力,在接下来,把源码开源,并将把 UFT 改造成通用的需求管理平台。目前平台仍有不少可以优化的地方,例如:
小规模游戏一般由一人或两人完成,不需要详细的产品说明等资料,直接进入代码的编写,然后在浏览器中解释并运行。有时候出现错误,重新编辑再回到浏览器解释运行,就是一个简单的编辑-解释-运行的重复工作模式。
大规模游戏一般由两人以上完成,一共分为三个阶段:设计阶段、说明阶段、实现阶段。首先是设计阶段:设计什么游戏,游戏的主体用户是谁,游戏的目的等。然后是说明阶段:清楚游戏需要的类和类中需要的方法,分别做一个规划,保证编写代码时思路清晰,以及代码的整洁性。最后是实现阶段,其实就是进入编辑-解释-运行的工作模式。也就是说,大规模游戏比小规模游戏多了两大阶段即设计阶段和说明阶段而已。
|
|
|
|
注意边缘的棋子的样式控制,另外鼠标经过时添加红色虚线提醒。
处理函数需要什么操作,只是把棋子画到相对应的棋盘即可吗?
画棋子之前需要了解一下事件委托:利用事件冒泡,只制定一个事件处理程序,就可以管理某一类型的所有事件。
它的好处在于占用内存少,假设有一个父元素ul,有100个li子元素,如果给100个li子元素都绑定事件,相当耗内存,事件委托的原理就是只需要给父元素绑定事件即可。
不过它的使用是有条件的,它要求事件会冒泡,会冒泡的事如click、mousedown、mouseup、keydown、keyup和keypress事件。
冒泡的过程大概如下:div -> body -> html(ie5.5-跳过) -> document -> window(ie9,fx,chrome,safari)。
因此我不需要给棋盘的每个格子绑定事件,只需要绑定棋盘(格子的父元素)即可。代码如下:
由于在父元素中绑定事件,我们知道,事件目标是被单击的单个五子棋格子。因此,需要检测 id属性或其他属性 来决定采取适当的操作。
如果不检测是否是我们想要的事件目标,可能导致错误。
代码如下,如果targetID不是我们的事件目标,可能i,j的数据则不正确,从而导致访问对应的五子棋格子gobangArr[i][j]出错:
首先设置count = 1。count的值代表在同一个方向连续在一起的棋子总数,达到5个则该方赢。
如水平方向按照如下顺序执行判断,其他方向雷同:
代码如下,一共检测四个方向:
主要是清除棋子以及棋子数组,另外设置默认数据:默认黑棋先下,重置提示语,重新给棋盘绑定方法。一方赢了的话,默认移除画棋子方法的,所以需要重新给棋盘绑定方法。如果理解了文章上面讲述的方法的话,重置游戏是比较简单的,因此不再赘述。
最后根据五子棋的主要方法,总结一下js事件。
即直接在HTML代码中添加事件处理程序。不推荐使用,原因如下:
html事件处理程序缺点:时差问题、代码紧密耦合、扩展事件处理程序的作用域链在不同浏览器中导致不同结果。
类似onclick等事件处理程序属性,通常全部小写。
DOM2:可添加多个事件处理程序,按顺序触发。
使用方法:
绑定函数addEventListener() 与 解绑函数removeEventListener():参数为事件名(如click)、作为事件处理程序的函数、布尔值。布尔值为true时代表捕获,false代表冒泡阶段调用事件处理程序,一般为false。
注意:若第二个参数为匿名函数,即使用removeEventListener()传入相同的匿名函数也无法解绑。
|
|
可添加多个事件处理程序,按相反的顺序触发。只有IE和opera支持。
在DOM0级中,this为所属元素的作用域内运行;但是在使用attachEvent()方法时,事件处理程序在全局作用域中运行,即this===window!!!
使用方法:
绑定函数attachEvent() 和 解绑函数detachEvent() : 参数为 事件处理程序名称(如 onclick )与函数。如:
|
|
|
|
对象this始终等于currentTarget,而target则只包含事件的实际目标。
例子:
|
|
跨浏览器解决方案如下:
|
|
在线地址: http://labs.qiang.it/qqpai/test/wcn/gobang/gobang.html
github地址:https://github.com/Newcandy/gobang
参考书籍:《Javascript高级程序设计》
Promise 是抽象异步处理对象以及对其进行各种操作的组件。 通俗点讲,Promise能解决由于回调嵌套带来的流程控制与可读性问题。
promise 已经是成为我们解决回调炼狱的常用方案,而且已经得到官方标准支持,如果你刚刚开始使用Promise,本文将帮助你了解几个常见的Promise的使用场景。
早在1976年就有人提出Promise 的概念。之后的计算机语言发展中,很多语言都提供了与 Promise 相关的特性。而对于Javascript语言来说,最早让大家广泛接触的 Promise 相关的库是由 jQuery.Deferred()
对象实现的。随着 Promise/A+ 标准规定了一系列 API,实现该标准的库如雨后春笋版涌现了出来,在最新的ECMAScript 2015中已经提供了Promise的内置对象,成为了基础库。
大部分原生的API函数并不支持Promise,还是基于回调来使用的,所以需要把一些方法改为返回一个 Promise 对象,这个过程被称为函数的 Promise 化。
下面一个例子将对定时器setTimeout
Promise 化。
Promise化本质上都属于一种Curry化。Curry化是指,将需要传递多参数的函数生成一个新的函数,如上代码先通过执行 timer得到一个新的函数,该函数会返回一个Promise,这样就完成了Promise化。将一些基础的函数进行Promise化,可以
大大减少不必要的代码。
下面的代码,将会体现这种优势:
变量promise_timer
赋予的函数,与函数promise_timer2
是等价的。 可以看出 setTimeout
Promise 化之后,代码程序可读性更强,代码量也变少了。
当某个函数需要在 N 个回调都完成时才执行,这个时候就可以使用Promise.all
来改善你的代码。
以下是一个图片并行加载的例子,当所有图片加载完成后,再将所有图片一起展示。
|
|
需要注意的是,Promise.all
中传入的 Promise 数组,各自 resolve 之后得到的值,将合并成一个数组传入到 then 中的方法,且数组中 resolve 值的顺序,与 Promise 数组的顺序一致。
在许多Promise示例中都可以看到类似如下的链式调用的代码。
|
|
如上面代码所示,我们可以很清楚的理解到程序执行的顺序是
但是如果我们对代码进行一点小的改造,将 then 中的方法不再返回 Promise ,那么执行的代码将会变成这样:
|
|
这是为什么呢?
因为每次调用 then 都会返回一个新的 Promise ,如果 then 中的申明的方法没有返回一个 Promise ,那么会默认返回一个新的
处于 fulfilled 的 Promise ,之后添加的 then 中的方法都会立即执行,所以执行的顺序就变成这样了:
当要在使用链式 Promise 时,请务必在then传入的方法中返回一个新的 Promise。
另外一个需要注意的是,resolve 传递给下个 then 方法的值只能有一个,上面 getTaskInfo 方法中是无法获取到 userInfo 的值,所以如果有多个值需要放在一个数据集合( Array , Object , Map , Set )中传入下个方法。
Promise 标准的API 中并没有提供相应的方法来 中断或者取消 Promise 链的执行,一些库中提供了类似Promise.break
或者 Promise.fail
的方法来中断或取消 Promise 链。利用Promise.catch
的特性来中断 promise链。
|
|
只要在 Promise 执行过程中抛出异常,都会直接跳转到 catch 中。但是这样的做法有一个缺点,无法区分程序本身的异常,还是手动抛出的异常。所以需要手动设置一个标识标量,来区分是为了中断执行还是本身的程序异常。
合理的利用 Promise ,可以让代码脉络更加的清晰易懂,流程控制,异常捕获也更加准确。当然为了使用 Promise 也要编写很多额外代码,
想要真正的解决回调问题还得期待ES7的 async
await
关键字的到来,不过在此之前,Promise 都将是解决程序流程控制的最优选择之一。
Chrome开发工具的Timeline面板监控了web应用运行时所有活动情况,不过它的功能很多,对于英文不好的童鞋,有点无从下手,下面直接上手来使用。
图中红框标出的部分是功能栏:从45到47版本,Timeline工具连续都有更新,当前截图的版本号是47.0;
- 开始/停止记录,打开Timeline面板时刷新页面会自动开始记录
- 清除已有记录
- 过滤内容
- 强制垃圾回收
- 视图模式,最新版本中点击切换两种视图模式
- 捕获内容选项,选中最后一项截屏的话会保留每帧的截图,记录数据的buffer空间会和快被占满
图中没被标出的部分是整个监控过程中的数据概览;红框标出的部分有两栏,上面是选中的时间段内每一帧的情况,下面是内存占用的变化。
浏览器渲染的速率达到60帧/秒,那么每一帧只有1000ms / 60 = 16.67ms的时间来响应,其中浏览器在每一帧还要做一些额外的事情,因为我们要保证每一帧的CPU time在12ms左右。
有红色三角形角标标出的表示当前帧消耗过多时间
点击选中一帧可以看到当前帧的详细情况,图中显示,当前帧的渲染消耗了30.8ms,可能会造成卡顿。
饼图中可能会有五个颜色:
- 蓝色: 加载
- 黄色: 脚本执行
- 紫色: 渲染
- 绿色: 绘制
- 灰色: DevTools不感知的活动
图中帧因为没有加载活动,没有蓝色区域。可以看出脚本执行和帧绘制占据了大部分面积,不过问题并不在这里,因为它们总共才花费7.82s。
在Chrome的45.0版本中,饼图里还有一种颜色:白色,表示刷新周期里空闲的时间。在这段时间里,一直在等待用户的响应,直到一个交互事件触发了页面视图的变化。最新的更新里在饼图中去掉了白色,区分为CPU time和帧的Duration,更加合理。这里引起视图渲染和绘制可能有:
使用requestAnimationFrame这个函数,将脚本执行和渲染流程联系起来,以避免在某一帧的中间突然执行脚本导致重新渲染和绘制而整个过程不能在16ms内完成。
这一帧比较极端,从图上看就是紫色部分花费掉了大量时间,我们就可以从这里入手来优化代码,在此之前先得了解一下浏览器绘制帧的整个过程。实际导致绘制帧卡顿可能是其中任何一个环节。
- Javascript: 除了使用Javascrit来实现视觉变化,还有CSS Animations、 Transitions。
- Style: 根据CSS选择器,对每个DOM元素匹配对应的CSS样式。
- Layout: 在上一步确定了每个DOM元素的样式规则后,这一步就是具体计算每个DOM元素最终在屏幕上显示的大小和位置。web页面中元素的布局是相对的,因此一个元素的布局发生变化,会联动地引发其他元素的布局发生变化。例如父元素的宽度变化引发子元素宽度变化,又联动的引发孙子元素的宽度变化。
- Painting: 绘制,本质上就是填充像素的过程。包括绘制文字、颜色、图像、边框和阴影等,也就是一个DOM元素所有的可视效果。一般来说,这个绘制过程是在多个层上完成的。
- Composite: 上一步的绘制是在多个层上进行的,在这一步,浏览器会将所有层按照合理的顺序合并成一个图层,然后显示在屏幕上。
说明,上述过程的解释总结或直接引用自谷歌开发者文档。
在以上5个过程里:
对于滚屏和动画,最理想的就是只使用transform和opacity来实现视觉变化效果,并且遵循第四点。
这是一个知名线上网站的首页,这里发生了滚屏触发加载内容,导致DOM结构变化,引发了大量内容的重绘和渲染,并且页面旧的内容没有回收,节点较多,明显的感觉到滚屏的时候有卡顿感。对于这个应用按照上述的思路来优化,在优化的过程中,我们也许会遇到下面这个问题。
这是另一个demo页面,有数千个节点组成,用以模拟无限懒加载数据的情况,其中只有一个节点上应用了循环的css3动画。其它的都不会改变。为了将有动画的节点独立到一个单独的渲染层中,我对它应用了translateZ(0)。本以为大功告成,结果在滚屏的时候发现非常卡顿。于是就有了上图,平均每帧的渲染都消耗掉了200ms左右,从Timeline监控数据中可以看到,其中Composite Layers这个过程耗费了大量时间。那么直接查看layer状态:
再看官方文档的提示:由于每个渲染层的纹理都需要上传到GPU处理,过多的渲染层来带的开销而对页面渲染性能产生的影响,甚至远远超过了它在性能改善上带来的好处。
实际上页面里并没有主动通过translateZ属性来独立更多的层,我们可以认为这是触发了浏览器的一个“bug”,最新版本的chrome仍然会触发这个问题,不过最新版本的ios里Safari已经不会触发这个问题了。至于什么情况会触发浏览器去创建一个独立层来渲染元素,例如拥有3DCSS属性的元素、使用加速视频解码的元素等,对这个问题这里就不再详述。
如果手动的将大量的层独立出来渲染,导致GPU罢工,或是任由他们导致CPU罢工,都会造成性能问题,不过有了Timeline工具,我们就能找到问题所在,一一去分析解决了。
https://developers.google.com/web/fundamentals/performance
https://developer.chrome.com/devtools/docs/timeline
Git
,如果对Git
只是一知半解,可以移步LV
写的 GIT常用操作总结,下面介绍到的一些关于 Git
的概念就不再赘述。
为啥想写这篇文章?主要是因为部门服务器因为安全性原因不允许SCP
上传文件进行应用部署,然后有一些应用是放在Github上的,然后部署应用的步骤就变成:
1.git clone github项目 本地目录
2.配置一下应用的pm2.json并reload
3.Nginx配置一下反向代理并restart
当然如果只是一次性部署上去就不再修改的话并没啥问题,但是要是项目持续性修改迭代的话,就比较麻烦了,我们就在不断的重复着上面的步骤。作为一个码农,怎么允许不断的重复同样的工作,于是Github webhooks
闪亮登场。
让我们看看 官方 关于Github webhooks的解释:
Webhooks allow you to build or set up integrations which subscribe to certain events on GitHub.com.
提炼出来几个点:
刚好符合了这几个条件,那接下来就看看如何进行网站自动化部署
,主要会从下面几点来讲解:
1.自动化shell
脚本
2.服务端实现
3.配置github webhooks
我之前翻译过一篇文章 使用Node.JS创建命令行脚本工具,但是我们现在的自动化脚本直接用 shell
来实现,假设名称为auto_build.sh
:
|
|
Note: 在执行上面shell
脚本之前我们必须第一次手动git clone
项目进去,例如:
|
|
shell脚本其实就跟直接在终端运行命令一样,类似于windows下面的BAT批处理命令,更多详细可以查阅资料。
Github webhooks
需要跟我们的服务器进行通信,确保是可以推送到我们的服务器,所以会发送一个带有X-Hub-Signature
的POST
请求,为了方便我们直接用第三方的库github-webhook-handler来接收参数并且做监听事件的处理等工作。
现在我们可以在shell
脚本的同级目录下面执行下面命令初始化一个package.json
:
|
|
然后执行下面命令安装上面提到的第三方库:
|
|
接下来创建我们的服务主入口文件index.js
:
|
|
紧接着参考github-webhook-handler
的demo
编辑我们的index.js
:
|
|
然后利用node管理工具跑起来服务,这里使用了pm2
:
|
|
到这一步服务已经跑起来了,但是对外网并不能直接访问到,所以还需要配置一下Nginx
做一下反向代理:
|
|
OK,到这里整个服务已经搭建完成,下一步就只需要配置Github webhooks
。
github webhooks
我们可以在我们的Github
上面最右边有一个Settings
的Tab,找到Webhooks & services
,如下图:
然后点击新建,输入Payload URL
跟Secret
,确定即可。
绑定成功之后,我们可以试试提交一下代码,然后来到Github
看看是否自动触发了接口,如下图:
然后随便选择一个点击一下,可以看到200
的响应:
上面就是利用Github webhooks
进行网站自动化部署的全部内容了,不难发现其实这项技术还是有局限性的,那就是依赖于github
,一般我们选择的都是免费github账号,所有项目都对外,一些敏感项目是不适合放置上去的。
这个时候就考虑这个组里同事推荐的 backup,自己还没试用,有兴趣可以了解了解。
]]>需求中常见的css3动画一般有补间动画(又叫“关键帧动画”)和逐帧动画两种,下面分别介绍:
补间动画/关键帧动画:
常用于实现位移、颜色(透明度)、大小、旋转、倾斜等变化。一般有Transitions
和Keyframes animation
两种方法实现补间动画。
Transitions:用于实现简单的动画,只有起始两帧过渡。多用于页面的交互操作,使交互效果更生动活泼。
CSS的
transition
允许CSS的属性值在一定的时间区间内平滑地过渡。
这种效果可以在鼠标单击、获得焦点、被点击或对元素任何改变中触发,并圆滑地以动画效果改变CSS的属性值。
Keyframes animation:用于实现较为复杂的动画,一般关键帧较多。
设置动画的关键帧规则。
animation
的timing-function
设置为ease
、linear
或cubic-bezier
,它会在每个关键帧之间插入补间动画,产生具有连贯性的动画。
逐帧动画:
animation的
timing-function
默认值为ease
,它会在每个关键帧之间插入补间动画,所以动画效果是连贯性的。
除了ease
、linear
、cubic-bezier
之类的过渡函数都会为其插入补间。
有些效果不需要补间,只需要关键帧之间的跳跃,这时应该使用steps
过渡方式。
更多详细用法请查看:《深入理解CSS3 Animation 帧动画》。
逐帧动画可用于loading动画,但更多的用于Sprite精灵动画(人物运动)。精灵动画把所有帧都放在一起,通过CSS3的animation
控制background-position
。
下面看一个精灵动画的例子:
(案例:拍拍无聊者联盟宣传页)
steps的参数有几个坑,需要特别留意:
第一个参数number
为指定的间隔数,指的是把两个关键帧之间的动画分为n步阶段性展示,而不是keyframes
写的变化次数。
我们将上述案例中hand部分作为例子:由雪碧图可知,手部的摆动一种有两种状态,故keyframes
需要写两帧:
|
|
设置不同的number值:
|
|
|
|
(左图:number
为2;右图:number
为1)
第二个参数可选,接受start
和end
两个值:指定在每个间隔的起点或是终点发生阶跃变化。通过W3C中的一张step的工作机制图可以理解:
(图片来源:W3C)
TIPS:
step-start
等同于steps(1,start)
:动画执行时为开始左侧端点的部分为开始;step-end
等同于steps(1,end)
:动画执行时以结尾端点为开始,默认值为end。
最后安利一个计算帧数的工具:CSS3动画帧数计算器
简单、高效
声明式的
不依赖于主线程,采用硬件加速(GPU)
简单的控制keyframe animation播放和暂停
不能动态修改或定义动画内容
不同的动画无法实现同步
多个动画彼此无法堆叠
总结一下在之前做动画需求时的经验,归纳为以下7个步骤。以此需求为例:
(案例:iphone6s推广游戏)
观察——哪些元素可以动?元素可以怎么动?
根据视觉稿,分析标题、按钮、人物、背景都可以适当加动画元素。
沟通——了解设计师的想法,并提出自己的想法。
这是设计师给出的大致动画过程,具体的过渡及动效没有明确给出,因此可以根据自己的想法与设计师进行沟通。
分析——分析动画元素的层次(出现顺序);画出动画时间轴;根据时间轴写出CSS动画时间轴。
分析该页面动画的出现可以分为四个层次:
根据前面的分析画出动画时间轴:
根据时间轴写出CSS动画时间轴:
方法一:将所有动画元素放在一个时间轴上(适合于元素较少的情况)。
|
|
方法二:同一阶段的动画元素放在一个时间轴上。
|
|
切图——PS CC 2015修改组/图层名为“*.png”,生成图像资源。
使用PS CSS 2015切图具体步骤如下:
定位——适当使用绝对定位;适当使用rem。
安利一款sublime插件:PX转REM插件。
实现
从无到有:
宽度—width(少用)
动起来:
润色
后文介绍
TIPS:不要在before,after里加动画!
惯性
物体没有停在本应该停止的位置上而是靠惯性继续摆动一段时间然后反方向摆回来。
惯性在日常的动画需求中应用相当普遍,元素的高速进入都涉及惯性。
透视
物体与观察者的距离远近在静态时通过物体的大小来体现。
当物体运动时,通过远近物体不同的运动速度来体现,从而形成层次感。近处的物体运动快,远处的物体运动慢。
(透视原理图)
示例:云朵与观察者的距离有远近之分(不可能所有的云都在一个平面上),设置云朵的飘动动画时,可根据云朵的大小(远近)设置不同的运动速度,近处的云朵飘动的速度比远处的快,从而形成透视。
|
|
(案例:iphone6s推广游戏)
节奏
善用曲线和缓动可使效果更生动。
多个元素保持相同节奏,保证画面的动画不过分凌乱。
示例:匀速的呼吸与缓动的呼吸。
|
|
|
|
(左图:匀速呼吸效果图;右图:缓动呼吸效果图)
跟随动画进行呼吸,可以明显感觉到缓动的呼吸更贴近我们实际的呼吸情况。呼吸函数如下:
(图片来源:让界面动画更自然——ISUX)
跟随
跟随动作是将物体的各部位拆解,通常是没有骨架的部位较容易产生跟随的动作。例如:一个奔跑的人突然停下,他的衣服头发等可能仍会运动。其中,人是“主体”,衣服头发等是“附属物”。
附属物的动作取决于:主体的动作,附属物本身的重量和质地,以及空气的阻力。
主体与附属物之间动作的重叠和追随,就是鉴定动作流畅性与自然度好坏的标准。
在之前的一段时间, 朋友圈中出现了一批使用全景图浏览技术的H5页面. 比如探班吴亦凡系列, 或者是探访京东总部大楼找优惠券页面. 在当时, 这几个页面取得了不错的宣传效果. 那么, 这种新奇的全景效果到底是怎样实现的呢?
现在的智能手机一般都自带全景图拍摄功能. 就算没有, 通过安装一些第三方软件也可以拥有这个功能, 但通过这些软件拍出来的只是一个非常宽的照片, 还无法达到360随意转动观看的效果. 要制作像上面那样的360全景观看页面, 我们需要从最基础的开始. 首先, 什么是全景图?
360度全景图也称为三维全景图、全景环视图。360度全景技术是一种运用数码相机对现有场景进行多角度环视拍摄之后,再利用计算机进行后期缝合,并加载播放程序来完成的一种三维虚拟展示技术。 – 360度全景图_百度百科
也就是说, 我们可以使用拍到的全景图, 使用计算机进行后期缝合, 并加载播放程序来完成三维显示. 具体到使用ThreeJS实现全景图这个场景, 我们需要做什么呢?
其实, 粗略一想也可以想到, 如果我们将拍摄到的全景图贴在一个圆柱的侧面上, 我们站在圆柱中心朝四周看的话, 应该就有全景观察的效果. 不过这样做也有坏处, 也就是我们的头顶跟脚底都是无法看到的区域. 我们需要使用其他的方式来实现. 在这之前, 我们需要了解一下ThreeJS中的相机.
在ThreeJS中, 相机还分为CubeCamera(立方体相机), PerspectiveCamera(透视相机)以及OrthographicCamera(正交相机). 其中, CubeCamera是创建动态贴图用的, OrthographicCamera创建的照相机不具有透视效果. 在这里, 我们用到的是PerspectiveCamera.
定义一个透视相机只需要一句话:
|
|
在这一段代码中, fov代表相机的视角, 即视野上平面与下平面的角度, aspect是相机的宽高比, near是视野近平面的距离, far是远平面的距离.
然而, 在3D的世界中, 仅凭上面这几个参数, 我们只能确定一个照相机的自身基本属性, 却无法确定这台照相机究竟位于什么位置, 是什么样的角度. ThreeJS中, camera.position属性是一个三维向量, 我们可以用这个属性定义相机相对于原点的位置. camera.lookAt(Vector3)函数可以定义照相机的观察方向, 参数同样是一个三维向量. 对于照相机而言, 还有一个参数显得非常重要, 这就是相机的上方向. 同样的空间位置, 朝向同一个方向, 照相机还可以是横着, 也可以竖着, 最后看到的效果也不会一样. 所以camera还有一个up属性, 定义照相机的上方向. 如上图蓝色空心箭头所示.
要让人产生全景的视觉效果, 很关键的一点是, 要让人看见他当前姿态所应当看见的景观. 如果将人眼比作一台照相机, 我们很容易想到, 我们如果将全景图贴在一个球形的内表面, 那么人眼这台照相机所看到的景象就是上下左右360无死角的全景.
想想总是美如画的. 我们不妨实践一下. 首先, 去google搜索关键字’全景图 360’, 随意下载一个全景图. 接下来, 我们需要将这个全景图贴到球形的表面. 这一步, 我们再一次用到了Blender.
首先, 新建一个工程, 然后往场景中添加一个经纬球.
我们希望使用之前下载到的全景图作为这个球体的贴图. 所以, 我们需要首先对这个球体做uv展开. 对这个球使用球面投射, 方向选择对齐到物体, 选上缩放至边界框, 接着我们看到UV展开图是这样的:
这个UV展开图非常不规则, 就算有了全景图, 我们也没法往上贴. 这是因为上下两个顶点处汇集了所有的经线, 使得Blender也无法准确得知我们想要怎样的贴图. 这种时候, 我们需要将球体的南北两个极点删除. 这样的话, 球体的上下两个顶点就成为了两个空心的圆圈. 然而, 模型空了两个洞不太好. 接下来, 使用Extude工具推挤出新的纬圈, 酌情缩小一些.
重新进行UV展开, 会看到这次的UV展开非常平整.
将新的纬圈设定scale为0, 再删除重叠的节点, 南北极就可以重新汇聚到一个点了. 接下来我们把全景图贴上, 一个全景球就这样诞生了.
我们将这个全景球通过io_three插件导出为json. 新建一个页面, 引入three.js. 核心代码大概是这样子:
|
|
这里需要注意, 如果将注释部分的代码删去, 我们将会发现视野中一片黑. 这是因为Blender导出的模型默认使用的是遵守Phong光照模型的材质, 这种材质在没有配置自发光, 又没有外界光照的情况下就是一坨黑色. 所以我们还需要手动配置一下. blender导出的json是scene本身. scene是一个树状的结构, 在它的children属性中有所有的对象信息. 在这里, 我们需要配置一下贴图的方向以及自发光, 接下来就可以看到效果了.
上面这样的实现其实也有一个弊端. 球状模型的顶点与面的数量十分逆天. 这些元素的数量越多, 耗费的浏览器资源就会越多. 那么有没有更加节能环保的方法呢?
答案是肯定的. 既然我们的人眼可以被类比为照相机, 那么如果摆多几台照相机, 将拍到的照片无重叠地拼在一起, 一样可以获得全景视觉.
这里, 我们使用6台90度视角, 纵宽比1的照相机, 从球体中心分别朝向立方体六个面的方向.
将6个渲染图分别保存下来.
接下来新建页面, 将这六张图片分别贴到立方体的六个面就大功告成了. 核心代码:
渲染出来的效果, 其实是完全一样的.
使用这两种方法做出来的全景展示其实还会有一些小问题, 比如展示空间的底部与顶部会有聚焦在一点的现象:
这种情况单靠一个全景图是无法解决的, 只能通过对底部与顶部多拍一个照片来补救. 目前, 全景展示的技术已经有许多应用, 比如谷歌地图百度地图的街景展示, 或者是上面提到的几个H5页面中也有用到. 作为一名前端工程师, 懂得其中的原理并付诸实践, 这是非常重要的.
1.工具:
After Effects
、Photoshop
2.设计流程:After Effects
在一个合成中创建转场效果,另外一个合成中创建展示效果,导出到PS。
3.进行帧处理,优化导出GIF
打开AE,合成>新建合成(Cmd+N),尺寸为518*715
,帧数29
,持续6s
导入伦敦背景图像,将图像丢入合成1,调整背景图像尺寸,使其适合画布
创建3个蓝色框(用形状图层),这些蓝色框将从屏幕上方下落到屏幕底部,宽度172
、174
、172px
(在AE中,尺寸特别不好确定,我们可以COM+R打开标尺去匹配),色值#063857
、#145071
、#416C8D
。三个方框上方的方框宽度640px,输入相应文本。
用钢笔勾勒出简单云朵和6片雪花空心圆(或者去找素材,这里的云朵,就是找的png图片)
创建合成2,尺寸1280 x 720
帧数29
,时间6s
导入iPhone背景,调整尺寸,增加背景层扩展。
把合成1拖到合成2里面,选中合成1,在效果面板,选择效果>扭曲>边角定位,让合成1的4个角对上模板中屏幕的4个角,变成如下形式。
以上基本的合成构建都已经做完了,那接下来,我们开始实现动效
在实现动效之前,我们先说说,AE里面如何实现动效,两种方案,一种是效果菜单下的效果动效,另一种就是AE对图层的变换处理包含:锚点、位置、缩放、旋转、不透明度,通过改变激活这几个按钮,改变图层的参数形成关键帧,从而形成动画。
打开背景合成,选中三个色块
备注
1.这里应用了位置和缩放,来实现动效。
2.位置上,三个蓝色的框,通过缩放scale 0-100%
,从上到下,不同延迟。
透明框的运动,从下到上缓慢的位移。
1.三个蓝色框,当蓝色框触及到底部时,文字快速弹出,不透明度从0
->100%
,比例从0
->100%
2.透明框中的文字随透明框一起运动(位置设置可以参考透明框),但是,左侧的文字先到,右侧的-4度后到,注意时间轴中得设置。
透明框的文本开始出现时,云朵出现,先是透明度从0-100%;而比例开始时是60%,当透明度到了100%
时,从60%
->100%
当云朵动效完成后,雪花小圆点开始逐个出现。
1.注意小圆点会错落的向右移动,然后弹回,注意运动顺序的调节
2.注意小圆点逐个出现,因此透明度的变化要在时间轴上依次设置。
这里运用了蒙版运动,来打造Snowy文本逐渐出现的效果,注意Snowy不透明度也要变化(蒙版,通过图层点击右键添加蒙层,然后在蒙层中选择alpha蒙层)
AE文件可以导出多种形式文件,主菜单合成渲染(主要合成导出序列帧相关)和导出菜单(文件->创建->创建代理)就可以设置需要的格式,常见的格式有:
3GP MP4 MP3 MOV GIF SWF PNG序列帧图片
这里呢,我做完动效后,是导出.mov
文件,然后把.mov
文件导入到ps
,COM+OPTION+SHIFT+S
导出gif
文件,效果如下(记得要选择循环播放额,不然只会播放一次)。
到此,这个小例子我们就实现了,效果就是初始的GIF
效果图,源文件下载
weather
最后,对比一下H5中AE
视频动画、GIF
动画、Canvas
动画之间的差异
这里将本例,分别用了3种形式做H5
动画,AE
动画效果(AE效果可能要注意一下,由于没有找到比较好的视频压缩工具,初始动画有点卡,循环播放是很流畅的)、Canvas
动画效果、GIF
动画效果,看看三者之间的差异,二维码如下:
AE动画效果
,动效整体还原度比较好,支持流媒体加载,但是Android
与IOS
展现形式不一样;IOS
支持格式比Android
多,如.mov格式
;IOS
可以隐藏播放器控制条,Android
无法解决;初始效果与文件大小影响比较大。
Canvas动画效果
,动画还原度无法达到GIF
、AE
的效果,复合动画帧比较多,图片比较大,对做雪碧图影响较大,做出的动画会出现闪烁低端Android
出现明显卡顿。
GIF动画效果
,帧比较多,在部分低端Android
表现卡顿,GIF
格式图片,无法很好压缩。
AE制做动画特效的神器,还有很多很多好玩的特效等待我们去学习思考,以及结合到平时的工作中。在移动端h5开发中,经常会遇到比较复杂的交互特效帧比较多,可能很多时候大家会选择GIF
,帧动画,Canvas
动画来实现效果,可是往往做出来的动画展示效果,无法达到预想的效果,或多或少的导致设备卡顿,加载过慢等现象。在我们了解了这些之后,可以通过AE
换一种尝试,做更有趣的动画效果。
关于新 Safari 对 Viewport 的影响我们先看下面一段引自苹果开发者官方文档的说明:
Viewport meta tags using “width = content-width” cause the page to scale down to fit content that overflows the viewport bounds. You can override this behavior by adding “shrink-to-fit = no” to your meta tag as shown below. The added value will prevent the page from scaling to fit the viewport.
可以看到 Safari 将会对有内容溢出 Viewport 区域的页面进行缩放适配,使溢出的内容完整展示而不出现滚动条,而在 Viewport 设置中引入了一个新属性 shrink-to-fit = no
,该属性可以禁止这种缩放行为的发生。
下面我们具体看一下该体贴行为对页面的影响。
在 Safari 9.0 之前的版本,当 viewport meta 标签的 content 属性有设置的时候,如果页面内容溢出 viewport 的定义区域,那么页面将会出现滚动条,如下面的页面,该页面的 viewport 设置为:
|
|
截图如下:
然而在 Safari 9.0 的版本,即使 viewport 设置了 initial-scale=1.0
和 user-scalable=no
,Safari 不仅重置了设定好的最小缩放值限制,而且还重新恢复了用户对页面进行缩放的操作权限,上述页面在 Safari 9.0 中的截图如下:
从上面的截图可以看到,Safari的缩放行为对页面视觉破坏性还是比较厉害的,如果溢出的内容很多的话,页面就会被缩得更小了。
在 viewport 设置添加 shrink-to-fit = no
这个属性后,设置如下:
|
|
Safari 就不会再对页面进行缩放处理,恢复之前版本的处理方式,
截图如下:
在日常的需求中,尽管内容溢出页面的情况不会太常见,但为了增强页面的容错能力,保证页面视觉的正常显示,在 viewport 设置中应该加上 shrink-to-fit = no
新属性。
Safari 9.0 新增了内容拦截扩展,该扩展程序能够便捷高效地拦截 cookies、图片、资源文件、弹窗等内容,不仅可以在 OS X 上使用,在 iOS 上也同样适用。
内容拦截扩展程序负责提供一个 JSON 文件给 Safari,这个 JSON 文件包含了一个数组,数组里面可以配置相关规则(主要由 triggers 和 actions 组成 )拦截指定的内容,Safari 会将该 JSON 文件转换成字节代码,转换后的字节代码被应用到所有加载资源,有效地避免了用户浏览器的信息泄露到扩展程序。
内容拦截扩展程序的核心就是通过 JSON 文件的配置和传送,Safari 接收到之后就可以根据 JSON 的配置对相应的加载资源进行适配完成拦截。
Xcode内设有内容拦截应用模版(Content Blocker App Extension template),里面有一个 blockerList.json
JSON 文件,默认设置是这样的:
|
|
表示对站点 webkit.org 的一张名为 icon-gold.png 的图片进行拦截。
除了可以从加载资源上拦截外,还可以通过 CSS 选择器对元素进行隐藏,如下面的配置会将站点所有 class 名为 “ads” 的 div 进行 display: none !important;
样式处理。
|
|
注意:由于 32 位架构的设备性能限制,iOS上的内容拦截扩展只适用在 64 位架构的设备上,苹果从 A7 处理器开始采用 64位架构,因此 iPhone 5s + 的机器才可能使用此扩展应用
关于参数 action 和 trigger 的更多介绍可以参阅:
Introduction to WebKit Content Blockers
Safari Content Blocking in iOS 9: a tutorial by example
实现一个拦截扩展应用很简单,现在我们以 拍拍小店首页 为例,拦截顶部广告区域:
Update Xcode 到最新版本,打开 Xcode,新建立一个 iOS Single View Application 项目(blocker-demo),如图所示:
然后新建立一个 Target,在 Application Extension 模版中选中 Content Blocker Extension:
完成之后,就可以在项目中看到刚才新增的 Target 文件夹(blocker),可以找到一个名为 blockerList.json
的 JSON 文件,在里面就可以配置需要拦截内容的规则了,如图所示:
(顶部广告区域的 class 名为 mod_ad
, 例子中的配置将会将顶部广告区域隐藏。)
然后以整个项目为编译对象,选择需要测试的模块器(这里选择了 iPhone6)运行程序 , 切记不要选中 content blocker 的 Target 为编译对象运行,在模拟器上不能生效 。
运行后我们看到白茫茫的一片,模拟器默认打开了刚才新建立的应用,这时候我们需要回到主界面(command + shift + h)进入系统设置界面, 到 Safari >Content Blockers 里面启动内容拦截扩展,如图所示:
启动 Safari,打开拍拍小店首页,这时候我们可以看到,启动内容拦截扩展之后,顶部的广告区域被隐藏了
用 Safari 的 Web 检查器看了一下页面,发现用户样式表里针对顶部广告区域多了一个 display: none !important;
样式
最后需要将程序打包,才能在真机运行,貌似需要 688RMB 的开发者帐号,土豪们可以尽情试一试。
在 OS X Safari 的扩展创建器(Extension Builder)加入拦截 JSON 文件(如下图)或者在 Safari 扩展对象中使用新增的 setContentBlocker API 就可以实现内容拦截功能了
需要注意的是,用于拦截内容的 onbeforeload 事件和 canLoad 信息已被弃用
更多有关 Safari 内容拦截扩展介绍:
Safari Content Blocking in iOS 9: a tutorial by example
iOS 9之Safari广告拦截器(Content Blocker)
苹果操作系统会从两种不同外观和大小的字体进行自动转换去调节系统新字体 “San Francisco”,可以通过 CSS 规则
font-family: -apple-system, sans-serif;
让系统智能选择适配操作系统的字体。
可以看一下下面两张在系统 OS X 10.11.1 下的对比图:
没有添加 -apple-system
属性值:
添加了 -apple-system
属性值:
可以看到添加了 -apple-system
,字体变得更圆润锐利
建议:在 font-family
属性加上 -apple-system
属性值。
图片看不出差别的同学请用 OS X Safari 9.0 打开以下链接:
更多关于 System Font Support 介绍 :Using the System Font in Web Content
Safari 9.0除了支持 -apple-system
新字体特性之外,在 iOS 平台提供了更丰富更具活力的文本样式,该文本样式不仅仅是特定的字体,包括所有的 font
样式,如字重和字号,因此只能以 font
来定义:
加上这些属性在 iOS 上会有不同的表现:
有兴趣的同事去 Demo 页看一下:
2015年3月26日,W3C的CSS工作组发布了CSS滚动界限点模块(CSS Scroll Snap Points Level 1)的首份工作草案(First Public Working Draft)。在浏览可以连续上下滚动或左右滚动的连续页面或一组图片时,通过触摸屏的触摸滑动或鼠标滚轴的滚动操作可以获得较好用户体验。
Safari 9.0 的新特性支持强大的 CSS Scroll Snap,利用这个特性,我们可以用CSS轻松实现网站常见的轮播图滚动特效
我们先看一下 Demo 感受一下 ,请用 Safari 9.0 打开
使用 Scroll Snap 需要的结构包含装载滚动元素的容器和需要滚动的元素:
<div class="scroll_container">
<div class="scroll_elements"></div>
<div class="scroll_elements"></div>
<div class="scroll_elements"></div>
<div class="scroll_elements"></div>
</div>
Scroll Snap 一共有5个CSS属性可使用,分别是
scroll-snap-type:定义滚动界限的类型,有三个属性值可选, none
| mandatory
| proximity
,初始值为 none
,目前 WebKit 只支持 mandatory
。
scroll-snap-points-x:定义滚动容器 X 轴方向的 scroll sanp 点,有两个属性值可选,none
| repeat(<length>)
,初始值为 none
scroll-snap-points-y:定义滚动容器 Y 轴方向的 scroll sanp 点,有两个属性值可选,none
| repeat(<length>)
,初始值为 none
scroll-snap-destination:定义滚动元素在滚动容器的对齐坐标点,属性值为 <position>
,初始值为:0px 0px
scroll-snap-coordinate:定义滚动元素与滚动容器对齐点重合的坐标点,属性值为 none
| <position>
,初始值为 none
属性 scroll-snap-type 、scroll-snap-points-x、scroll-snap-points-y 和 scroll-snap-destination 应用在容器元素
属性 scroll-snap-coordinate 应用在滚动元素
滚动容器 scroll snap 坐标点 和 滚动元素 scroll snap 坐标点分别如下图红色十字和黄色十字所示:
此时的设置是:
|
|
属性详细使用情况请参阅 CSS Scroll Snap Points Module Level 1
该草案还在不断修进改进,WebKit 目前只支持带 -webkit- 前辍的写法
要启用 scroll snap 效果,滚动容器必须具有非 none
值的属性 scroll-snap-type
滚动容器 scroll-snap-points-x
和 scroll-snap-points-y
的属性设置会忽略滚动元素 scroll-snap-coordinate
属性设置
滚动容器 scroll-snap-destination
属性与滚动元素 scroll-snap-coordinate
总是成对出现的
滚动内容发生改变(如滚动容器大小、滚动元素大小、节点的改变等)使得滚动元素的 scroll snap 点不在滚动容器的 scroll snap 点的时候,滚动偏移值会发生改变。
不同尺寸的滚动元素混合在一起发生滚动的时候,在 X 或 Y 轴方向上离滚动容器 scroll snap 点最近的滚动元素 scroll snap 点方向轴将会与滚动容器 scroll snap 点的方向轴重合(存在滚动区域的时候)。
更多关于 scroll-snap 属性 Demo 可查看 Scroll Snapping with CSS Snap Points
Safari 9.0 支持 CSS initial-letter
(要带 -webkit- 前辍) 属性了,以前我们通过伪元素 ::first-letter
实现首字下沉的效果,但是局限性比较多:
首字母的高度需要通过调整字号大小实现,比较难精确到需要设置的高度
段落字号改变的时候,首字母字号并不能自适应改变,因此并不能自适应实现对齐
CSS initial-letter
属性能很好的解决这些问题,我们先看一下它的属性值:
normal – 初始值
[<number> <integer>] – 定义首字母的大小和下沉位置
<number> 首字母的大小,大小值为正数值,字高占据的行数为单位,如 “2” 表示首字母高大小占据段落两行
<integer> 首字母下沉的位置,大小值为不为0的整数,若缺省,则以第一个参数 <number>数值为准
initial-letter
属性应用在伪元素 ::first-letter
或容器第一个行内子元素
w3文档介绍的 EXAMPLE 截图引用如下:
实验 Demo 如下:
CSS背景滤镜属性 backdrop-filter
可以让元素的背景或元素层级以下的元素加上滤镜效果,以下的属性值在 Safari 9.0 得到了全面支持:
blur(<length>)
:模糊,原始效果值为 0px
,不接受负值
brightness([ <number> | <percentage> ])
:亮度,原始效果值为 1
或 100%
,不接受负值
contrast([ <number> | <percentage> ])
:对比度,原始效果值为 1
或 100%
,不接受负值
drop-shadow( <length>{2,3} <color>?)
:投影,原始效果值为所有长度值为 0
,长度值至少2个,最多3个,注意:不支持投影扩展值和混合投影
grayscale([ <number> | <percentage> ] )
:灰度,原始效果值为 0
,最大值为 1
或 100%
,不接受负值
hue-rotate( <angle>)
:相位,原始效果值为 0deg
invert( [ <number> | <percentage> ])
:反相,原始效果值为 0
,最大值为 1
或 100%
,不接受负值
opacity([ <number> | <percentage> ] )
:透明度,原始效果值为 1
,最大值为 1
或 100%
,不接受负值
saturate([ <number> | <percentage> ])
:饱和度,原始效果值为 1
,不接受负值
sepia([ <number> | <percentage> ])
:乌贼墨,原始效果值为 0
,最大值为 1
或 100%
,不接受负值
滤镜属性 filter
同样具有以上的属性值,与背景滤镜backdrop-filter
不同的是,filter
作用于元素本身(文本、背景等)以其子元素,而 backdrop-filter
只作用于元素本身的背景(文本及其子元素不受影响)以及其层级下面的其它元素,具体的测试参考下面的 Demo
更多关于 backdrop-filter
WebKit.org:Introducing Backdrop Filters
w3c文档:typedef-filter-function-list
Safari 9.0 支持了 CSS3 功能查询 @supports
,可以对 CSS 属性(包括但不限于带前辍的属性,如 -webkit-,-moz- 等)进行检测
|
|
可以使用逻辑关键字 not
、and
、or
进行多个单件查询
|
|
@supports
后面总是跟着一个空格符和一个括号,逻辑关键字
前后总有一个空格符。因此,写查询条件的时候
|
|
多个单件查询,当有多个逻辑关键字混合使用的时候,查询语句最外层最多只有一个逻辑关键字:
|
|
@supports
更详细说明可参考:at-supports。
以下 CSS4 选择器在 Safari 9.0 完全支持了:
:any-link
:lang
:matches
:not
:nth-child
:placeholder-shown
一般情况下,选择符中属性名和值的大小写敏感性取决于文档语言的敏感性,为了使其匹配忽略大小写敏感性以及其文档语言的敏感性,可以在属性选择器的关闭符]
前加上标识符 i
,这样不管在什么文档,都能忽略大小写敏感性,如以下例子:
结构:
<div class="box" title="BOX">Box</div>
样式:
|
|
关于 Case-insensitive 更详细介绍:w3介绍文档 - Case-insensitive
测试 Demo
w3文档介绍大概的描述是这样的:
如果元素有定义伪类
:link
或:visited
,:any-link
将会对其进行匹配。
于是写了 Demo 验证了一下
HTML:
<a href="http://aotu.io/" title="" class="lk">凹凸Team链接</a>
CSS:
|
|
结果发现:
:any-link
Safari 9.0 生效了,Chrome、Firefox 并没有生效。
伪类 :any-link
的确会对已定义有的伪类:link
和 :visited
匹配,并重新覆盖元素相同的CSS属性, 而 :hover
和 :active
并没有匹配成功,:hover
和 :active
依然生效
CSS代码解析的时候渲染 :any-link
前就渲染了 :link
和 :visited
,因此匹配成功了,而 :hover
和 :active
出现在 :any-link
后面,并不能匹配成功
若加上 -webkit-
前辍,Chrome 亦能生效。
如果将 :any-link
出现位置改变,:any-link
始终匹配出现在他之前的 Link 相关伪类,有兴趣的同学可以试一下。
小小总结了一下:
:any-link
Safari 9.0 不需要加前辍,Chrome、Firefox 浏览器需要加 -webkit-
、-moz-
前辍。
:any-link
匹配有效性和书写顺序有关,:any-link
只对出现在其之前的 Link 伪类匹配生效。
写 a 标签样式的时候,在 :hover
和 :active
前应该加上 :any-link
伪类样式,覆盖系统默认的 :link
和 :visited
伪类样式。如下所示:
|
|
关于 :any-link
更详细介绍: w3介绍文档 - :any-link
实验 Demo
:matches()
是一种类似函数的伪类,可以将不同的选择器以数组形式混合形成一个选择器组,我们可以将之看作是CSS的一种语法糖,请看下面的例子:
:matches(section, article, aside, nav) h1 {
color: #BADA55;
}
/* 相当于下面的代码 */
section h1,
article h1,
aside h1,
nav h1 {
color: #BADA55;
}
更方便书写一些复杂的选择器,如:
:matches(section, article, aside, nav) :matches(h1, h2, h3, h4, h5, h6) {
color: #BADA55;
}
/* 相当于下面的代码 */
section h1, section h2, section h3, section h4, section h5, section h6,
article h1, article h2, article h3, article h4, article h5, article h6,
aside h1, aside h2, aside h3, aside h4, aside h5, aside h6,
nav h1, nav h2, nav h3, nav h4, nav h5, nav h6 {
color: #BADA55;
}
减少重复的书写:
.links:matches(:hover, :focus, :active) {
color: #BADA55;
}
/* 相当于下面的代码 */
.links:hover, .links:focus, .links:active {
color: #BADA55;
}
注意::matches()
不能嵌套而且不能与 :not()
一起使用,以下的写法都是不允许的:
/* Doesn't work */
:matches(:not(...))
/* Doesn't work */
:not(:matches(...))
/* Doesn't work */
:matches(:matches(...))
更多关于 :matches()
可参考
CSS-tricks ::matches()
W3文档:matches
如果元素 E 设置了属性 placeholder
,属性值不为空并出现在页面的时候,那么E:placeholder-shown
就能匹配到该元素,当 placeholder
文本在页面消失的时候,匹配到的相应样式也会消失,如以下 Demo 所示:
详细用法有兴趣的同学可以到 w3文档 查阅:
:nth-child - #the-nth-child-pseudo
以下CSS属性在 Safari 9.0 已完全支持,不用再带 -webkit-
前辍:
Safari 9.0 推出了响应式设计模式,command + option + R 或在“开发 – 进入响应式设计模式”可以进入,界面如下图:
这里有:
点击选中的机型图标还可以以不同的方式展示,如 iPhone 的横屏,iPad 的分屏等
响应式设计模式 Chrome 浏览器早早就有,但单从苹果产品的调试去看,个人更喜欢 Safari 的响应式设计模式,无论从外观、体验以及专注度,都要优于 Chrome,Safari一直在进步,从不怀疑 Apple Developer 的级数。
之前所了解到的 Web Inspector 版本:
(从苹果开发者文档介绍了解到的,没使用过,目测是 Safari 4.0 or 之前的)
(直接 google 图片了解到的,没使用过,目测是 Safari 6.0 重新设计那款)
Safari 9.0 的 Web 检查器经过了重新设计,更直观易用,用户体验更棒。
开发任务 Tab 切换更简易快捷,新的渲染时间线框架更容易更细致检测到页面渲染的情况
元素查看,样式修改调试方便,直接点光标直接处于编辑状态了,不明白 Chrome 的为什么点了默认全选,编辑态还有一个输入框的形态,Apple 的极简设计理念无处不在,处处体现着苹果 Design 的基因
断点调试器也哗啦啦地好看易用:
更多的内容有兴趣的同学去体验一下吧,这里就不一一烧图了。
本文译自 Building command line tools with Node.js,介绍了如何通过Node.js来创建命令行脚本工具,介绍了很多实用的npm
包等。翻译水平有限,敬请指正~
在我的职业生涯中已经写过了上百个 Bash
脚本,但我的 Bash
依然写得很糟糕,每一次我都不得不去查一些简单逻辑结构的语法。如果我想通过 curl
或者 sed
来做一些事情,我也必须去查找 man
文档。
然后,有一天,我看到六个字母的语言[译者注:这里指NodeJS] — 一门在过去十年里我几乎 每一天 都在使用的语言,这才让我幡然醒悟。结果是你可以使用 JavaScript
来写脚本!
在这篇教程中,我将会在使用 Node.js
和 npm
创建一个脚本或者命令行工具方面给你一些我的想法。特别地我们将会包括以下内容:
npm
封装一个新的 shell
命令ACSII
进度条我热衷于已经可以工作的例子,所以为了解释这些概念我们将会创建一个新的 shell
命令,它的名字为 snippet
,可以在我们本地磁盘的文件创建一个 Bitbucket Snippet。
这是我们的最终目标成果:
npm
不单单用来管理你的应用和网页的依赖,你还能用它来封装和分发新的 shell
命令。
第一步就是通过 npm init
[译者注:可以通过 npm init -f
直接快速生成一个package.json
]来创建一个新的 npm
项目:
|
|
这会在我们的项目中创建一个新的 package.json
文件,那时我们将需要创建一个 JS
文件包含我们的脚本。让我们根据Node.js的传统命名为 index.js
。
|
|
注意我们必须加一些 东西
来告诉我们的 shell
如何处理我们的脚本。
接下来我们需要在我们 package.json
里面的最顶级增加 bin
部分。设置的属性(在我们的例子中是 snippet
)将会变成用户在他们的终端处理脚本使用的命令,属性值就是相对于 package.json
的脚本位置。
|
|
现在我们已经有一个可以工作的 shell
命令了!让我们安装它并且测试结果。
|
|
真整洁! npm install -g
实际上是将我们脚本链接到 path
变量的位置,所以我们能够在任何地方使用它。
|
|
在开发环境中我们实际上使用 npm link
便利地将我们的 index.js
软链接到 path
变量的位置。
|
|
当我们开发完成的时候,我们可以通过 npm publish
将我们的脚本发布到公共 npm
仓库,然后任何人都可以下载安装到他们的机器上:
|
|
但是让我们先让我们的脚本能够工作先!
我们的脚本现在需要一些用户的输入:他们的Bitbucket名字,他们的密码,还有作为 snippet
上传的文件位置。典型的方法就是通过命令的参数传输这些值。
你可以通过 process.argv
拿到序列化的参数,但有很多 npm
包在解析参数还有选项方面提供了很好的抽象给你。我最喜欢的就是 commander,来自 Ruby gem
同一个名字的灵感。
一个简单的命令安装它:
|
|
上面命令将会把最新版的 commander
加入 package.json
。我们这时可以通过简单声明式的方式定义我们的选项:
|
|
上面代码可读性很强。事实上,这是一个保守的说法。相对于那些我们需要通过 switch
来控制的像 Bash
,这是一个艺术品。至少,我写的 Bash
是这样子的。
让我们快速测试:
|
|
很棒!commander
还提供一些简单的帮助输出给我们,基于我们上面提供的配置。
|
|
所以我们已经拿到了参数了。但是,让用户在空白的地方输入他们的密码作为选项有一点难用。让我们解决它。
另一种通用的取回用户的内容的脚本方式是从标准输入流中读。这可以通过 process.stdin
实现,但是再说一次,已经有很多 npm
包提供了非常好的 API 给我们使用。很多都是基于 callback
或者 promises
,但是我们将使用 co-prompt (基于 co),因此我们可以利用 ES6 的 yield
关键词。这让我们写异步的代码而不需要 callbacks
,看起来更加脚本化。
|
|
为了组合使用 yield
和 co-prompt
,我们需要通过一些 co
的魔法来包裹我们的代码:
|
|
现在快速测试一下。
|
|
很棒!唯一的窍门就是ES6的 yield
,所以这只能在用户运行在 node 4.0.0+上面。但是我们可以通过加入 --harmony
标志让 0.11.2 版本的也可以正常使用。
|
|
Bitbucket拥有一套非常漂亮的 API。在这个例子中我将关注传输单一的文件,但我们可以发送整个目录,改变我们的入口配置,加一些代码等,如果我们需要的话。我最喜欢的node HTTP 客户端是 superagent ,所以让我们把它加入项目中。
|
|
现在就让我们将从用户收集到的数据发送给服务器。 superagent
其中一个优点就是在它在处理文件上拥有非常好的API。
|
|
现在让我们测试一下。
|
|
我们的 snippet 已经发送了!\o/
到现在为止我们已经处理了一切正确的情况,但是如果我们上传失败或者用户输入错误的信息呢?UNIX-y
的方法来处理错误就是将标准的错误信息输出并且以非0的状态码结束程序,所以我们也这样子做。
|
|
这样子就可以处理错误了。
如果你的用户是在使用体面的 shell
,这里也有一些包提供给你使用让你方便彩色化的输出。我喜欢 chalk ,因为它拥有干净可链式的API以及自动检测用户的 shell
支持的颜色。这是有益的提示如果你想将你的脚本分享给 windows 用户的话。
|
|
chalk
的命令能够彩色输出同时还能方便跟常规字符串串联起来。
|
|
让我们旋转一下(这里我使用了截图,以便你能看到极好的颜色)。
ACSII
进度条snippets
的API实际上支持任何类型的文件(最多10MB),但是当文件比较大或者网速特别慢的时候就需要在命令行界面显示上传文件进度了。命令行解决方案就是优雅的 ASCII
进度条。
progress 是现在最常用的 npm
包用来渲染进度条。
|
|
progress
的API非常简单而且可扩展,唯一的问题就是 superagent
当前node版本没有事件能够订阅我们上传的进度。
我们可以通过创建一个 可读的流 并且增加一个事件来触发请求。然后我们初始化进度条为0,当事件触发的时候不断增加。
|
|
下面是一个比较快的网速下上传大约6MB的文件的截图:
很棒!用户现在就能够看到他们上传的进度并且知道什么时候上传完成。
我们只不过接触了用Node开发命令行脚本的冰山一角。在每一期的 Atwood’s Law 都有很多 npm
包优雅地处理标准输入、管理并行任务、监听文件、管道流、压缩、ssh、git、还有任何你能用 Bash
做到的。更多地,还有非常好的API来处理子进程如果你需要其他shell脚本处理(当JavaScript处理不了的时候)。
我们上面例子的源码是在 available on Bitbucket 的license下,并且已经发布到 npm仓库。我这里也提一些上面没有讲到的概念,比如 OAuth
,这样子你就不需要每次都输入用户名跟密码。如果你想自己简单体验一下:
|
|
如果你觉得本教程有帮助,发现了bug或者有其他更酷的Node.js脚本建议,可以在Twiiter私信我。(我是 @kannonboy)
]]>:matches()
的介绍出现了 foo
和 bar
:
:matches(.foo, .bar) {
background-color: green;
}
随即想起,以前在不少编程书里经常看到 foo
、bar
这对 couple,一直不知道他们的具体含义,于是默默去 google 搜了一下,发现结果都几有意思。
首先在 Quora 里发现一位名为 Thom Parkin 的网友回答:
“foo” has been (until very recently) always used as part of a pair (with “bar”) when representing arbitrary names in computer science. This is primarily in the instance of examples.
Much like the “Hello World” is a standard for examples in computer programming the use of “foo” and “bar” had grown in popularity.
The origin comes from a common phrase much older than computers and computer programming.
The choice of “foo” coupled with “bar” is derived from the coloquial acronym FUBAR (pronounced FOO-bar).
Legendarily from the military world, it represents the phrase:
“Fucked Up Beyond All Recognition” (in ‘mixed company’ the first word could be “Fouled”).
大概的意思是说:
在计算计领域里,“foo” 和 “bar” 通常会成对出现,在举例的时候用作没有明确意义的命名,就好像 “Hellow World” 例子在计算机编程里面作为标准的例子一样,“foo” 和 “bar” 的使用已经变得很流行。
而该词在常用的习语里面出现的时间远比在计算机或计算机编程里面出现的早,“foo” 和 “bar” 成对出现则起源于口语 “FUBAR” 一词(发音和 Foo-bar 一样)。
来自军事世界的传说中,“FooBar” 的含义则是 “Fucked Up Beyond All Recognition,全句意思是 “糟糕糟到面目全非/真是混乱不堪” ?
看完 Thom Parkin 的解释,隐隐约约觉得此词来头不小,但是啊 Thom 的介绍又不够详细,然后继续 search 一下,发现万能 Wiki Sir 对此有更详细的描述。
Wiki Sir 直接就来一句 “Not to be confused with FUBAR”,心里只想说:666666
Wiki 对 “Foobar” 一词的定义是这样的:
The terms foobar (/ˈfuːbɑr/), fubar, or foo, bar, baz and qux (alternatively, quux) and sometimes norf[1][2][3][4] and many others[5][6] are sometimes used as placeholder names (also referred to as metasyntactic variables) in computer programming or computer-related documentation.[7] They have been used to name entities such as variables, functions, and commands whose exact identity is unimportant and serve only to demonstrate a concept. The words themselves have no meaning in this usage. Foobar is sometimes used alone; foo, bar, and baz are sometimes used, when multiple entities are needed.
大概意思是:
Foobar 还有一群兄弟姐妹,例如 “fubar” 、“baz”、“qux”、“quux”、“norf”,他们很多时候都会作为占位名(或者作为伪变量)被应用在计算机编程或与计算机相关的文档中。
他们被用作变量命名、函数命名、和命令命名,这些命名基本都是不重要的,仅仅作为某个概念阐述的示例,他们本身并没有什么具体含义
Foobar有时候单独使用,而当多个这种用法出现的时候, “foo”,“bar”,“baz” 会组合使用。
嗯~~ 原来是这样~~
那么出处呢?Wiki Sir 介绍得比较详细,有兴趣的同学自己 Click Here 去看看原文,在下也试下意译到本文
Foo 一词起源于 20 世纪 30 年代的一个并没有任何意义的词(nonsense word)和 20 世界 40 年代军事术语里面的 “FUBAR” 以及大约在 20 世纪 60 年代麻省理工学院铁路模型技术俱乐部(TMRC)在一个程序上下文用到的词汇。然而,这些词汇之间明确的联系仍然不清晰,从而使得一些有趣的理论学说进一步去定义他们。
Foo 在文献 《Internet Engineering Task Force (IETF) RFC 3092》里有定义(文献标题为 Etymology of “Foo”),文献给出了 foo 最早的使用记录,就是出现在 20 世纪 30 年代由美国 Bill Holman 创作的连载漫画 Smokey Stover,foo 被用作没有具体含义的词,如下图的 “foomobile”:
(Smokey Stover driving a “foomobile”)
作者 Holman 说使用这个词是因为他在旧金山唐人街看到一尊中国玉雕像底部的“福”字,foo 大概是 福(fu) 的音译。而当时像 “福祿壽”、“福满处”等象征上帝的虚构形象在中国非常流行。
Smokey Stover 从 1935 连载到 1973,期间一直重点保留着 “foo” 的特征,就好像上面 “foomobile” 的插图。这个词在 20 世纪 30 年代非常流行,除了在 Smokey Stover 出现,还在其它卡通和连载漫画里出现,如由 Bob Clampett 创办的 Looney Tunes(兔巴哥)卡通系列里的 The Daffy Doc 和 Porky in Wackyland 以及漫画 Pogo
接着,Foo 走进了军事俚语 “FUBAR”(Fucked Up Beyond All Recognition)一词中,合并到词里的 “FU”。在第二次世界大战里盟军飞行员用词汇 “foo fighter” 描述各种各样的 UFO 或者一些神秘的空中现象
Foo 第一次在正式的程序类印刷品作为较为人知的词汇出现于 1965 年 TMRC 的一版印刷品,关于紧急开关(scram switches)方面的内容:
在复杂的模型系统中,一些紧急开关被安装在许多场所附近可以利用的空间里,这些场所有可能会发生突发的不幸事故,如全速前进的火车遇到障碍物。这种模型系统的另一个特性就是调试模拟盘上的数码时钟。当有人按下紧急开关,数码时钟就停止工作,显示屏上亦随之换成 “FOO” 这个词。在 TMRC 里面,紧急开关也因此称作 “Foo开关”(Foo switches)。正因为这样,“foo” 一词录入了 TMRC 语言字典。
有一本书描述了 MIT(麻省理工学院) 列车室靠门侧的两个分别标上 “foo” 和 “bar” 标签的按钮。在那时期,这两个词通常应用到各种按钮上;当 MIT 的黑客们有新奇想法的时候也会反复使用他们。因此,foo 和 bar 被广泛用作变化不定的命名。在 TMRC 语言字典精简版中有这样的描述:
Multiflush: stop-all-trains-button. Next best thing to the red door button. Also called FOO. Displays “FOO” on the clock when used.
Multiflush(不知道叫啥):停下所有列车按钮,门边的红色按钮,也叫做 “FOO”,当使用后,时钟上会显示 “FOO”。
好了,现在终于知道 foo、bar 是什么了,简单总结下就是:
20 世纪 30 年代,一位美国漫画达人在一尊中国玉雕像上看到一个中国字 – “福” ,然后就将其音译成 “foo”,并应用到他自己的漫画上
漫画大叔将 “foo” 发扬光大不久,唯恐不乱的军事世界将 “foo” 放到了俚语 “FUBAR” 里面,演变成了 “Foobar”
之后 MIT 人才在介绍一项 “complex model system” 的紧急开关中引入了 “foo” 一词,并发表出去,于是乎 MIT 的黑客们一有新奇想法就用 “foo” 一词,并用上了瘾,久而久之大家就对 “foo” 和 “bar” 认识达成了共识
一句话概括就是:Foo、Bar 什么鬼都不是却能代表一切,FUBAR!
The End
]]>本文翻译自An Introduction to Sass Maps:Usage and Examples
对于前端可伸缩页面的编写来说Sass Maps可以说是一个福音,从逻辑模块中抽取出配置是一种非常好的方法。现在就让我跟你解释为什么我认为在Sass 3.3中Sass Maps是最好的特性。
Sass 3.3[注1]即将更新给所有人使用,但是对于很多开发者来说还有很多实用性的特性他们依然不熟悉。新版本的Sass 3.3带给我们新的数据类型称为map
。Maps
是key/value
键值对的集合,能够帮助我们通过简单的代码创建一块配置区域。
首先我们会覆盖Sass Maps的基本用法,后面会看几个实例。
下面是一个快速创建Sass Maps的语句,创建一个变量(这里用了$map
)然后输入一些keys
跟values
,通过,
来分割,这些键值对通过括号包围起来:
|
|
当你定义了很多对key/value
键值对之后,有时候你需要取出一些值。你可以通过map-get()
方法来找出某个key
的value
。该方法需要传入两个参数:map
的名称还有key
。
|
|
上面编译输出后的结果如下:
|
|
在用Sass
进行编码的时候强烈建议使用可靠的错误处理。在这里Sass
给了我们一个方法map-has-key()
。这个方法能够检测出某个key
是否存在,如果不存在能够输出其他信息给开发者。
可以移步 Hugo Giraudel
写的这篇如何处理错误的文章 An Introduction to Error Handing in Sass。
|
|
编译之后结果如下:
|
|
这个一个福利:Sass
允许我们合并两个甚至更多个maps
成一个,这是一个非常实用的功能,通过下面这个例子我们将知道如何使用map-merge()
方法:
|
|
编译之后结果如下:
|
|
上面我们介绍了如何实用Sass Maps
,现在我们将通过一些实战训练看看在哪些地方适合使用该特性。
1.如何循环Map生成类
你可以遍历map
通过里面的values
去定义你需要的变量然后加到map
里面的name
去,这样子你可以创建出很多种values
。
在下面的例子中我将输出classes
来展示icons
。我将icon
的name
作为key
,让value
去替代实际的content
(通过伪元素加进去)。
注意:在项目实战中我们通过会先声明一些基础的样式,这不在本教程的范围内。
|
|
编译之后结果如下:
|
|
这是一种非常高效的方法来输出icons的所有类,还有大量情况也会使用这种方法。
2.如何拿出Maps的多个值
让我们继续,给一个key
赋予多个value
也是可以的。多个value
之间通过,
来分割。下面的例子能够非常好的输出不同模块的样式。
这里我将定义一系列buttons
,每一个key
的第一个value
是background-color
,第二个value
是font-color
。
然后我将通遍历keys
赋值给$colors
对象。通过nth($colors,1)
(第一个参数是对象的名称,第二个参数是值得位置)拿到第一个key
。如果你需要拿第二个value
,那将第二个参数改为2
。
|
|
编译之后结果如下:
|
|
3.处理层(z-index)
在某种程度上来说,我还没有见过不跟z-index打交道的前端开发。当你在项目中多个地方需要使用到z-index
的时候问题通常随之而来,Sass maps
能够帮我们解决这些问题。
首先我们定义了一个map名称为$layer
,所有的key
都应该合理命名以便我们能够知道哪个value是对应哪个element的-比如:offcanvas
,lightbox
,dropdown
等。
|
|
上面我定义了一个方法用来获取特定key
的value
,但为什么我要这样做?理由很简单:这样子比每次都写map-get()
方法要方便快捷。另外一个方面就是你可以创建错误处理
给开发者一些错误信息当没有输出期望的信息的时候。
编译结果如下:
|
|
4.在项目中为字体创建基本样式
每一个项目都拥有自己的配置文件,用来给全局使用。例如在我的项目中我会定义一些字体属性:字体颜色,可选的字体颜色,字体集或者字体大小。我通常都会为每个属性创建一个变量,但是map
能够做得更好。
下面是一个简单的例子,先从旧的解决方法开始:
|
|
接下来看通过Sass Map
写的新
的解决方法:
|
|
5.Breakpoints < 3
我很喜欢这个使用案例。在你的整个项目中拥有一块专门用来处理断点是非常好的。所以,像这一节中关于处理z-index
,你就已经用到了断点。当你改变值得时候,整个项目的行为也随之改变,这多么令人惊讶。
那就先让我们通过一个map
名称为$breakpoints
开始吧。
我们的目标就是在一个元素中使用断点通过易懂的名字替代那些pixel
值,所以需要一个mixin
方法来实现占位name
,我把mixin命名为respond-to
以及传入$brakpoint
参数。通过$value
我就能得到期望的断点然后后面在媒体查询
中使用。
|
|
示例:
|
|
这种使用案例是我最喜欢之一!
6.颜色的高级使用
现在事情变得有一点点困难了,让我们看看通过不同色调定义的颜色计划。
我们的Sass map
在这个例子中定义了一个$colorscheme
同时里面定义了很多对象拥有keys
跟values
。项目中拥有不同的灰色调,但我们不想为每一个都声明一个变量。所以我们增加了一个对象gray
,然后通过键值对分割。
下面开始这个Map:
|
|
现在让我们加入setcolor
方法来获取颜色的不同选择。第一个参数是Sass map的对象($scheme
)-在这个例子中可能是gray
或者brown
,第二个参数就是你想要的颜色($tone
),默认值是base
。
|
|
最后,这里给出了一个例子能够让你从这个map中获取不同颜色,比你想象中的还要简单(也许)!
|
|
你会完成上面的挑战的,现在你能够创建一组色调而不需要通过为每一种颜色创建一个变量。
这个方法我是受到了Tom Davies的启发,同时我也建议你看看他这篇文章。
这是为高级Sass用户准备的。在项目中会经常需要通过一些基础代码创建多套主题,所以这里给出一个建议:在文档的最开始就定义一个 主题类 来满足特定的工作。我们需要一个对象以便能够处理不同名字的主题,同时给出不同的样式模块。
最开始,在你的项目中通过Sass map全局定义themes,value
就是主题名字,同时这个类必须附在body
元素。在这个例子中我创建了一个map$themes
,里面有两个主题:theme-light
和theme-dark
。
|
|
现在我们需要一个方法来快速获取模块的值,这是一个简单的方法包括三个变量如下:
$map
: 定义map的名字确定values是从哪里来的$object
: 在这里例子中就是theme的key$style
: 需要的样式属性值
|
|
现在我们创建新的Sass Map
名称为$config
,每一个主题都是一个对象同时名字必须是$themes
中的key
:不然将会报错。
|
|
最后一部分会使用一点点技巧。开始的地方我们定义了一个模块.m-button
然后我们期望在每个主题下面外观是不一样的。所以我们使用@each
方法遍历Map$themes
拿到$key
跟$value
。遍历之后就能够为不同主题创建map。
在这一节的开始我提到了keys
在每个map里面必须是一样的($themes
跟$config
)。因此我们必须检查map$config
的key是否都来自map$themes
,这里使用到了map-has-key()
方法。如果包含了key那就继续往下执行,否则抛出错误给开发者。
|
|
通过上面的代码,让我们看看输出的结果。这是非常好的,保证了配置区域跟模块解耦。
|
|
最后你可以自己试试上面的代码看能不能运行出结果。也许对于你来说这个方案并不是最好的,你会寻求另外的方案,但是我依然希望这能够帮助你维持代码。
在我看来 Sass Maps
是Sass 3.3最值得介绍的特性。理由是我认为它给出了非常好的方式来创建健壮的结构,这只需要少量的配置。Sass maps使得我们可以在不影响整个项目的基础逻辑代码基础上改变值。开始使用它吧,你的伙伴将会感激你!
如果你已经开始使用Sass maps
了,让我知道你如何在你的项目中使用它!
Edge Animate主要使用HTML5,CSS3和JavaScript。HTML5是其用于创建的doctype,2D的transform,translate,rotate,scale,skew等特效都是渲染为CSS3,如果是最新版本的浏览器,可以操作纯粹的CSS3,如果是主流大众浏览器,Edge Animate则会通过JavaScript库的配合来保证HTML和CSS的显示兼容性。
1.精确的动画。
2.直觉化的使用者界面。
3.绘图和文字工具。
4.移动路径。
5.可重复使用的符号。
6.兼容性比较不错,android机也表现良好。
单一使用做html5 css3动画,带来了巨大的福音。
1.不太适合,做复杂的动画与游戏,适用场景狭窄。
2.操作比较适合设计师,不适合程序员,操作效率不高,调节不是特别可控。
3.采用html5 css3相关的动画,频繁产生重排导致效率低下。
4.动画元素层级绝对定位,后期维护增加元素,层级覆盖影响较大。
5.比较适合pc端动画制作,移动端需要进行二次转换,scale进行缩放已达到多屏适配的问题。
在开始玩Edge Animate前,你可能有个顾虑,就是Edge Animate如果要做交互动画,生成的内容是否会打破已有的HTML文件代码结构?答案是不会。通过Edge Animate生成的HTML代码少之又少,几乎都可以用下面的代码表达方式来概述:
Edge Animate使用JSON来存储元素的定义和属性。相关的动画内容则会全部以代码块的形式嵌入上面的div中,修改和删除都不会影响原有的HTML项目代码,对HTML中元素的操作都通过JSON对象和引入的JavaScript来完成。但是有一点,edge动画对html元素的动画操作,会导致浏览器频繁重排,降低效率。
创建edge animation 工程
启动Edge Animate,创建一个新项目ctrl+N/command+N。
设定Stage(舞台)宽度为640px, 高度为1136px。Ctrl+S保存为demo.html。打开我们保存的项目目录,一个标准的Edge Animate项目是由一系列html,css,js和相关资源文件组成的。如下图所示:
1)Edge Animate的工程文件: .an 格式文件更像一个空壳文件或者索引文件,标明了项目的必要信息。你可以使用记事本打开.an文件了解其中具体内容。
2)edge_includes目录:该目录下是Edge制作的html5所依赖的JS类库:edge.6.0.0.min.js 115kb,还是蛮大的。(之前的版本有依赖jquery,再5.0之后就去掉了,产生了自有库,写法参展jquery)。
3)其他.js文件:诸如demo1_edgeActions.js,demo1_edge.js等js文件是专门针对当前动画所生成js文件。
4).html文件:主html文件。
Edge Animate的工具界面默认情况下包含了7个Window面板,均可以通过菜单的Window选项开启和关闭,分别是Tools工具,Properties属性,Stage场景,Elements元素,Library库,Timeline时间轴,代码管理和lessons课程。接下来,简单的让大家了解一下,edge animation的属性面包,元素面板,时间轴runtime,代码管理器,这些是我们经常会用到的。
edge animation 属性面板
Adobe Edge Animate的强大之处在于它能获得舞台中元素的标签(即ID)和这个元素样式属性(CSS)的变化,并将这些变化在时间线上以关键帧的形式“标记”。这样必然导致关键帧之间属性值的变化,这个中间阶段会被转化为一个过渡阶段,或者说一段动画(如图片透明度Opacity的变化,淡入淡出、一个元件的移动Location和缩放Scale等等)。
edge animation 元素面板
Adobe Edge Animate元素面板显示的是节点式的树形结构,表示元素与父元件Stage的联系。这点与ps,flash都比较类似。
放置(或堆叠)在上方的元素具有更高的Z-index值,会显示在其他元素(堆叠在下方的元素)上方。
edge animation 时间轴runtime
Adobe Edge Animate的时间线融合了元素的节点式树状结构和与元素属性关联的关键帧,这些信息显示在时间线左侧,而右侧则显示时间信息表。元素属性值(关键帧)被标记在时间线确切的时间点,当属性值发生改变时,过渡阶段会产生动画。
1.动画标签(Label)和触发器(Trigger):自定义的时间线动画标签和触发器也可以控制舞台中元素动画的状态和处理方法;使用Ctrl/Command + L可以在播放头所在时间点生成一个动画标签,使用Ctrl/Command + T可以在播放头所在时间点放置一个触发器。
2.仅显示具有动画的元素(Filter Animated Elements)
3.启用时间线吸附功能(Enable Timeline Snapping):拖动播放头,播放头将会自动吸附到时间点、动画或关键帧等
4.启用时间线网格(Enable Timeline Grid):时间信息表上将会显示时间网格,方便设计者在确切时间点设定关键帧等,网格的大小可用户自定义。
5.自动记录关键帧模式(Auto Keyframe Mode(K)):开启模式下,设计者对元素所做的属性值修改都会以关键帧的形式记录在时间线上。
6.自动生成动画模式(Auto Transition Mode(X)):开启模式下,在两个关键帧之间会自动生成动画,默认开启。
7.播放标记(Toggle Pin(P)):可以设置播放的起始和终止位置。
edge animation 代码管理
代码管理器使用一个单独完整的窗口界面来展示所有事件控制代码和时间线触发器。(快捷键Ctrl/Command + E或者通过菜单Window-Code打开)所被编辑的控制代码或触发器会被高亮显示。
1.点击“+”图标添加全局、局部、时间线控制代码或者触发器;
2.点击“Full Code”按钮可以显示并编辑单个文件的全部代码,而不仅仅是单个功能函数内部代码;
3.可以通过下方的代码错误提示窗进行错误快速排除检测。
当我们了解以上这些知识点,之后,我们来做一下简单的实例,实现顶部图片我们所看到的界面,人物跑动,背景向前移动。
1.启动Edge Animate,创建一个新项目blackfriday,设置stage大小为640*1136;
2.导入blackfriday SpriteSheet
点击菜单“File->Import”,或者快捷键Ctrl+I / Command+I,来导入blackfriday_sprite.png到舞台。在舞台上选中导入的图片,在左边栏Position and Size栏目下,设定其X坐标值=160px,y坐标值为330px,保证第一个smurf的位置在舞台最左侧并垂直居中。
3.使用Clip切割Spritesheet,显示单一Sprite元素
我们导入的蓝精灵Spritesheet是320*480大小的透明背景png图片,一共描绘了32个蓝精灵行走的姿态。通过序列播放这32个行走姿态,就可以制作一个完整蓝精灵行走动画。
首先,要在舞台上只显示一个Sprite元素,比如最左上角的第一个蓝精灵。在Edge Animate左侧属性面板中,倒数第二个栏目是clip子面板。顾名思义,clip功能区可以帮助我们“切割”舞台上的元素,比如图片。该功能区实际上利用了CSS的clip属性。
点击clip子面板的开关按钮,可以激活clip功能。使用鼠标,可以在clip属性设定面板上的上、下、左、右属性值上滑动,你可以在舞台上实时看到“切割”的图片情况。如下图。 在本例中,请设定clip的top,right,bottom,left属性值分别为0, 320,480,0。
5。创建完成后,预览:
使用快捷键Ctrl+Enter/Command+Enter,可以启动默认浏览器看起运行的效果.
现在,人物已经实现了行走动作,但是还没有完成行进,还不循环播放,运行完32个帧就停止了。
6.转换为元件在最后一帧,设置trigger触发器,达到帧循环动画
在舞台上选中该图片后,快捷键Cmd+Y/Ctrl+Y将其转换成一个元件(Symbol),叫us,然后删除stage舞台上面的元素,从library面板里面拖动stage舞台上面 。双击元件,进入元件舞台区域,如图,insert trigger:
1.倒入背景图片,生成元件
快捷键Cmd+I/Ctrl+I导入背景图片american.png,在舞台上选中该图片后,快捷键Cmd+Y/Ctrl+Y将其转换成一个元件(Symbol),命名为americanbg2。
2.激活Auto-Keyframe Mode,Auto-transition Mode,平移背景图片形成单循环的过渡动画。
但是当背景向左移出舞台的时候,会看到舞台空出了部分
3.实现背景图循环滚动的效果
再次向舞台导入元件,命名为americanbg4.拷贝americanbg2的运动动画,到americanbg2左移舞台出现空白的临界点帧时间,给americanbg4粘贴动画效果。因为我们不需要americanbg4整体效果,所以需要删除americanbg4运动帧超出americanbg2运动帧的结束点。
4.最后一帧,设置trigger触发器,形成循环动效
这一部分,基本参照第一二部分的我们就可以完成整个动画,有一点需要注意设置playback,做初始延迟。
由于这里出来的动画是640x1136px出来的效果,实现多终端预览时,需要做适配处理,这里我是采用scale进行缩放,已到达统一的效果。
我们只需要把class类o2-scale,添加给舞台就可以,完成适配。
demo演示效果
手机扫描:
ios,android亲测,效果还是比较流畅的。
Adobe Edge Animate CC 2014 JavaScript API 5.0
Adobe Edge Animate 视频教程
作为一个注重用户体验的 H5 页面,合适的页面布局方式很重要。
移动端页面常规布局基本分两种:
这是普遍使用的方法,流式布局使用这个标签即可自适应所有尺寸的屏幕。
优点:响应式、简单、兼容好。
缺点:非设计稿尺寸屏幕展现效果可能不是很理想。
可行方案:
优点:页面等比缩放,比例与设计稿一致。
缺点:大屏手机等比放大后的体验可能不佳。
两种布局方式各有优劣,项目最终选用流式布局,原因:
设计规范也是重要元素之一,条件允许情况下,从开发前期就需要跟设计师达成共识制定了一套设计规范。
设计规范需要包括:
页面背景色、文字颜色、边框颜色、各种按钮样式、图标等等全局通用样式。
当然除了前期到设计规范,开发过程中这边也需要分离出一些可复用的组件、公共样式,包括:
公共底部、商品组模块、轮播组件等等。
在 2014 年的 WWDC,“设计响应的 Web 体验” 一讲中,Ted O’Connor 讲到关于“retina hairlines”(retina 极细的线):在 retina 屏上仅仅显示 1 物理像素的边框。
简单点说就是:在 Retina 屏的设备上,1px 其实相当于 2 个物理像素,所以 1 个物理像素 = 0.5px。
实现 0.5px 有很多种方法,这里比较一下各种处理的优缺点:
实现原理:常规属性。
|
|
优点:原生、简单、常规写法。
缺点:目前只有 iOS8 以上系统才能支持,iOS7及以下、安卓系统都显示为 0px,可以通过脚本判断系统然后区分处理。
实现原理:设置 1px 通过 css 实现的背景图片,50%有颜色,50%透明。
优点:兼容性较好,单边框、多边框可实现,大小、颜色可配置。
缺点:代码量多、无法实现圆角、同时占用了背景样式
实现原理:利用 css 对阴影处理的方式模拟。
优点:兼容性较好,单边框、多边框、圆角可实现,大小、颜色、可配置。
缺点:模拟效果强差人意,颜色不好配置。
实现原理:通过设置页面 viewport 与对应 rem 基准值。
|
|
优点:兼容比较好,写法跟常规写法无异。
缺点:老项目改用 rem 单位成本较高。
实现原理:通过图片配合边框背景模拟。
优点:无。
缺点:图片边缘模糊,大小、颜色更改不灵活。
实现原理:利用 :before/:after 重做 border,配合 scale 使得伪元素缩小一半
优点:实现简单、单边框、多边框、圆角可实现,大小、颜色可配置。
缺点:代码量多,可通过 sass 预处理器处理。
经过比较与实操测试,最好的处理方式是 CSS3 缩放,目前项目已经在广泛使用。
Flexbox Layout(弹性盒模型)模块(目前W3C工作草案正在最后通过)的目的是为了提供一种更有效的方式来布局,使各模块即使大小是未知或者动态的也可以在项目空间中合理分配位置(就像“弹性”这个词一样)。
项目哪些地方可以使用 flexbox?比如这些
|
|
实现的方法有很多种,下面几种比较常用:
实现方法:表格内容本来就是垂直居中的,可以通过模拟表格处理。
|
|
实现方法:利用空元素占位实现
|
|
|
|
|
|
兼容性:Android2.3 系统浏览器不支持容器直接使用 fixed 进行定位,外加 fixed 容器可解决。
经过各种场景的适用型比较,项目使用 transform 实现,兼容性好且使用方便。
图片未加载出来之前浏览器是无法计算出图片实际尺寸的,所以会出现一个问题,页面刚打开各种元素会因为图片未完全加载而跳动/错位。
这里推荐一种兼容性很好做法简单的方法:
实现原理:
通过图片宽高比例计算出图片所需占位空间,赋值于外容器,图片再绝对定位在等比缩放的容器当中。
计算公式:
padding-top = 图片高度(px)/图片宽度(px) * 100%
比如:
1:1 比例的图片 padding-top: 100%;
2:1 比例的图片 padding-top: 50%;
大多图标元素都可以使用 CSS 样式绘制,可以大大减少样式图资源请求还有增强图标可维护性。
比如:链接箭头、圆点、优惠券点点、选择框、checkbox等等。
WebP 格式,由 google 于 2010 年推出的新一代图片格式,在压缩方面比 JPEG 格式更优越,并能节省大量的服务器带宽资源和数据空间。与 JPEG 相同,WebP 也是一种有损压缩,主要优势在于高效率。在质量相同的情况下,WebP 格式图像的体积要比 JPEG 格式图像小 40%。
项目中大部分页面素材图都使用了 WebP 格式,兼容方案:
打开场景是浏览器:通过 javascript 检测,对支持 WebP 的用户输出 WebP 格式图片。
WebP 支持 插件:http://webpjs.appspot.com/
打开场景是APP:根据不同系统使用 Android WebP 解析库或iOS WebP 解析库做兼容解析。
Android4.0 以下解析库:https://github.com/alexey-pelykh/webp-android-backport
iOS 解析库:https://github.com/carsonmcdonald/WebP-iOS-example
PS:除了 Android4.0 以上提供原生支持
资料参考:http://isux.tencent.com/introduction-of-webp.html
结论:
保证图片高清质量的同时,大大压缩了图片大小,节省了服务器带宽资源和数据空间。
注: APP缓存策略由开发同学提供
模块化的使用 APP 缓存,写入磁盘,包括 HTML、JS、CSS。
图片使用浏览器缓存,稳定后的背景图以及常用图片也使用 APP 缓存。
主模块更新:APP 打开就发送主模块版本号到服务端,返回有更新的模块内容以及版本号,并且返回所有模块版本索引。
子模块更新:进去一个主模块,在加载完成后,会检查所有子模块版本索引,并获取需要更新的模块内容。
当前模块更新:在直接打开模块时(非首页进入),会去 check/更新一下当前版本,然后在加载。
这篇文章仅仅是向你介绍应对下面这种的场景的方法:如果有人突然跟你说,你的皮肤挺不错的耶,你用的什么洗面奶,我也想买一个,你会怎么回答呢?手头又没法拿出你的洗面奶给他看,光凭言语无法准确地形容出你的洗面奶到底长什么样。作为前端的程序猿,我会想,这种时候如果可以有一个链接让对方自己去看一下把玩一下,对方势必会对你的洗面奶高Bigger有更深刻的认识。本文的目的,就是向屏幕对面的程序猿介绍这种高效搞笑的方式。
在页面里面放几个图片是完全无法满足让观看者自己看自己把玩的要求的。所以这里我们将会使用3D的形式来展现我们的洗面奶。老话说得好,同样是山,却有横看成岭侧成峰的不同。比起2D渲染,3D渲染多了一个维度,由于透视效果,物体遮挡、光照角度、光的反射折射等的存在,观看者在不同的角度观看会得到不同的结果。
在浏览器里面,CSS3提供了3D变换的相关属性,但对于光照相关的需求却是无能为力。而使用Canvas进行绘制的话,如果不依赖封装好的图形库,进行图形变换又是相当麻烦的事情。就算是在CSS中一个简单的2D旋转或者是放大,在Canvas的像素操作中,我们还需要通过矩阵计算才能知道变换后每个像素的位置。正因如此,图形库出现了。
说到图形库,我们不得不提到OpenGL。OpenGL是一个跨平台的图形编程规范,定义了2D与3D绘制中所需要各种接口,进行图形绘制中所需要的变换,纹理映射,光照等。OpenGL定义的接口很多是为硬件加速设计的。有了各大硬件厂商的支持,OpenGL的渲染效率比起软件渲染高了不止一点点。同时,OpenGL不局限于某平台或者是语言,它只是一个关于图形渲染的规范,对外提供关于图形渲染的各种接口,所以有许多的语言绑定,而在浏览器中用到的是Javascript的绑定WebGL。
ThreeJS是一款在浏览器中进行3D绘制的Javascript库,为使用canvas绘制,WebGL渲染等图形操作提供了简便的API。到底有多简便?在PixiJS等2D绘制库中,我们需要场景+物件+贴图来搞定一张图,而在ThreeJS中我们只需要在这基础上额外添加适当光照与一台照相机而已,下面是一个最简单的Demo,绘制了一个旋转的绿色立方体。
|
|
画出来就像这样子:
ThreeJS中提供了少量基础的几何模型,如长方体(Box3),球体(Sphere)等,但面对我们要实现的洗面奶还是太小儿科了。这是不是说明我们的洗面奶没办法做了?文章都写到这里了,办法肯定是有的。ThreeJS提供了加载外部模型的模块(Loader),可以加载外部的Obj,json等格式的模型。另外,ThreeJS的Github仓库中还提供了在3ds Max、Blender等3D绘制软件中导出模型的工具。是的,我们就可以用别的3D建模软件建模再导出成ThreeJS所需要的格式了。
我们这里使用的是建模工具是Blender。我们需要先拍下物体的三视图作为建模的参考。导入Blender后,依据三视图,我们很快就可以建出洗面奶的模型。
模型的样子
在Blender中加上ThreeJS的插件之后,我们可以在Blender的文件菜单中见到Export/Three.js(.json)选项。点击之后,选择导出的目录,然后记得在左下角勾上我们要导出的元素。在这个例子中,我们需要导出的是Scene,也就是场景本身。
在ThreeJS中进行导入的操作也十分简单。ThreeJS中提供了许多种类的Loader,分别针对不同的使用需求。比如,JSONLoader针对的是.json格式的模型,OBJLoader针对的是.obj格式的模型等等。翻阅网上资料的时候还可以看到SceneLoader的踪影,这就是用来加载整个场景的.json格式文件的。可是在ThreeJS的新版本中,SceneLoader已经被废弃,取而代之的是更为牛叉更为智能的ObjectLoader。ObjectLoader可以判断导出的模型到底是什么种类,从而将它们转化为ThreeJS中的对应对象便于开发者使用。
在这个例子中,我们导出的.json文件中包含的是场景本身。所以,除了需要添加部分ObjectLoader的代码,其余部分的代码甚至比上面那个例子还要简单:
|
|
浏览器中一看,却不太对劲。形状对了,可是颜色呢?高端黑的洗面奶怎么就变成这么山寨的颜色了,而且每次刷新都变颜色。活了二十几年,小便表示还真没见到过这样的洗面奶。
出现这种情况,毫无疑问是材质的问题。原来我们在建模软件中还没给洗面奶加过材质,所以ThreeJS加载完我们的.json文件后,发现只有模型却没有材质,就给模型加上了一个随机颜色的材质。解决办法也很简单,在ThreeJS中手动贴上贴图就好了。而更简单且有效的办法是,在建模软件中上好贴图再一起导出。
回到Blender中,将洗面奶的表面进行UV展开后导出展开图后,我们新建一张图片,将我们要贴的图放到展开图上的相应位置,再回到Blender中将这张新的图片设为瓶身的材质。渲染一下,检查到效果无误后,将模型导出。这次要记得将左下角的Materials勾上,另外还需要勾上texture的复选框。
将这次导出的模型放到先前的目录下,我们会发现,浏览器中并没有出现想象中的场景。在控制台中可以看到,由于我们没有将贴图一起放到一个目录下,贴图加载失败了。按照要求放好后,我们会更惊奇地发现,浏览器中除了一片黑,什么都没有。这是为什么呢?
神说要有光,所以就有了光。
在3D的世界中,光是非常重要的存在。我们之所以能看见物体,都是因为有光进入了我们的眼球。除去本身会发光的物体,我们能看见的其他的物体,都是因为这些物体将外界的光反射后进入我们眼球了。换句话说,没有光的话我们就什么都看不到。
在ThreeJS中也是如此。如果没有光,我们就看不到自发光以外的材质,视野中将是一片黑。在ThreeJS中,光照也有很多种:全局光照(AmbientLight),有向光(DirectionalLight),点光源(PointLight)等。在这个例子中,为了360观看整个物体,我们添加一个全局光照。
|
|
再刷新一下,就可以看见我们的洗面奶了。大功告成!
进行3D建模,将物体在浏览器中展示,目前已经有了不少的应用,有的公司在宣传新产品的时候会使用上这样的技术,让消费者可以在浏览器中亲自把玩产品,观察产品的每个小角落,起到了不错的效果。同样的技术并不只是在展示商品的时候能用上。将适当的全景图贴在立方体的内表面,用户视角置于立方体中心点的话,还可以让用户有置身其境的感觉,可以上下左右转动视角观察一个地点周围的景象,做出街景的效果。可以说,浏览器中的3D技术将会有越来越多的用武之地。
]]>注:此文非干货聚集地,来找干货的对不住了。
注注:又多又大的图预警!!!
上回说到CSS3动画与传统动画之间千丝万缕的联系,现在就让我们来探讨一下用CSS3动画做一部动画电影都需要些什么。
一、
首先你需要一个故事。
即使只是一堆雪花往下掉,也是包含故事的——为什么下雪?是冬天来了?那是冬天的第一场雪吗?第一场雪有什么特点呢?好吧作为一个从没见过雪的南方人我承认我给自己挖了个坑,不过就是类似这种思路,让我们拥有了一个故事,所以,即使只有一秒钟的动画也是有故事的。Use your imagination.
小tip:在做影视题材的专题页时,我会首先根据相关影视的预告片确定入场动画的风格与基调,观看预告片不仅能够了解影片的风格,同时还能学习其字幕出现、消失以及转场的方式,获得一种节奏感,也就是上面所说的时间掌控。在看电影正片时也可留意影片开头与结尾字幕出现的形式,尤其是科幻片,电影字幕的设计与电影风格相辅相成,常常能让你脑洞大开——原来还能这么玩。
|
|
二、
当我们在脑内小剧场构思好动画小故事之后(当然,你也可以将它写下来),我们就可以进行关键帧与时间轴的设计了。
任何人都可以用电脑动画软件将一个物体移动。但是如何赋予物体重量、大小、规模、移动和幽默感,这些都与你如何移动物体相关。电脑不能为动画师创造动画,动画师仍然需要了解时间掌握的原则知识以赋予电脑动画生命力。(《动画的时间掌握》)
这时需要注意的是因果关系对动画的影响,“一个动画师必须懂得自然界物体运动的力学知识”,这样“才能创造情绪和表达正确的感觉。”我们来看看为了使动画更加流畅真实,迪士尼爷爷想出了什么办法。[白雪公主与七个小矮人]作为80、90后动画电影启蒙,使用了一项革新动画制作的技术——转描机。
视频中有一个细节,迪士尼爷爷让动画师注意那位大叔在跳踢踏舞时重力对裤腿的作用(19分15秒)。是的,迪士尼爷爷强调的就是动画与物理学的关系。其实即使是网页中的动画也能用到牛顿运动定律,将网页元素看作一个有重量、有结构、有柔韧性的物体进行动画设计,会得到意想不到的效果。事实上已经有人这么做了——
Adds a bit of realism to an otherwise static interface. ——The 12 basic principles of animation - The Art of Animation
Using Bounce.js and classical animation concepts to bring life to user interfaces. ——Giving Animations Life
三、
不断的修改与调整。
这是一个需要细致与耐心的过程,你得在不断的调整中保持大局观,避免陷入细节的纠结,同时又需要有能够将别扭的细节调整好的灵感。说白了就是同时拥有汉子的粗犷与妹子的细腻。节奏是一个很重要的要素,与银幕上的动画类似,CSS3动画创作者的意念必须即时并完全交给观众。
意念清晰易懂靠两个因素:1、好的表现手法和设计,要使每个主要动作能以最清楚和最有效的方式呈现在银幕上。2、好的时间掌握,要有足够的时间先使用户预感到将有什么事情发生,然后用于表现动作本身,最后要有好的收尾。这三者中,任何一项所占时间太多,便会感觉节奏太慢,用户会感到不耐烦,动画的出现便如同鸡肋。反之,如果时间太短,那么用户在注意到它之前,动作已经结束,创作者的意念未能充分表达,就浪费掉了。——《动画的时间掌握》
四、
别忘了进行性能测试。
这是一步有可能推翻前面两步甚至三步的一个步骤。但是即便发生了这样的事,也不要气馁,这并不意味着之前做的前功尽弃,反而是个宝贵的财富——对于性能的感受又多了一次体验,而其中的一些动画心得或许下次也能用上。
说了这么多,一切都显得辣么抽象,下面就上栗子。
这是个使用了最简单的css属性——padding、line-height、box-shadow——实现了令人吃了一斤效果的栗子,就像一道脑筋急转弯一样让大家对CSS3的动画的理解不止于CSS3的新属性,我们曾经用烂的CSS2.0属性同样也能开出花儿。
我们看到,CSS3动画并不只是由transform、opacity等等简单组成,它还可以包含许许多多的设计、想法、甚至感情。台上一分钟,台下十年功在动画上也适用,或许在所有事物上都适用。
目前为止用CSS3动画拍电影只是个概念,但想象一下你是这部电影的导演,所有元素都是可调度的场景与角色,用CSS3动画拍电影是不是也没有那么遥远了?
最后,我想将我一直以来没能找到合适实现方法的动画效果放上来,希望能够抛砖引玉、集思广益:
火焰的动作受火的上部流动着的空气的控制。最热的部分在火的中央,在这之上热空气上升,当热空气上升时,旁边的冷空气冲入取代热空气的位置。这部分冷空气变热后又上升,这个过程重复不已。空气的流通常常使火焰成为粗略的圆锥形,由冷空气的漩涡形成一连串锯齿状火焰,从火的底部向里和向上移动。(《动画的时间掌握》)
除了空气与火焰的关系外,火焰的运动由于随机性很大,循环动画需要写得看不出动作在循环也是难点之一。如何才能在保证结构、性能的同时做出最佳的火焰效果?
最最后,放上迪士尼爷爷的一段话,在我做动画甚至做任何事时它将不断地在脑海中回响:
曾经有人问迪士尼,[白雪公主]大受欢迎的秘密是什么?他回答说:
“我们只能确定一件事,每一个人都有童年,每次拍一部新片,我们不是为大人而拍,也不只是为小孩子拍,我们是为了唤醒每个人内心深处那种早就被遗忘的纯真世界。”
《动画的时间掌握(修订版)》[英]哈罗德•威特克;[英]约翰•哈拉斯;[美]汤姆•赛图
了解所有与CSS3动画相关的属性,使用动画工具更直观地调整变形。
最近存在感极强的动画12原则,如果想要做出自然流畅的CSS3动画,这个由迪士尼动画巨头所总结出的经典原则绝对是必读之物。这里附上视频版:http://vimeo.com/93206523
注:此文非干货聚集地,来找干货的对不住了。
注注:又多又大的图预警!!!
CSS3动画的变形基础(transform)包含4种变形方式(translate、rotate、scale、skew),同时还可设置2D、3D、变形原点(transform-origin)、透视(perspective)、透视原点(perspective-origin)等等特性;动画时间频率包含9种基本模式(ease、linear、ease-in、ease-out、ease-in-out、step-start、step-end、steps),甚至还可以使用cubic-bezier写出任何你想要的模式;再加上动画持续时间(animation-duration)等设定,各种排列组合,CSS3动画简直拥有了整个世界!
根据维基的释义,动画是指由许多帧静止的画面,以一定的速度(如每秒16张)连续播放时,肉眼因视觉残象产生错觉,而误以为画面活动的作品(gif图片正是运用这种原理)。因此最初的动画是通过几张快速翻动的连续画面制作而成,而后经历了电影摄影技术的出现、电脑科技的进步,逐渐转向数字化。
无论是2D还是3D动画,关键帧,正如其名,是动画制作中最关键的部分,同时也是最难把握的部分。曾经有位设计师告诉我,在大学的第一节flash课的课后作业,老师要求大家上交一份小球动画,包含气球、石头球与皮球,并告诉大家,以相同的外观表现出不同的质感是在考验你对关键帧的悟性,而这一个作业就能体现你是否适合学习动画。
A 需要很大的力才能使一个炮弹移动。一旦它移动了,同样需要很大的力才能阻挡它前进。B 一只汽球只需要很小的力去移动它,但空气阻力使它很快停止动作。这两个例子都画了动作分格线,可以看出在银幕上表现物体的轻重,取决于对它们动作的时间掌握。(图片来源:《动画的时间掌握》)
在《動畫製作流程介紹》提供的视频中可以看到关键帧在动画制作中所起的地基般的作用。
与关键帧紧密关联的即为时间轴(或摄制表),时间轴是补齐中间帧不可或缺的一项,在传统动画制作中,导演就是通过制定时间轴来掌控整部动画的节奏。
在CSS3中,@keyframes正是动画的关键帧容器。@keyframes中包含的包括transform在内的元素形态设定构成了关键帧的画面。@keyframes中的百分比即为时间轴的体现。中间帧则由浏览器自动完成(就像flash中的补全动画)。
|
|
现在我们知道了CSS3动画的结构与传统动画之间的关系,重点来了, CSS3动画可以做出一部动画电影吗?欲知详情,且听下回分解。
SVG即Scalable Vector Graphics可缩放矢量图形,使用XML格式定义图形。
优点:
|
|
|
|
|
|
svg sprites类似于css sprites,将各个svg合并在一起。
最主要的优点就是能减少页面的加载时间,优化开发流程,以及保持页面中图片元素的一致性。
实践中我们可以把整块的svg放在head头部, 因此可以在一处地方更新svg即可,而不是让svg的代码块散落在文档的各个地方。
在这里https://icomoon.io/app/#/select可以设置sprites,可以自己导入svg。
在head头部的svg中使用symbol元素,并使用id属性。其中symbol类似flash中的元件,可多次使用。
然后在需要用到这个元件的地方使用use元素引用元件。其中xlink:href=”#truck”相当于元件的链接,通过引用元件的id来实现。
兼容性详细情况请点击 此处
一般在html中使用SVG有三种方法:使用<img>元素来嵌入SVG图像;将SVG图像作为背景图像嵌入;使用<svg>元素,通过代码将SVG图像嵌入到HTML代码中。
使用过程中,可通过svg sprites提高性能,通过symbol以及use元素提高文档的可维护性等。
|
|
解释:x为x坐标,y为y坐标;width 和 height 分别为形状的高度和宽度;rx 和 ry 属性可使矩形产生圆角。
另外,下面三个属性是文章后面会用到的,之后不会过多讲述:
|
|
解释:cx 和 cy分别为圆点的 x 和 y 坐标;r为半径。
|
|
解释:cx 圆点的 x 坐标,cy 圆点的 y 坐标;rx 水平半径,ry 垂直半径。
|
|
解释:(x1,y1)为线条的开始坐标;(x2,y2)为线条的结束坐标。
|
|
解释:points 属性定义多边形每个角的 x 和 y 坐标。为了可读性,建议x与y坐标用逗号分开,每个角之间的坐标用空格分开。
|
|
解释:points 属性定义多边形每个角的 x 和 y 坐标。
直线指令:
M = moveto
L = lineto
H = horizontal lineto
V = vertical lineto
Z = closepath
注释:以上所有命令均允许小写字母。大写表示绝对定位,小写表示相对定位。
解释:该路径开始于位置 250 150,到达位置 150 350,然后从那里开始到 350 350,最后在 250 150 关闭路径。
由于绘制路径的复杂性,建议使用 SVG 编辑器来创建复杂的图形。
贝塞尔曲线指令:
C = curveto
S = smooth curveto
Q = quadratic Belzier curve
T = smooth quadratic Belzier curveto
贝塞尔曲线控制小工具http://dayu.pw/svgcontrol/,操控多次可加深对贝塞尔曲线的理解。
CSQT比较难记,联想记忆法“厕所切图(CSQT)”就比较容易记住了。
厕所是3D空间,所以CS是三次曲线噢,切图是平面图,所以是二次曲线,很好记。
|
|
最后一个坐标(x,y)表示的是曲线的终点,另外两个坐标是控制点,(x1,y1)是起点的控制点,(x2,y2)是终点的控制点。小写指令c表示相对坐标。
|
|
之所以S命令没有x1 y1,是因为S命令跟在C命令后,x1 y1默认是C命令的第二个控制点的对称点,如下图蓝色线条所示。
如果前面没有C命令,即S命令单独使用时,S命令画出来的是二次贝塞尔曲线,因为x1 y1和x2 y2默认是同一个控制点了。
|
|
|
|
T命令的控制点默认是前面C命令的控制点的对称点。
如果T命令前面没有C命令,即T命令单独使用时,T命令画出来的是直线。
路径比基本形状更强大、更灵活。使用路径绘制复杂图形比较麻烦,但是使用它们来绘制图形的效果大多数时候很赞,一般其他基本图形是做不来的。
DEMO:
g元素是用于把相关元素进行组合的容器元素。animateMotion使元素沿着动作路径移动,且该路径不可见,path定义的是可见的路径。rotate=”auto”使元素移动得更加自然,会随着路径旋转一定的角度。
DEMO:
在path等元素上必须使用stoke属性,否则动画不执行。stroke 表示描边颜色;stroke-width 表示描边的粗细;
使用CSS3 animation实现,因为内联的svg本身就是Html元素,可以通过CSS3来控制颜色等属性。
stroke-dasharray 表示虚线描边;stroke-dashoffset 表示虚线的起始偏移。
clip-path按照路径内部的尺寸进行裁剪。只有路径内的内容可见。使用方法如下:
|
|
DEMO1:
利用伪元素:before和:after实现相同大小相同位置的文字,使用clip-path分别裁剪文字:
DEMO2:
如果结合clip-path和keyframes动画,可实现相关元素按照我们的路径逐步出现的效果。clip-path可通过此工具http://www.bennettfeely.com/clippy/实现复杂的路径。
本章主要介绍svg路径动画,svg路径描边动画以及css中的clip-path,从中我们也发现了路径的强大之处,如若能在项目中灵活使用,无非给动画添加更生动的效果。
]]>$ git config --global user.name "Your Name"
$ git config --global user.email "email@example.com"
git tracked的是修改,而不是文件
#将“当前修改”移动到暂存区(stage)
$ git add somfile.txt
#将暂存区修改提交
$ git commit -m "Add somfile.txt."
$ git status
$ git diff
# 放弃工作区修改
$ git checkout -- file.name
$ git checkout -- .
# 取消commit(比如需要重写commit信息)
$ git reset --soft HEAD
# 取消commit、add(重新提交代码和commit)
$ git reset HEAD
$ git reset --mixed HEAD
# 取消commit、add、工作区修改(需要完全重置)
$ git reset --hard HEAD
$ git reflog
$ git log
$ rm file.name
$ git rm file.name
$ git commit -m "Del"
$ git remote add origin git@github.com:michaelliao/learngit.git
# 第一次推送,-u(--set-upstream)指定默认上游
$ git push -u origin master
$ git push origin master
$ git clone https://github.com/Yikun/yikun.github.com.git path
$ git clone git@github.com:Yikun/yikun.github.com.git path
# 查看当前分支
$ git branch
# 创建分支
$ git branch dev
# 切换分支
$ git checkout dev
# 创建并checkout分支
$ git checkout -b dev
# 合并分支
$ git merge dev
# 删除分支
$ git branch -d dev
$ git tag 0.1.1
$ git push origin --tags
注意:本文Fork自 yikun.github.io,凹凸实验室作进一步完善。
]]>记得曾经带我的师傅说过一句话:
重构工程师不应该满足100%还原设计稿,更应该去追求101%还原度。
101%还原是重构工程师在还原设计稿的过程中,发挥自身的主观能动性,对设计稿进行修正或向产品、视觉、交互提出合理的建议,让最终还原出来的视觉设计稿更接近设计本意。
101%的还原可以归为两类,一类是对视觉稿的主动修正,不包括具有信息传达的内容;另一类是对视觉稿深入思考并提出合理建议,包括但不限于具有信息传达的内容。
视觉设计师有时候会比较忙,设计稿中可能会出现像素之间的误差而造成不对齐的情况,如下面的设计稿:
(Retina 2X图)
很明显,视觉设计师原意是想让右则的按钮垂直居中对齐的,我们在还原的时候可以去微调边距让之对齐,得到下面的重构稿:
(重构稿1X图)
可以看到,还原出来的设计稿虽然和原设计稿并没有100%吻合,但是出来的效果确实更符合垂直居中对齐的准则,更接近设计的本意,而且节省了沟通的成本。
有些同学许会问,在没有周知视觉设计师的情况下私下改动设计稿貌似有点不太尊重视觉设计师的工作吧?
对视觉稿的主动修正有一个很重要的原则,就是在没有信发生息传达的情况下修正,所作的修正不能违背设计本意。拿刚才的例子来说,如果我个人不喜欢按钮的边角是方角的,就私下把方角还原成圆角,如下图:
那就产生了还原稿与设计原稿之间的信息传达内容了,因为方角和圆角是两个不一样的设计风格,圆角按钮明显不是视觉设计师的设计本意,如果圆角按钮在视觉设计师完全不知情的情况下上了线,那就是真正的不尊重视觉设计师的工作了。
当然视觉设计师都希望自己输出的设计稿完美无缺,但一些客观因素(交接模块)或主观上的操作(工作太忙,拖动元素排版的时候手抖)难免会让设计稿存在一些像素级的误差,对视觉稿的主动修正,是和视觉设计师协作的一种默契,如果每一项这样的修正都要让视觉设计师重新去调整再生成新的PSD文件的话,沟通成本未免过高
我们先看一下下面的设计稿:
从视觉稿来看,视觉设计师把“关注本店”、“关注公众号”和“联系卖家”这三项的按钮所占的区域作了等分处理,按钮里面的文案和icon水平居中
等分的排版,虽然个体之间的内容不一样会出现内容与分隔线边距不相等的情况,但是在按钮所占展示区域比例相等的情况下,三个按钮整体看上去是非常整齐的,排版逻辑很清晰:
可以看出,让三个按钮在页面上的排版整体看起来是整齐的是设计师的设计本意,那么问题来了:
在还原设计稿中,我们通常会考虑到布局的扩展性,会考虑到不足三个按钮或者多于三个按钮的排版情况,如果按照等分的排版型去还原,按钮出现增减的话,就会出现下面的情况:
(等分四个按钮)
(等分:三个按钮)
(等分:两个按钮)
(等分:一个按钮)
可以看到,大于3个按钮的时候,文案的展示出现了问题,小于3个按钮的时候按钮的大小和文案与分隔线的间距就略显夸张了,特别是只有一个按钮的情况,几种情况的排版在视觉上变化都很大,而且视觉稿里是没有相应的展示的,那么这里面包含了排版之间的信息传达。
在还原过程中,当发生信息传达的时候,任何疑问和建议都应该周知相关的负责人,这是101%还原中的基本原则
把刚才上面的几种情况,写好demo向产品和视觉设计师展示,反馈并确认两个问题:
按钮在不同的场景中最多展示的个数和最少展示的个数
如果按钮展示个数有变,用等分自适应排版的话,以上情况的排版方式能否接受
然后得到的答复是:
产品:最多展示3个,最少展示1个,排版看还有没有更好的
视觉:按钮不用等分自适应,用定宽展示的
可以get到信息:
等分自适应的排版在这里不适用:对视觉设计稿排版信息的理解与视觉设计师的原意有出入
与产品和视觉设计师对相应的信息进行确认后,于是给到了定宽的效果:
(定宽:三个按钮)
(定宽:两个按钮)
(定宽:一个按钮)
定宽排版与等分排版两个方式对比来看:
定宽排版的3个按钮与等分排版的效果一样
定宽排版的2个按钮比等分排版的效果差。
定宽排版的时候,按钮所占空间并没有等分展示区域,而文案离分隔线的间距又不相等,排版逻辑不清晰,显得很不整齐
定宽排版的1个按钮比等分排版的效果好。
等分排版的时候,虽然按钮在展示区域中是居中对齐了,但是考虑到上下文的对齐,按钮并没有和星星的对齐,相反,等宽排版的文案更接近与星星的左对齐,虽然并没有绝对对齐,只是展现出来的排版效果、与上下文的排版逻辑显得更严谨。
其实两种排版的文案都没有办法100%保证与星星左对齐的,因为文案长度不一样,居中的位置就不一样:
(定宽、等分的居中文案)
综合以上,向产品和视觉设计师提出建议:按钮的文案与分隔线间距相等,并且第一个按钮的文案与星星左对齐。于是得到了最终的效果:
(优化后的)
(优化后的)
(优化后的)
这与原有的设计稿相比,扩展性更强,排版逻辑更严谨性、更趋向设计本意,建议是合理的。
优化的地方虽然不大,但我们需要对设计稿进行认真深入的分析,从产品层面、视觉层面、开发层面甚至交互层面去尽可能地解读设计稿,深入思考才能发现问题,提出合理的建议,101% 中的 1% 威力就会慢慢的程现出来。
由于项目快速迭代,视觉设计资源紧缺的原因,于是有了这张没有经过视觉流程的设计稿,是一位产品GG给到的,如果我们拿到设计稿,不加以思考的话,100%还原出来,那么
得到了第一稿:
其实认真思考一下,可优化的地方还是有的:
交互上
视觉上
复制按钮的配色欠恰当,复制的操作并不会引起用户警告或错误的提示,而红色恰好有让人警惕的意味
输入框和复制按钮的高度不一样,显得不够整齐美观
内容和顶部的间距太小,内容视觉焦点欠佳
交互和视觉上都涉及到不同的信息传达,因此不能用对视觉稿进行主动修正,将以上信息和产品反馈
最终建议:
“添加朋友” 和 “关注” 采用文本加色强调形式表现
复制按钮的颜色采取常用的给人友好感觉的绿色
调整复制按钮的调试使之与输入框的调试一样
调整内容与顶部的间距大小
得到了第二稿:
(第二稿)
第二稿中“您已收藏店铺,可在个人中心查看”的收藏反馈提示是产品新增的内容,一开始要求放在上图的位置,虽然第二稿在交互和视觉上得到了一定的优化,但还有优化的空间,特别是
产品上
最终建议:
让反馈内容居中对齐
增加与操作内容的边距,让用户可以一目了然看到反馈内容的归属
调整文案强调色的色系,让其不至于过重而显得太突眼。
最终得到101%的还原效果:
这个例子中,最终还原出来的效果也许没有视觉设计师那么专业,但至少还原稿上多了一份思考,多了一份心思,在这过程中也能让大家去探讨一些非自己职位上的知识,互相学习,互相提高。
从100%到101%的过程,其实就是一个主动思考,积极执行的过程,是一个追求极致的过程,是一个把事做好的过程。在还原设计稿的过程中,1%在100%中占的比例不多,但是如果能把这1%处理得好,还原出来的设计稿会更具级数。
]]>phantomJs是一个基于WebKit的服务器端JavaScript API。它全面支持web而不需浏览器支持,其快速,原生支持各种Web标准: DOM 处理, CSS 选择器, JSON, Canvas, 和 SVG。PhantomJS可以用于页面自动化,网络监测,网页截屏,以及无界面测试等。
网络上有大量的安装教程,如果你用的osx,建议直接
brew update && brew install phantomjs
。此处只叙述下在安装过程中遇到的一个坑。
在Mac OS Yosemite 版本可能都会遇到这样一个问题:
运行 phantomjs hello.js
后得到了报错信息:Killed: 9
这个问题的原始链接在这里github:issue/12928 解决方案如下:stackoverflow
$ brew install upx
$ upx -d phantomjs-2.0.0-macosx/bin/phantomjs
$ ./phantomjs-2.0.0-macosx/bin/phantomjs
|
|
把以上代码保存到screen.js,切到terminal:$ phantomejs screen.js
可以得到如下的网站截图:
当然还可以继续page.scrollPosition以及Js脚本做延时截图,来避免截图中出现图片未加载完全等问题。
|
|
phantomJs2.0中click事件不是标准事件
若page.evaluate
中操作dom时并未引入jQuery,则应自己实现一个click事件如下:
|
|
解决方案来自stackoverflow:questions/15739263
登录后截屏
可以看到此登录并未触发验证,若用别的网站被脱库的账号密码来批量查询的话,有很大概率撞库。
众所周知知乎的搜索如此之烂,刚看过的问题,再搜就搜不到了,那把知乎上你想要的分类的问题抓取下来,自己搜索咯。(当然想这么一个烂理由来抓人家的数据也是醉人)。随便抓人家的数据不好,此处只做技术分享,切勿用于商业用途。
|
|
其中用到了批量page.open 页面来控制翻页到所有的问题,然后将查询到的数据写入文件中,用到了File System API.
In CSS 2.1, each box has a position in three dimensions. In addition to their horizontal and vertical positions, boxes lie along a “z-axis” and are formatted one on top of the other. Z-axis positions are particularly relevant when boxes overlap visually.
The order in which the rendering tree is painted onto the canvas is described in terms of stacking contexts. Stacking contexts can contain further stacking contexts. A stacking context is atomic from the point of view of its parent stacking context; boxes in other stacking contexts may not come between any of its boxes.
Each box belongs to one stacking context. Each positioned box in a given stacking context has an integer stack level, which is its position on the z-axis relative other stack levels within the same stacking context. Boxes with greater stack levels are always formatted in front of boxes with lower stack levels. Boxes may have negative stack levels. Boxes with the same stack level in a stacking context are stacked back-to-front according to document tree order.
——W3C
其实说人话,大致意思就是:
每个盒模型的位置是三维的,除了x轴和y轴,还有一个表示层叠的z轴;
z轴上的位置决定了我们看到的盒模型之间的层叠效果(谁盖住谁)。
上述规范还解释了层叠上下文的特点以及盒模型的层叠级别,下面我们通过栗子慢慢探究。
eg.1-1/eg.1-2共同结构与样式:
|
|
eg.1-1:常规流中非定位非行内元素的层叠情况
结论: 常规流中非定位非行内的元素根据html顺序,按照“后来居上”的规则层叠。
eg.1-2:定位元素/行内元素/浮动元素之间的层叠关系
结论: 层叠顺序如下(高➡低):
z-index为auto的定位元素;
常规流内行内非定位元素;
非定位的浮动元素;
常规流内非行内非定位元素;
z-index为负的定位元素。
首先,你必须了解以下两点:
eg.2-1/eg.2-2共同结构与样式:
|
|
eg.2-1: 5个定位元素在未设置z-index时的层叠情况
结论: z-index为auto的定位元素根据html顺序,按照“后来居上”的规则层叠。
eg.2-2: 5个定位元素设置不同z-index时的层叠情况
结论:
定位元素的层叠级别由z-index的值决定,z-index为auto则其层叠级别为0(注意:只是层级为0,其z-index值仍为auto);
同一层叠上下文中,层叠级别大的元素位于层叠级别小的元素之上;
同一层叠上下文中,层叠级别相同的元素根据html顺序决定元素的层叠关系,遵循“后来居上”原则。
结合上面的例子进行总结,可得每一个层叠上下文内的层叠顺序:
Within each stacking context, the following layers are painted in back-to-front order:
- the background and borders of the element forming the stacking context.
- the child stacking contexts with negative stack levels (most negative first).
- the in-flow, non-inline-level, non-positioned descendants.
- the non-positioned floats.
- the in-flow, inline-level, non-positioned descendants, including inline tables and inline blocks.
- the child stacking contexts with stack level 0 and the positioned descendants with stack level 0.
- the child stacking contexts with positive stack levels (least positive first).
—W3C
规范中的描述比较乏味,可结合w3help中的一张图片进行理解:
看完上述的例子,对于层叠顺序应该大致了解了。但突然冒出一个“层叠上下文”,这又是个什么鬼?
- stacking context可以嵌套
- 每个stacking context相对于兄弟元素是完全独立的,其内部规则不会影响到外部
- 每个stacking context元素都会被父stacking context当做一个元素施加stacking规则
——Abruzzi
The root element forms the root stacking context. Other stacking contexts are generated by any positioned element (including relatively positioned elements) having a computed value of ‘z-index’ other than ‘auto’. Stacking contexts are not necessarily related to containing blocks.
——W3C
CSS2中规定创建层叠上下文的两种情况:
根元素(html);
定位元素(absolute/relative)且z-index的值不为auto。
注:在同一层叠上下文中,父元素、子元素与自身都被当作是并级关系进行层叠级别的比较。他们之间可能互相层叠。
eg.4-1:z-index为auto的定位元素没有创建层叠上下文
|
|
分析上述例子:
结论: z-index为auto的定位元素不会创建新的层叠上下文。
IE中的BUG:
在IE6-7浏览器中测试eg.4-1:
结论: ie6-7中,z-index为auto的定位元素也会创建新的层叠上下文。
A stacking context is formed, anywhere in the document, by any element which is either
- the root element (HTML),
- positioned (absolutely or relatively) with a z-index value other than “auto”,
- a flex item with a z-index value other than “auto”,that is the parent element display: flex|inline-flex,
- elements with an opacity value less than 1. (See the specification for opacity),
- elements with a transform value other than “none”,
- elements with a mix-blend-mode value other than “normal”,
- elements with a filter value other than “none”,
- elements with isolation set to “isolate”,
- position: fixed
- specifying any attribute above in will-change even if you don’t specify values for these attributes directly (See this post)
- elements with -webkit-overflow-scrolling set to “touch”
——MDN
CSS3中规定创建层叠上下文的十种情况:
eg.4-2:opacity创建新的层叠上下文
分析:
If an element with opacity less than 1 is not positioned, implementations must paint the layer it creates, within its parent stacking context, at the same stacking order that would be used if it were a positioned element with ‘z-index: 0’ and ‘opacity: 1’.
If an element with opacity less than 1 is positioned, the ‘z-index’ property applies as described in [CSS21], except that ‘auto’ is treated as ‘0’ since a new stacking context is always created.
——W3C
结论:
如果元素设置opacity小于1但未定位,视为css2中元素定位/z-index:0且opacity:1的情况;
如果元素设置opacity小于1且定位,z-index为auto,视为css2中元素定位且z-index:0的情况;
如果元素设置opacity小于1且定位,z-index不为auto,则根据css2的描述层叠。
(其他CSS3创建层叠上下文的元素情况与opacity相同,故不赘述。)
至此,应该能够明白:
图片在如今的站点上不可或缺。试想一下,在我们网页上没有图片会怎样?高质量的图片能够使你的站点更加出色,但同时伴随着一定的性能损耗。由于图片文件较大,下载时间相对较长并且会减缓页面的加载。如果是一个带宽较低的用户,用户体验将会特别差。
在移动设备上面,这种现象会更加明显。在移动设备上加载大型图片消耗时间取决你的网络以及连接速度。如果你是一个不耐心的用户,你将会变得沮丧。幸运的是,我们有能力处理 响应式图片 。通过使用 picture 标签,我们可以根据用户的设备为用户提供不同大小、设备像素比(DPR)以及格式的图片。例如,下面的代码就可以做到这一点。
|
|
在上面的代码中,我们指定不同的图像大小及其相应的设备像素比给给定的屏幕宽度。使用 picture 标签,浏览器可以基于设备决定最佳的内容。以上的代码可以完美运行,我们可以进一步扩展,以适应更多的场景。
你可能听过WebP图片格式。相比于PNG图片,其文件大小能够节省26%;相比于JPEG图片,能够节省大约25%-34%。目前,Chrome、Opera以及Android能够支持WebP格式,但Safari和IE尚未支持。既然我们能够用picture标签来处理响应式图片,我们也能够使用WebP格式的图片并且允许浏览器在不支持WebP时进行回退。
让我们在上面代码的基础上,添加WebP图片的支持。同时,我们要确保能够根据不同的DPR使用最佳视觉效果的图片。
|
|
在上面的代码中,我们已经创建了能够同时使用JPEG和WebP图片的picture标签。浏览器将根据设备决定最佳的选项。由于WebP并不支持IE和Safari,使用WebP图片意味着你需要在服务器上同时保存WebP和JPEG格式的图片副本。上面的代码足够满足我们当前的需求,但试想一下如果每张采用这种方式来编写,代码将会变得非常臃肿。当你的站点开始增长时,为每张图片编写上面的代码将会变得非常乏味。这时候,便可以采用Service Workers来解决这个问题。
我们采用开发者工具观察HTTP请求头部,可以看出可以根据Accept头部来判断我们的浏览器是否支持WebP图片。为了利用这一点,并开始提供WebP图片,我们需要注册一个Service Worker。Service Worker的一大特性就是,它们能够拦截网络请求,这样子,我们就能够完全控制响应内容。使用这个特性,我们能够监听HTTP头部,并决定如何做。如果你想了解更多关于Service Workers的内容,可以看看这个Github库获取更多的信息。
我们在html页面添加如下代码用于注册Service Worker。以下的代码引用service-worker.js文件。
|
|
在上面的代码中,我们做了一个简单的检查,判断浏览器是否支持Service Worker,如果支持,注册并安装Service Worker。这段代码代码最好的地方就是做了兼容处理,如果浏览器不支持Service Workers,它们会自动回退并且用户不会注意到其中差别。
接下来,我们需要创建Service Worker文件‘service-worker.js‘,用于拦截正在传递到服务器的请求。
|
|
上面的示例代码做了一系列的事情,让我们来一步步分解。
在前面几行,我添加一个事件监听器来监听任何一个fetch事件。当每个请求发生时,先判断当前的请求是否是获取JPEG或者PNG格式的图片。如果当前的请求是获取图片,我就能根据HTTP请求头部来决定最佳的响应。在这种情况下,我通过检查Accept头部并且查找是否存在“image/webp“ Mime类型。一旦查询完头部的值,我就能确定浏览器是否支持WebP图片,如果浏览器支持WebP图片,就返回相应的WebP图片。
现在,我们的HTML看起来比较整洁,能够支持WebP图片而并不臃肿。
|
|
Service Workers给了我们无限的可能。在这个例子中,我们可以扩展到包括其他图片格式,甚至是缓存。你还能轻松地添加支持IE的JPEGXR。这样子,我们能够更快地给我们的用户展示我们的页面。
如果你想看看示例代码运行的效果,请移步到deanhume.github.io/Service-Workers-WebP。打开支持这些特性的浏览器,如Chrome,打开开发者工具,就可以看到页面的运行。
舞台大小修改为:600x600 (这里可以根据设计稿调整所需要的舞台大小)
是指每秒钟放映或显示的帧或图像的数量,这个数值设置越大,动画越快,但同时也是性能消耗大户。这里我们设置为36
文件 > 导入 > 导入到库
快捷键Ctrl + L或者窗口菜单下 > 库
从资源库中把资源拖到舞台进去,通过移动拖拽的形式进行布局
我们可以将单独的动画,放到一个独立的影片剪辑里,这样可以更好的控制动画。几个独立的剪片剪辑,可以组成一个完整的动画。
当我们把图片从资源库拖到舞台时,它这个时候,只是普通的位图,并不能做补帧动画,所以我们必须把它转换成元件。
下面制作以飘动的钱,做个例子说明
选择位图,右键 > 转换为元件,这个时候,弹出一个对话窗口,我们首先选择“影片剪辑”,保存。双击进入刚才创始的影片剪辑,这个时候,由于刚才我们只是把位图转成了影片剪辑,但实际上,它里面,仍然是一个位图,所以并不能做动画操作。所以我们需要在影片剪辑里,把图片转换了“图形”。
上面已经把图片转成图形元件,所以我们现在需要时间轴某个地方中插入关键帧。这里我们在30,60帧处插入关键帧。然后在30帧处,移动元件的位置,然后在每个关键帧的中间右键,选择“创建传统补间”。速度可以通过删除或者增加两个关键帧的补间动画时间长度来控制。
如果我们希望动画可以连续从头再播放,可以在动画的最后一帧插入一个空白关键帧,打开动作面板,然后写上
|
|
即可回到第一帧重新播放,如果希望停止动画,则
|
|
如果希望跳到某帧去播放
|
|
如果希望跳到某帧并停止
|
|
文件 > 发布设置
最终会生成一个html文件和一个js文件
|
|
|
|
Zepto.js
是使用频率比较高的库之一。由于它的体积小,加载速度快,有着和 jquery
类似的 API,而备受开发者喜爱。可随着时间的推移,我们遇到了不少 Zepto
的坑,而且文件体积的大小跟代码的执行效率并没有什么关系,最后我们发现 Zepto
并没有太大的卵用。
jsperf 上有个 zepto
和 jquery
DOM 操作的对比测试,有兴趣可以看一下:zepto vs jquery - selectors
开源项目好坏的一个评判标准之一:是否有一个强大的社区和一批积极的贡献者
我们简单的看一个对比:
很明显,Zepto
的活跃度远远没有 jquery
高。不过言归正传,还是回到 Zepto
的话题上。
一直以来,我们在移动端上面使用 zepto
并没有出现太大的问题。直至我们将 Ajax 跨域请求从 iframe 的方式切换成 CORS
之后,一个比较隐蔽的 Bug 出现了。
我通过 Fiddler
或 Charles
抓包发现,在 webview 中,点击按钮之后的 Ajax 请求并未发出,但是页面在手机QQ浏览器和 PC 上表现都是正常的。因为是在切换 CORS 之后,页面才出现异常的,在此之前并没有版本迭代。所以 CORS 代码首当其冲要进行深层次的 code-review,于是我直接在 CORS
的代码块上进行 try-catch
,结果捕获到异常:
INVALID_STATE_ERR: DOM Exception 11
先来看看测试代码:
|
|
这段代码在大多数浏览器中都可以正常执行,但是在 Android 的 webview 和一些旧版本的手机浏览器中会抛出错误。
以上代码和普通的 Ajax 请求不同的地方在于设置了 CORS
的 withCredentials
属性。(CORS
请求默认是不会带上 cookies
等身份信息的,如果需要在请求中带上 cookies
,则需要设置 XMLHttpRequest
的 withCredentials
属性值为 true)
下面通过两个例子来分析一下:
例一:
|
|
这段代码在部分浏览器中依旧会抛出异常:INVALID_STATE_ERR: DOM Exception 11
例二:
|
|
这段代码可以正常执行,并不会抛出异常
为什么 xhr.withCredentials
赋值在 xhr.open()
方法之前就会出错呢?
秉着科(xian)学(de)严(dan)谨(teng)的态度,翻看了 W3C 在 2011 年和 2012 年关于 XMLHttpRequest
的规范文档,发现使用 withCredentials
属性的规范发生了改变。
2011 年的规范:
2012 年的规范:
对比两份文档,我们重点看一下 step 1:
2011 年的规范中规定当 XMLHttpRequest
的 readyState
状态不是 OPENED
时,会报错;
2012 年的规范中规定当 XMLHttpRequest
的 readyState
状态不是 UNSENT
或 OPENDED
时,会报错;
下面简单介绍一下 XMLHttpRequest
的 readyState
值:
Value | State | Description |
---|---|---|
0 | UNSENT | open() has not been called yet. |
1 | OPENED | send() has not been called yet. |
2 | HEADERS_RECEIVED | send() has been called,and headers and status are available. |
3 | LOADING | Downloading;responseText holds partial data |
4 | DONE | The operation is complete |
由此可以看出,当一个 XMLHttpRequest
对象被创建时,默认的 readyState
状态为 UNSENT
,只有执行了 open() 方法并且还没有执行 send() 方法时,readyState
的状态才为 OPENED
。
由于一些老版本的浏览器是按照 2012 年之前的规范来实现的,所以这一部分浏览器中,open() 方法要在设置 withCredentials
属性之前调用。因此为了兼容,正确的做法应该是在 open() 方法之后再设置 withCredentials
属性。
下面来看看 zepto.js v1.1.3 的源码:
zepto
是在 open() 方法之前设置 XMLHttpRequest
的属性值的,所以这会导致在使用 CORS
并且设置 withCredentials
的时候,代码在部分浏览器中报错。Android webview 中重现的几率很大。
总结:在使用 CORS
时,如果要给 withCredentials
赋值,请务必要在 open() 方法之后,否则无法向后兼容。
对于 zepto.js 的问题,已经有用户向作者提交了 PR,作者也表示会在下个版本中修复(可是直到今天,都更新到 v1.1.6 版本了,还是没有修复这个问题,更改一下代码顺序就那么难吗?!难怪阿里也嫌 zepto 更新速度太慢,问题多,所以自己 fork 代码进行了定制化)。
所以目前如果要用 zepto
来进行 CORS
的话,还是需要自己更改 zepto
的 ajax 模块代码,然后手动构建。
XMLHttpRequest Level 2 2011
XMLHttpRequest Level 1 2014
XMLHttpRequest Level 2 2014
Zepto issues
在一次需求中,需要做出三张卡牌走马灯式滚动的效果,由于在前面的一张卡牌需要挡住后面的卡牌,自然而然地就用 z-index 使前面的卡牌显示在最上面,配以 transform 动画让“走马灯”滚起来,在开发过程中,在 PC 侧 Chrome 中表现良好,在本人手机浏览器中也表现良好,最后测试时却发现,在微信客户端或 QQ 客户端中打开页面出现问题,“走马灯”滚动时,卡牌先通过 transform 就位后,才把 z-index 设置较大的卡牌置于上面,感觉上非常的不流畅。
究其原因,发现这是某些浏览器的渲染规则,涉及到 stacking context
的概念,transform 的元素会创建新的 DOM,层级会在普通元素的上面,除了 transform ,还有哪些情况会创建新 stacking context
呢?
MDN 上有相关介绍:
- the root element (HTML),
- positioned (absolutely or relatively) with a z-index value other than “auto”,
- a flex item with a z-index value other than “auto”,
- elements with an opacity value less than 1,
- elements with a transform value other than “none”,
- elements with a mix-blend-mode value other than “normal”,
- elements with isolation set to “isolate”, on mobile WebKit and Chrome 22+, position: fixed always creates a new stacking context, even when z-index is “auto”,
- specifing any attribute above in will-change even you don’t write themselves directly
下图是对 transform 和 opacity 的测试结果:
很明显,红色 div 都在绿色 div 上面了,说明真的有创建了个更高层级的 stacking context
。再做进一步测试,我给两组的 div 都加了 position:relative;z-index:1;
,结果绿色的都在上面了,手机微信上也一样,这能不能说明 z-index 对层级的影响大于 transform 和 opacity 呢。
至于 transform 变换的时候会让 z-index “临时失效”,其实并非 z-index 失效了,只是 z-index 被用在不同的 stacking context
上,而非在默认的 context 上同等地比较层级了。所以 DOM 在 transform 的工程中,DOM 处于一个新的 stacking context
里,z-index 也是相对于这个 stacking context
的,所以表现出来的实际是 stacking context
的层次,动画一结束,DOM 又回到默认的 context 里,这时的 z-index 才是在同一个 context 上的比较。
那该用什么方法来控制卡牌的层级,又能让动画流畅地表现呢,当然是 translate3d 中的 z-axis,很多时候我们并不知道它是用来做什么的,平常用得最多的只是它的 x-axis 和 y-axis,不妨先看个例子:
.box1 {width:100px;height:100px;background:red;transform:perspective(100px) translate3d(0, 0, 100px);}
.box2 {width:100px;height:100px;background:blue;transform:perspective(100px) translate3d(0, 0, 200px);}
实际效果是,看不到它们,然后我们再设置 perspective 为 201px,这时可以很明显地看到,box2 占据了整个屏幕,而 box1 宽高约为 200px,唯有设置 translate3d(0,0,0) 时,宽高才为 100px。
现在可以来理解下 perspective 和 translate3d 的关系,perspective 可以比作镜头和 DOM 的距离,实际上设置多少都没影响,因为它通过跟 z-axis 上的数值比例来影响样式,它更像是一个刻度,而 translate3d 的 z-axis 则表示了 DOM 和屏幕的距离。假定镜头跟屏幕的距离固定了,z-axis 越大,DOM 逐渐远离屏幕,靠近镜头,这时 DOM 看起来也就越大,当 z-axis 大于或等于 perspective 时,DOM元素已经在我们镜头的后面了,所以也就看不到它了。
现在也就好理解为什么 perspective 和 translate3d 能够影响 DOM 的层级了,它们在屏幕和镜头之间的距离不同,所以就有了层次,移动端设备很好地表现了这个结论,但在 PC 的 Chrome 上测试则不然,我们仍需要 z-index 才会表现出我们需要的 层次关系。
参考
]]>选择你所喜欢的,爱你所选择的–列夫·托尔斯泰
用了几年Vim,我发现我越来越喜欢它了,和好多人一样喜欢它,也许喜欢的理由各不相同,但大抵有如下几个:
我知道上面这些说得太抽象了,也许我们应该来点具体的,你可以边看边对比一下你使用的编辑器的操作:
编辑 | 用 Vim |
---|---|
如何向下移动7行 | 7j |
如何删除一个词(word) | dw |
如何删除一行 | dd |
如何删除{}里的内容 | di{ |
光标移动到第80行 | 80G |
如何在当前文件里搜索光标所在位置的词 | *(#) |
如何在50行到100行之间查找并替换 | :50,100/old/new/g |
如果你想在同一文件中对比两个不同的块时怎么做 | :sp (to ‘split’ the view) |
如果你想要打开光标所在文件名的文件 | gf (which means ‘g’o to this ‘f’ile) |
如果你想要得到每行前10个字母而它后面的数据不是你想要的 | ctrl-v (win 下是ctrl-q) |
如果你想要转换别人给到你的混合大小的文件变成小写 | 1GVG u |
录制一系列命令然后执行 | 用宏 |
在width:16px的数字16上加20 | 20 ctrl+a |
输入23+45+119的结果 | ctrl+r=23+45+119 |
以上的这些操作都是直接在键盘上操作的,没有用鼠标。
喜欢一个人的时候总能说出好多的理由,而不喜欢却只需要一个理由。此时我赞美我的女神你也在心里列举了你心中女神的诸多可爱美丽特点。所以可以想象各种关于编辑器文章下的各种互喷吐槽,即使是同一个编辑器的文章下也不免相互吐槽,毕竟不同人理念也是不一样的。
vim是个高效的文本编辑器,在文本编辑这一领域可谓是独步天下,高手使用vim处理SRT字幕内容,痴迷者除了使用vim写代码、发邮件、看股票、写博客、写markdown(这篇文章就是vim写的markdown)还使用vim浏览网页,普通人可以把它作为普通人的编辑利器。在文本编辑领域,后来者只有模仿而无法超越vim。小李飞刀独步天下却也砍不了骨头,在代码编程领域,公认的最适合的场景是做Unix/Linux服务器编程,不用下载服务器端的文件而直接修改;其次是编写html/css/javascript/shell/python/c等;最后在mac、linux下要改文件用系统带的vim随手改改。而编写Java
EE、Objective-C等还是用Eclipse、XCode等IDE的比较好。
vim只是一个工具而已,它并不能让你码代码的能力提升,只能帮你更快速地输出你的思维。每一个工具都有它特定的场景,聪明的人总会在适当的场景做出合适的选择。好多人都说学习起来好难的呀,投入的成本换来的收益是不是成正比的呢?我觉得还是值得的,上面也说过,它是万变中的不变,同时它真的是个高效的工具,另外也许最后出来的高效的编辑外还带给你一些东西,比如好奇发现带来的惊喜,又如你发现你用了几年还有好多不会的谦虚,还有用宏解决了一个棘手问题的喜悦。最后它只是一个工具,是雕刻林诗音松木人像的小刀,或是冠绝武林的小李飞刀,关键在人。
佛渡有缘人,细想这个‘有缘人’还挺有意思的,其实就是信的人,不信的说再多也是没有用的。如果想学,不必卸载你的是编辑器,做出破釜沉舟之势,万一用了两天觉得不方便或是有紧急的要修改的内容,要换回来用熟悉的编辑器。另外也不要觉得一时学不会就感觉挫折,毕竟很多东西都不可一蹴而就的,需要一点积累,所以慢慢来的。那些《7天XX》《21精通XX》真的害了好多人,慢慢来,实在不行认为自己笨了,不还有勤能补拙吗?好多的编辑器都有vi插件或模式,你可以找一找然后装上试试的,等熟悉了觉得不满足了再换成vim,要是觉得一直用vi插件也不错的就一直用着,能解决问题的顺手的才是最高效的。
我在小学的时候搞了个小霸王说要学习五笔,然后字根没有背完就忘记了,大学时候再一个想起这个事决定再学五笔,然后没有几天又放弃了,毕业两年后想起学五笔这个事,心里有个结,于是又学起来了,然后一直在用,虽然是五笔混合模式,偶尔也用纯五笔模式。vim就是在我可以用五笔的时候开始学的,当时拿同事的vimrc来直接就用了,现在也在用的,只是改了少许的地方,一直在够用就好的状态,然后在用的时候觉得有什么不自然的地方就找一下答案的,慢慢地越来越顺手了。细想我这个慢方法还是可取的,那么多我的命令我只会我常用的十几个,但那也是够用的了,并且是越来越喜欢,看着别人用的也还常发现还有很多不会的东西。
再回首,一切是那样自然,一如vim的理念keep it simple,我也只装了平时常用的几个插件,还有一些比较强大的插件也没有用得着,所以没有装上。最近为了给同事们讲一下vim,才意识到虽然平时说vim有几个模式,但是用的时候自己也没有区分得很清楚,觉得就是一个整体,应该那样,有时候我们只是为了区分和认识一个东西才把它解构了,就像一首好的歌曲,我们只用觉得好听,那些乐评人说的为什么好感觉都很有道理。vim命令有多难记也还是有逻辑的;vim里可以快速精确的查找定位;vim真的好像一切文本皆对象,操作针对字符,词,行,块,文件这些对象;vim的设计哲学是如果你的工作只需要做一次,那没问题,怎么搞都行,如果你的工作是要重复地完成某些工作,则vim总可以帮你找到更少的按键方式来实现相同的目标。如VimGolf所说Real Vim ninjas count every keystroke - do you?
]]>如果你第一次听说PostCSS
这个东西,那么请看下面摘自官方Github
的介绍:
PostCSS is a tool for transforming CSS with JS Plugins. These plugins can support variables and mixins, transpile future CSS syntax, inline images, and more
翻译成中文的意思如下:
PostCSS 是一套利用JS插件实现的用来改变CSS的工具.这些插件能够支持变量和混合语法,转换未来CSS语法,内联图片,还有更多
我们用过Less
、SASS
等工具来对CSS做预处理
操作,按照它们约定的语法来书写并且最终转换成可用的样式,这付出的代价是必须先熟悉这个工具的书写语法。
随着近几年 Grunt、Gulp、Webpack 等自动化工具的兴起,组合式应用
变得非常的热门,那PostCSS
存在的意义是什么呢?答案是:CSS生态系统
PostCSS
拥有非常多的插件,诸如自动为CSS添加浏览器前缀的插件autoprefixer
、当前移动端最常用的px
转rem
插件px2rem
,还有支持尚未成为CSS标准但特定可用的插件cssnext
,还有很多很多。就连著名的Bootstrap
在下一个版本Bootstrap 5
也将使用PostCSS
作为样式的基础。
一句话来概括PostCSS:CSS编译器能够做到的事情,它也可以做到,而且能够做得更好
上面大致介绍了PostCSS
,也许我们并没有在头脑里形成对它的认知,那下面我们就通过一个简单地实例来看看如何使用PostCSS
。
PostCSS
得益于插件,支持Grunt,Gulp,webpack,Broccoli,Brunch还有ENB,这里我们将以Gulp
作为实例来讲。
创建并进入我们的实例目录
|
|
然后快速生成package.json
文件
|
|
将上面创建的package.json
文件的main
参数改为gulpfile.js
,然后安装我们所需的依赖
|
|
创建gulpfile.js
|
|
将下面代码贴进gulpfile.js
|
|
在项目根目录下创建src目录,再在src目录下面创建css目录,然后创建style.css文件
|
|
编辑样式如下:
|
|
一切准备就绪之后可以在项目根目录下执行刚才我们定义的任务
|
|
如果不出什么意外的话就会在根目录下面生成一个dist
文件夹,里面有一个样式文件,内容如下:
|
|
我们可以看到我们写的样式自动添加了浏览器前缀,并且一些未来CSS语法也被转换了。
通过上面的实例我们应该知道PostCSS
的使用方法,此时让我们先回想一下CSS预处理器
的使用历程:
而PostCSS
的使用历程:
通过对比我们类比一个结论:CSS预处理器好比给你一个工具箱,工具箱里面有什么东西该怎么拿已经跟你约定好,你必须按照这个规则来拿;而PostCSS好比给你一个盒子,你可以从旁边选择自己需要的工具放进盒子打包拿走,如果还不够用你可以自己造一个工具
PostCSS
自身只包括了CSS分析器
,CSS节点树API
,source map生成器
,CSS节点拼接器
,而基于PostCSS
的插件都是使用了CSS节点树API
来实现的。
我们都知道CSS的组成如下:
|
|
也就是一条一条的样式规则组成,每一条样式规则包含一个或多个属性跟值。所以PostCSS
的执行过程大致如下:
Parser
利用CSS分析器
读取CSS字符内容,得到一个完整的节点树
Plugin
对上面拿到的节点树
利用CSS节点树API
进行一系列的转换操作
Plugin
利用CSS节点拼接器
将上面转换之后的节点树重新组成CSS字符
Stringifier
在上面转换期间可利用source map生成器
表明转换前后字符的对应关系
在PostCSS官方推特上看到,由JavaScript编写的PostCSS比C++编写的libsass还要快3倍,下面来自官方推特的截图:
如果你对上面的性能截图有疑问,可以亲自来这里测试看看。
PostCSS
在自己的Github上放了一些常用的插件,更多的插件可以在postcss.parts进行搜索。
但有时候已有的插件不满足我们的需求,这个时候需要编写自己的PostCSS插件,下面我们将一步步创建一个简单的插件,这个插件功能非常简单,如下:
|
|
当输入上面的样式时,会生成下面的样式:
|
|
我们将以Gulp
作为基础来实现我们的插件,首先创建项目文件夹
|
|
然后快速创建package.json
文件:
|
|
紧接着先安装必备的包
|
|
再创建gulpfile.js
并且输入下面内容:
|
|
我们在执行npm install
安装的包都放置在node_modules
文件夹下面,这里我们创建PostCSS的插件文件夹,注意:PostCSS的插件命名格式为:postcss-插件名字
|
|
现在我们可以在postcss-fontstack
文件夹创建入口文件index.js
,PostCSS
创建插件的方式如下:
|
|
那我们可以在index.js
里面贴入下面代码:
|
|
在gulpfile.js
引入上面的插件,代码如下:
|
|
在项目根目录下运行实例,最终实现我们的效果
|
|
基于PostCSS
能够做到很多CSS预处理器
做不到的事情,未来发展前景还是挺不错的,而且最新的Atom
编辑器也可以下载插件来支持PostCSS
这种语法。
但这就意味着CSS预处理器
过时了么?不会。PostCSS
的出现并不是为了替换掉之前的技术,只是提供多一种思路让我们去考虑,就比如Sass编译后再加autoprefixer
自动补齐浏览器前缀这种做法当前还是比较流行的。
再回到文章最开始说的,PostCSS
其实是在打造一个改变CSS开发方式的生态系统。也许暂时我们还是保持传统的开发方式,但未来对于PostCSS
我还是保持关注,它是值得期待的。
o2
。主题单独开源于 hexo-theme-o2,有兴趣的朋友可以自由下载使用。
2016年12月更新:当前主题为全新设计,不再使用
hexo-theme-o2
,喜欢的童鞋可以直接fork o2team/o2team.github.io的源码。
使用github头像
记得将你们在github的头像更新到最帅,将你github的用户名在文章内头部填好即可!
hexo-wordcount 显示博文字数
安装hexo
|
|
将o2team.github.io的源码拉到本地
|
|
初始化子模块(submodules)
|
|
安装nodejs包
|
|
运行hexo s --watch
运行上述命令后,浏览器打开 http://localhost:4000 即可本地访问我们的网站
有两种方法创建文章,可任选其一:
注意:文件名不要出现中文!!!
使用hexo new
命令
|
|
拷贝现有的文章进行修改
hexo使用markdown来编辑文章,在source目录下,拷贝任意md文件进行创建新的文章。具体可参考下hexo的官方说明
博文图片统一位置:source/img/post/
在博客内容中可以使用 pimg
自定义标签来引用图片。注意别覆盖了别人的图片!!!
其语法格式为:
{% pimg imageName [alt text] [JSONImageAttibutes] %}
例如:
{% pimg post-aotu.jpg "空格请用%20来区分" '{"title":"hello","class":"test_img"}' %}
需要注意的是:pimg
利用空格来划分字段属性,如果一个属性的值需要空格,请将空格用%20
代替!!!
为了保证博客整体美观,每个文章需要一张配图(大小:840x340)
指明文章的副标题、作者信息、封面图片地址
|
|
利用<!-- more -->
设置文章的摘要
示例:
|
|
实际使用请参考本文。
为了便于统一维护,博客的发布由LV
负责。同学们写好博文并在本地预览OK后直接提交Github即可。