Sneezry


Electron开发实践II

上一篇博客中,我向大家介绍了Electron以及Electron开发环境的部署,在这篇博客中,我将向大家介绍简单Electron应用的开发过程。

Electron程序的入口

程序入口往往是初学者在学习前的第一个疑问:这个程序是怎么跑起来的?由于Electron是Node的runtime,所以它的入口是在package.json中的main进行定义的。接触过Node的读者对此会非常熟悉,对于接触过Chrome程序开发的读者可以把package.json简单理解成manifest.json。无论是package.json还是manifest.json,它们都是一个程序信息的描述文件,里面声明了程序的名称、简介、版本等信息。

比如下面是一个简单的例子:

{
    "name": "My Electron App",
    "version": "1.0.0",
    "main": "app.js"
}

上例中定义了程序的名称为“My Electron App”,版本为“1.0.0”,入口脚本是“app.js”,这样当用户运行这个程序时,app.js就会被执行。

一个简单的Electron应用

接下来编写一个非常简单的程序,在package.json同目录下新建一个app.js文件,并写入以下内容:

var fs = require('fs');

fs.writeFileSync('message.txt',
    'This is my first Electron app!',
    'utf8');

对于没有接触过Node的读者可能对上面代码中出现的require感到陌生,这里的require和C语言的include或Java中的import类似,通过requireapp.jsfs模块引入进来,并在后面调用了它的writeFileSync方法。

fs模块是Node中提供操作文件功能的模块,writeFileSyncfs模块提供同步写入文件的方法,上面的代码就是在当前目录下,将“This is my first Electron app!”以utf-8编码方式写入message.txt文件中。运行结果如下:

my_first_electron_app

怎么样,是不是非常简单!

我们需要一个界面

如果仅仅创建一个在后台默默运行的应用,那么Electron的一半特性就被忽略了。既然Electron是一个桌面应用开发框架,那么就一定离不开界面。

Electron内置了WebKit渲染器,所以可以直接使用HTML编写程序的界面。在上一篇博客中提到的例子是包含了一个界面,这个界面也是由app.js创建的。

对上例的app.js进行修改:

var app = require('app'),
    BrowserWindow = require('browser-window');

app.on('ready', function() {
    mainWindow = new BrowserWindow({
        width: 400,
        height: 300
    });

    mainWindow.loadURL('file://' + __dirname + '/app.html');

    mainWindow.on('closed', function() {
        mainWindow = null;
    });
});

新代码中引入了appbrowser-window两个模块,这两个模块都是Electron提供的。app.on方法在整个程序生命周期中进行事件监听,ready事件在程序初始化完成之后触发。在上面的代码中,程序准备就绪之后,创建了一个BrowserWindow实例mainWindow,这个实例就是打开的窗口。在创建BrowserWindow实例时定义了窗口的尺寸,随后定义了窗口中需要加载的HTML文件位置。__dirname是Node中的一个常量,指向当前运行脚本所在的目录,所以app.htmlapp.js处于同一目录下。最后实例mainWindow通过监听closed事件进行自我销毁,即关闭窗口。

创建app.html写入以下内容:

<!doctype html>
<html>
<head>
<title>My First Electron App</title>
<style type="text/css">
body {
    margin: 0;
}

textarea {
    width: 100%;
    border: none;
    background: #eee;
    margin: 10px 0;
    padding: 0;
    outline: none;
}
</style>
</head>
<body>
<textarea rows="10"></textarea>
<button>Write</button>
</body>
</html>

这是一个非常简单的HTML文件,只包含一个文本框和一个按钮,style标签中是对页面上元素样式的简单描述。运行结果如下:

my_first_electron_app_interface

还是挺丑的对吧,毕竟只是个Demo嘛。

界面和逻辑的联系

上面的程序仅仅提供了一个无用的界面,无论怎么点击都不会有任何交互,所以需要把界面和程序功能联系在一起。

创建main.js并在app.js中通过script标签引入,将以下代码写入main.js

var fs = require('fs'),
    textarea = document.getElementsByTagName('textarea')[0],
    button = document.getElementsByTagName('button')[0];

function writeFile() {
    var text = textarea.value;

    fs.writeFileSync('message.txt',
        text, 'utf8');
}

button.onclick = writeFile;

需要注意的一点是,在app.html中,main.js要在页面的最后引用,否则main.js中的textareabutton无法正确获取的元素。

是不是很神奇,居然可以在网页中调用Node模块!这就是Electron的魅力所在。最后的运行结果如下:

my_first_electron_app_writefile

SOURCE
Electron开发实践I

Electron是什么

Electron是一款利用Web技术开发跨平台桌面应用的框架,它的前身是Atom Shell。从它前身的名字可以看出,Electron的诞生,离不开GitHub开源编辑器Atom的发布。

Electron和NW.js(前身是Node-Webkit)有很多相似的地方,两者都是利用NodeJS和Webkit渲染器解释JavaScript和渲染HTML,使Web技术应用于桌面应用,但两者又有所不同。Electron的工作方式更接近于Node.js运行环境(“Electron works more like the Node.js runtime”),而NW.js更像是将一系列网页打包起来运行在本地的网站。

有哪些应用是用Electron开发的

在Electron官方页面上罗列了数十款利用Electron开发的应用,其中比较知名的应用有AtomSlackVSCodeWordPress.com等。

开始部署Electron开发环境

安装Node.js

Electron开发环境依赖于Node.js,所以需要先安装Node.js。这里有一篇指导文章可以帮助你在不同平台下安装Node.js已经npm包管理器。

Node.js安装完毕之后运行

node -v

来查看安装是否成功,如果安装成功,你会看到node的版本号。网络上很多教程的截图中会显示版本号是0开头的,而你所安装的node很可能是4开头的,不必担心,由于历史问题,node从0这个大版本直接飞跃到了4这个大版本,你看到这个诡异的版本号并不是你做错了什么。

node-v

如果你的网络环境比较复杂,无法正常使用npm包管理器(大陆的网络环境通常都比较复杂),可以使用淘宝提供的npm镜像,之后将所有npm指令都替换为cnpm指令即可。

安装Electron

运行

npm install electron-prebuilt -g

如果提示权限错误,非Windows用户请运行

sudo npm install electron-prebuilt -g

Windows用户可以通过管理员权限打开cmd后重新运行上述命令。其中-g代表全局安装,这也是可能导致权限问题的原因。

安装完毕后运行

electron -v

检查是否安装成功。

electron-v

运行Electron Quick Start

Electron官方给出了一个简单的示例程序,可以通过其发布在GitHub上的Repo获取,对于没有安装Git的用户,可以直接点击Download ZIP获取文件,解压缩后,在终端中(Windows的命令行)进入到文件目录,然后运行

electron .

如果一切顺利,就可以看到应用窗口了(注意上面的命令后有一个“.”)。

electron-quick-start

SOURCE
向Hooloo道别

去年我发布Hooloo以来,Sneezry.com一直使用它作为博客系统,Sneezry.cn作为Sneezry.com国内镜像,使用的也是Hooloo,但Hooloo功能的简化让我觉得用起来还是有些不舒服,尤其是无法被Google正常索引,一直让我很困扰。

不久之前我发布了MoteDown,这是一个管理Jekyll站点的软件,虽然目前我只发布了Egg内测版,但在实际使用中我已经深深地爱上了它,所以我利用周末将Sneezry.com重新迁移到了Jekyll上,同时删除了大部分之前的博文(太水了,不忍直视),不过这些博文依然可以通过archives.sneezry.com阅读。

目前Sneezry.cn对Sneezry.com的镜像还没有部署,所以依然是Hooloo,在几天之后会进行同步,届时Sneezry.com和Sneezry.cn的内容会再次保持一致。

还有一个值得庆幸的是,我的博客的feed终于又可以正常使用了 =)

不久之后MoteDown就会与大家见面,还希望到时候大家多多提出宝贵意见 =D

SOURCE
拥抱Jekyll的新方式

在去年2月,我发布了一篇博客,基于Github的前端轻量级博客系统,这个系统被我命名为Hooloo,同时也发布到了V2EX社区,也得到了不错的反馈。

编写Hooloo的初衷是希望能帮助不想在电脑上配置开发环境的用户同样能使用GitHub Pages撰写博客,出于这一点考虑,Hooloo在设计上使用了最少的配置,实现了博客网站的基础功能。这是必须妥协的地方,配置简单、使用简单,那么功能上就必定大打折扣。没错,Hooloo是一个轻得不能再轻的博客系统,轻到只提供一个博客列表和博客内容,其他的都是在Theme中hard code的。

虽然Hooloo是这么简陋,但在我发布之时还是引发的很多人的热烈讨论,这是我意想不到的,甚至有些人也用Hooloo的思路开发了自己的博客系统。我意识到,使用GitHub Pages写博客不应该是极客们的特权,即使作为开发者,我真的应该为写博客在个人电脑上安装一整套开发环境吗?

这些天我重新思考了这个问题,同时也觉得是时候对Hooloo说Goodbye了。多数人相信使用Jekyll写博客的优势是Git,也有些人认为是Geek,但我觉得我们不应如此吝啬,Jekyll是个超赞的东西,不应该让它成为小众的玩物。

但似乎Jekyll打出现以来就和开发者们脱离不开关系,它是设计处处透着Geek范,我曾经将Jekyll安利给我的朋友们,但当他们看到官方文档里大段大段的代码和命令就望而却步了,这不是他们的错,毕竟他们不以coding为生。

今天我向大家宣布我在过去一周里开发的新项目——MoteDown,我相信它不仅能让更多抵触代码和命令行的人拥抱Jekyll,同时我也相信,对于很多可以熟练使用Jekyll CLI,可以轻松配置Jekyll开发环境的人也能更方便地使用Jekyll。

MoteDown是一个Jekyll博客管理软件,它可以方便快捷地帮你管理多个Jekyll Site。MoteDown在设计上充分参考了Windows Live Writer,尽量让没有接触过Jekyll的人将精力放在内容上,而不是网站本身上。

MoteDown自带了一个Markdown编辑器,也可以方便地预览Markdown编译后的样式。得益于GitHub强大的API,MoteDown脱离Git客户端,使用RESTful API即可对文章进行版本控制,这对没有接触过Git的人是一个不错的消息。

由于我只在业余时间开发了一周,MoteDown还只能算是一个雏形,很多功能都还没来得及实现,但现在的版本带给我的博客体验已经足够出色了,所以在今天我迫不及待地写了这篇文章与大家分享。

MoteDown在未来一段时间里会发布4个版本:Egg,Caterpillar,Pupa和Butterfly,其中Egg会在非常有限的人群中提供测试,Caterpiller会在一部分人群中提供测试,Pupa会公开测试,Butterfly会是一个功能相对完善、性能相对稳定的正式版本。

当然,能听到你的想法,是我最盼望的事情 :)

SOURCE
随着音波起飞——发布自己的播客

相信很多人都有自己的独立博客,拥有自己富有个性的域名,甚至很多人还为自己的博客配置了 SSL 增加安全性防止页面被 ISP 劫持,那么大家想过发布自己的播客吗?其实发布播客并没有想象中的难,我与 RebornixSNK 老师就在这几天发布了我们自己的博客——三人行,下面我就来和大家一起分享一下我们发布播客的整个过程,带领大家一步一步发布自己的播客!

我们来发布自己的播客吧!

提到发布自己的播客,不得不谈一谈我们当初发布播客的初衷。9 月末的一个下午,Rebornix 与我和 SNK 说,我们一起做播客吧!Wow,我们可没有什么播音的经验,这也是大部分人做播客的一个顾虑,不敢将自己的声音发给别人听。不过自媒体播客的优点也在这里,播主无需专业的播音知识,只要有干货就会有听众,即使没有多少听众,单纯做成一件事也是非常有成就感的,所以这点顾虑并没有过多影响我们的计划。Rebornix 的想法也很简单,我们只想做我们自己的播客而已,仅此而已。所以如果你也想做一个自己的播客,那么请记住,播客是为自己做的,不要有太多的负担 :-D

选择一个有趣的域名

我承认,一个网站的价值在内容上,但一个好域名可以为你的网站加不少分,更重要的是,会让你更容易投入进去。我们在做播客前,着实费了好大的力气起名字找域名。如果你的预算充足,fm 域名是个非常好的选择,如果你的资金不够充裕,也可以考虑 io 或 sh 等程序员们喜欢的域名,如果你的目标听众不是程序员,就选择 com 等常规的后缀。

多人 Remote 录制播客

通常来说,播客需要多人一同录制,而多数情况下是需要 Remote 录制的。三人行的第一期中的三个声音就是分别来自北京、上海和深圳。我们使用的是 Skype 多人语音通话,通话质量还是很不错的。录音软件我们使用的是 ocenaudio 这款来自巴西免费的开源软件,值得强调的是,ocenaudio 是一款支持 Windows、Linux 和 OS X 的全平台软件。

后期剪辑

在录音时,没有必要准备太过详细的草稿,甚至是完整的稿件,因为这样会给人很不自然的感觉,我们只要准备一个粗糙的提纲即可,那么这样就不得不面对一个问题——口误和冷场。别担心,我们不是在直播,口误和冷场都可以通过后期编辑的,说错了也没有必要整句话重新说,只需稍作停顿,把说错的部分重新说一遍就可以了,当然一同录制的朋友们也有心照不宣地配合不要笑场,否则后期切起来就麻烦了。三人行后期处理软件使用的是 Audacity,同样是一款免费的开源跨平台软件!剪辑的时候主要考虑三点:去噪、切停顿(冷场)、切口误。需要注意的是,最好不要把所有停顿都切掉,因为这样会给人造成急促的感觉,这会让听者感觉很累,降低体验,当然如果你想做一个每期 5 分钟的每周新闻速报就忽略这条吧 ;-)

使用 Jekyll 搭建播客网站

播客和博客只有一字之差,两者网站在结构上也十分相似,所以很多播客网站直接就是博客网站改的,三人行也是如此。我们直接采用 Jekyll 来生成播客网站,相信 Jekyll 大家都非常熟悉,即使不熟悉也没关系,因为 Jekyll 的官网上有详细的使用说明。虽然大家多数会在 iTunes 或者其它第三方收听我们的播客,但一个好的网站依然是必不可少的,不能草草放一个 Just Works 的网站上去。一个好的网站的标准是,有一款赏心悦目的主题,设计合理的交互,方便简洁的互动。主题上,如果你对前端技术有所了解,从头自己设计制作一套独一无二的主题自然最能向听者们体现你的诚意,如果你对前端不是很了解也不必沮丧,Jekyll 有大把的精美主题,很多都是遵循 MIT 协议发布的,三人行的主题也是 MIT 协议发布的,如果你没有能力自己制作主题,我们非常欢迎大家使用我们的主题

选择一个合适的 CDN 服务

播客与博客不同,由于播客信息传播的媒介是声音,这通常会导致播客的内容数据要比博客大得多,同时考虑到不同用户的网络环境差异很大,一个 CDN 服务是必不可少的。同样不必担心,对于一个发展初期流量不大的播客来说,CDN 不会花费很多银子。三人行使用的是 UPYUN,因为 UPYUN 相对来说价格比较有优势(不是广告啦,你们看我都没加链接),同时对国外用户也比较友好,虽然我们多数是做针对国内用户的中文播客,但是发布到 iTunes Store 是需要美国那边人工审核的,我们第一次选择的 CDN 就是因为对国外线路支持不够完善导致上架 iTunes Store 被拒。

创建 Podcast Feed

Podcast 使用专门格式的 RSS Feed 作为订阅源,苹果官方有提供完整的技术说明文档,通过 Jekyll 生成 Podcast Feed 的例子大家可以参考三人行的源码。以下几点需要强调:

  • iTunes Store 要求播客的 Logo 最小尺寸是 1400 × 1400 像素(真是大呀),最大尺寸是 3000 × 3000 像素
  • description 部分的内容如果使用了 HTML 标签,需要用 <![CDATA[]]> 包含,HTML 代码中的 <> 不要转义
  • enclosure 要包含 length 属性,我知道很多已经被 iTunes Store 索引了的播客都没有这么做,但相信我,length 在标准中是必选属性,没有这个属性你的 RSS 就是无效的
  • itunes:category 标签可以嵌套也可以自闭,当它的 text 值是主分类时(比如 Technology),那么它就是嵌套的,如果是子分类(比如 Software How-To)就是自闭的,举例:
<itunes:category text="Technology"\>
    <itunes:category text="Software How-To" \>
</itunes:category\>

测试你的 Podcast Feed

测试 Podcast Feed 有效性的网站不止一个,在此推荐 FEED Validator,根据 FEED Validator 的建议逐条修改你的 Feed 直到出现“Congratulations! This is a valid RSS feed.”字样。

提交到 iTunes Store

如果选择域名、搭建网站、配置 CDN、测试 Feed 你都做完了,那么恭喜你,大家已经可以通过手动直接添加 Feed 的方法订阅了(安卓用户手动添加的方法,苹果用户手动添加的方法),但是这还不够,我们要发布到 iTunes Store,这样用户订阅起来才更方便!首先打开 iTunes 电脑客户端,登录 Apple ID 后,点击左上角的“播客”图标(如果没有,先点击三个点的“更多”图标,选择播客),然后点击“iTunes Store”,在页面右侧的菜单中点击“提交播客”,在新页面中填写你的播客的 Feed 地址后点击“继续”,如果一切正常,就可以看到播客的信息了(包括标题、作者、简介等)。我相信部分朋友会看到“We are currently experiencing techincal difficulties. Please try again later.”这个错误,这非常符合苹果的做法,就不告诉你到底是啥问题……别看上面说“We are…”,多半是你的 Feed 里有什么问题,老老实实返回上一步测试 Feed,连警告也不要忽略,修正后再返回提交看看是否顺利。还有些运气不好的朋友可能会收到“We had difficulty reading this feed. Host parameter is null.”,为什么说是运气不好呢,因为没有人知道这个错误到底是啥意思……不过如果遇到这个问题了,也确定 Feed 没有问题,那么就多提交几次试试吧 :-( 如果一切一切都非常顺利,24 小时之内就可以在 iTunes Store 中搜索到你的播客啦!

三人行,聊聊技术,聊聊生活

看吧,为了推广我们的播客,我费了好大劲绕了这么大圈子,如果你真的读到了这里,不如订阅我们的播客试着听一听吧,由于这是我们的第一期,肯定还有很多不足,但希望大家能给我们一个机会,给我们一点鼓励 :-)

谢谢。

不用谢。

SOURCE
提高页面加载体验——延时加载你的图片

这是一篇迟到的文章,足足迟到了一个月,所以我对于自己的拖延症已经放弃治疗了……那么除了前几天找工作和黏豆包的离开,还有什么事情占用了我写博客的宝贵时间呢?奥特兰山谷!那个夺得荣誉的地方!为了联盟!

好了好了,看得出你们一脸鄙夷,我承认我都去打魔兽了……希望今天在国庆节尾巴翻然悔悟的我能得到大家的原谅(据说只要不睡就还算假期)。

扯了两句皮,回到我们今天的正题,延时加载图片。为什么要延时加载图片?产品展示网页通常会包含很多图片,但一般情况下也不会有太大问题,因为这些图片都不算太大,如果用户网络较好,是不会出现糟糕的体验。那么有没有包含很多超大图片的网页呢?有,比如苹果的 iPhone 介绍页面。这些网页为了体现出高清漂亮的产品照片,包含了大量的大尺寸图片,这些图片不夸张地说下载下来都可以直接做壁纸用。那么这样的网页在加载时会出现什么问题呢?想来大家都有体会,曾经苹果的页面中图片加载得十分糟糕,用户经常能看到加载一半的图片,更糟糕的是,这些图片还没有被完全加载完毕就被另一张加载一半的图片代替了——比如那些华丽的轮播图。

不过大家有没有发现,苹果现在的网站不会再出现这个问题了,整张网页从打开到最后加载完备,整个过程犹如牛奶般丝滑……

这是什么 magic tech ?先卖个关子,为什么我会研究起这个问题?因为我目前实习的公司也遇到了这个问题……我们的首页也包含了很多图片,切换起来也有苹果曾经遇到的问题,但是人家苹果改进了呀,所以本着见贤思齐的原则,我把苹果的页面加载过程彻彻底底研究了一遍。

总的来说,网页中加载图片有两种方法,一是直接用 img 标签引用,二是设置背景图片。早期的浏览器(IE8 及以前)加载资源的顺序是顺序加载,即在 DOM 前面引用的资源会被优先加载,这也是目前很多书籍推荐将 script 紧贴 body 标签闭合之前的原因,这样能够 script 最后加载而不阻断 DOM 的渲染。

如果媒体也是按照 DOM 引用的顺序来加载也不会出现什么问题,排在 DOM 后面的图片通常也排在页面的后部,而用户刚刚打开页面时它们也通常不在屏幕可视范围内,大可慢慢加载,但是现在浏览器对资源加载做了优化,浏览器按照自己的判断加载资源,而图片通常会被同时并行加载——这是个很棒的 feature ,这提高了页面加载速度,但也会导致上面提到的问题——排在前面的图片不会最先加载完毕,用户会看到加载一半的残图。

问题分析完毕,我们来想想解决办法。问题出在浏览器对媒体文件“自作聪明”地并行加载,所以解决办法就是想方设法让浏览器慢点来——加载好了一部分图片后再加载另一部分,把整体并行改为整体穿行局部并行。

对于背景图片的引用,只需要在 CSS 中用 background: none!important; 覆盖掉就好,这样背景图片就不会被加载了,当时机合适,再移除相关的 className 就好。那么对于 img 标签直接引用的图片呢,也很简单,只需要将图片地址不写在 src 里就可以了,比如写在 data-delay-src 里,然后用 JS 去读这个属性值,在合适的时机里赋给 src 就可以了。

但是呢,用户也不希望进来的时候发现页面上一片空白对吧,所以呢,在页面第一屏区域的图片就不要处理了,让它们在用户打开页面的第一时间呈现出来,第一屏的图片也不会太多,不会过于影响体验。待第一屏图片加载完毕后就可以再顺次加载第二屏、第三屏的图片了。

下面是我优化前后的 Smartisan 官网首页加载效果的对比,需要注意的时,我在 Chrome 开启了模拟网络环境,导致加载速度被刻意放慢,这是为了更加显著地体现差别,实际上不会这么慢。

上面的视频是优化前的加载过程,下面的视频是优化后的加载过程,是不是看起来顺滑多了?有心的你一定发现了其他的不同——优化前页面 loading 时前面转圈的加载图没有显示出来,优化后的显示正常。这是因为浏览器会在 DOM 渲染完毕后才去加载图片资源,如果你使用了 AngularJS 的 ngView 配合路由异步请求模板,这个 loading 的图很可能是在你请求到模板后才能显示出来,这样这个 loading 图基本就永远不会被看到了。我解决的方法是用 base64 URL 代替外部地址,这样图片是随 DOM 下载好的,自然也会在第一时间被显示出来。

另外还有个有趣的地方,就是我在优化页面的时候,为了尽快让浏览器标签上的 loading 停下来(长时间转的话别说处女座的同学了,我自己都受不了),所有延时加载的图片都是在 window.onload 触发后加载的,这样也会导致在用户强刷新时,这些被延时加载的图片依然顽固地从缓存里蹦出来,下面看看优化前后资源加载的 timeline 。

左侧是优化前的,共请求了 3.9MB 资源,耗时 21.7s ,优化后请求了 2.0MB 资源,耗时 11.76s ,至于这算一个 bug 还是算一个 feature 呢,就请各位看官自行决定吧,但我们最后是按 feature 对待的(CDN 很贵的哎~)。

如果你觉得看视频不过瘾,那么请在新标签中敲入 t.tt ,体验线上效果!如果不出意外,你将会看到:很快就加在完毕的标签(标签上的 loading 图很快消失),渐变淡入的首屏图片,自然丝滑的图片切换,强刷新依然傲娇地读取缓存(from cache)。

我要是在这里结束这篇文章,那么这绝对是一篇不折不扣的炫耀文章!瓜皮菜叶鸡蛋壳估计是少不了的!我知道你们希望弄懂背后的理论,但我更相信你们渴望一套现成完备的解决方案,所以…… https://github.com/Sneezry/image-delay.js 上吧,霹雳贝贝们!咦?肿么全是英文?哎呀,允许人家偶尔装一下好不好~(不写中文教程你会咬我吗?不咬是吧,不咬就不写了哈~)

最后安利一部电影,Mad Max: Fury Road,里面 Nux 小哥好帅啊~(虽然是一个 half-live “活死人”,有的人就是这样,演个僵尸啥的也辣么耐看……)

SOURCE
聊聊找工作的经历

当鸟哥在微信里告诉我不用去美团了的时候,我知道今天是我的幸运日。

我从 2008 年开始吉林大学读电子专业,一读就读到了现在。直到 2013 年我刚刚攻读微电子学硕士时,依然一门心思地要在 IC 界混出一片天地,我的导师也非常看好我,在我刚刚进入实验室时就和我讨论直博的事情,那时的我决心将毕生的经历投入到电子行业。硕士生涯第一年很快就过去了,而在这一年里我唯一的成果就是写了一本讲解 Chrome 扩展开发的书,我知道我的心并不在电子上。 2014 年 8 月,我顶着巨大的压力和导师聊了聊我自己的人生规划——也许电子行业不适合我,我不想继续攻读博士了。这个决定的背后有太多的苦涩——导师和父母都被我深深地伤了心,但我知道,自己的路必须自己来选。

2015 年 6 月,在我改变研究方向的 10 个月之后,我完成了计算机视觉方面的基础研究,用 JS 写出了一个可以满足我研究需要的计算机视觉库,发表了自己的算法,完成了 paper ——我已经达到了毕业的要求,这时我觉得如果以后想在 IT 方面就业,应该找实习了——然而似乎我找得有点晚了,不过令人欣慰的是,锤子科技还在招聘实习生,而且池建强老师也在锤子科技,那么这家公司一定不会错。于是 6 月末我参加了锤子的网络面试,面试的职位是前端开发实习生。一切都非常顺利,锤子前端的同事没有问深邃复杂的算法题,也没有问数据结构题,每一道题都是紧贴前端技术的,我也如鱼得水,回答得没有半点含糊。

进入锤子实习如此顺利,但进入公司后前几天却并不那么顺利,原因是我自己太过自信太过自我, QA 提出的问题我每次第一个想到的就是他们不懂编程,这个问题不是编程的问题——换句话说,这个问题不应该算是技术问题。当初面试我的同事很快知道了我在工作中的问题,他找了一个时间专门和我潜心聊了聊我对工作中遇到问题的看法,我也丝毫没有隐晦,但听了他说的一席话,我发现了我自己这个严重的错误。他说,大家的目标都是为了做出更好的产品,我们在遇到问题的时候更应该考虑怎么样能解决这个问题,而不是想方设法去告诉别人为什么解决不了这个问题,当然如果真的遇到无法解决的问题,我们会重新评估需求的。同事的话说得轻描淡写,那次谈话虽然更像大哥哥与弟弟的谈话,但我的脸却红得不行。后来 QA 再提出任何问题我都跑过去现场确认,仔细询问复现的方法,工作上的心情也完全变了 180 度。

在锤子实习期间,我也向网易、腾讯、知乎、美团和百度投递了简历,几家公司的结果各不相同。网易为我安排了三次电话面试,听得出来面试官人都很 nice ,不过可惜的是没有再收到后续的消息,我猜可能是我在算法能力上的薄弱最终让网易对我保持了谨慎的态度。腾讯因为拿到了微信内推, TST 的简历评级是 A+,也就是说我无需参加笔试,只需参加面试,如果一切顺利就可以拿到 offer ,所以我也一度认为未来基本会在鹅厂工作,但最终我还是收到了鹅厂在线笔试的通知,在常规的校招流程了,我这个非 CS 专业的人肯定没有任何优势,所以不出意外,两轮笔试后再无消息,后来我又联系了之前内推我的同学,他表示 TST 已经结束了,并且好心提醒我关注下腾讯其他的部门,在此只能像那位内推我的同学说声抱歉了,我也真的是不争气……知乎是一家给我印象非常不错的公司,工作环境很优雅,办公区别致的越层让人拍案叫绝。由于去知乎面试之前我在公司还有些事情没有做完,做完之后没顾得上吃午饭就忙不迭地跑去面试了,当知乎 HR 知道我还没有吃饭后,热情地给我拿来了吃的,和我说免得一会面试时会头晕,这些事情都让我倍感感动。在知乎聊得也很开心,不过遗憾地是最后得到的答复是 reject ,后来我想可能知乎更想寻得一名工作经验丰富的前端,对于校招可能并不是他们的目标,不过想起来即使这样也那么热情地接待我,更令我感动!美团比较直接,上来就是连着三轮面试,美团也是我面试过的出题最难的公司(因为鹅厂没有给我面试机会,所以不考虑鹅厂的面试难度),第一轮面试是纸上用 JS 写快排,由于之前我写过,所以很轻松就写出来了,第二道题是问多叉树叶子之间的距离,当时我还小心翼翼地询问是二叉树吗,面试官微笑着说是多叉树,有很多叉的,然后还非常体贴地为我画了图。虽然没考虑过多叉树,但其实也不难,于是很快也给出了面试官认可的答案。接下来是等待结果,大概半小时后通知我进行二面。二面就直接问 JS 的问题了, this 的指代,原型链这些深层次的就都问出来了,但由于之前有读过 You don’t know JS 这本书,所以这些问题也都可以 handle ,不过在判断 this 指代时,第一次脱口而出的是正确答案,后面反复思考后给出的恰恰是错误答案,不过面试官没有马上做结论,还是体贴地提醒我继续考虑考虑,最后给出了正确答案,并给出了令面试官满意的解释。第三轮其实算不上面试了,因为整个过程都是那位工程师在向我介绍他们部门,希望我能过去,我说不还没确定 offer 吗,他笑笑,说会很快通知你的。美团面试结束后第三位面试官热情地把我送到楼外,那次面试也给了我很大的鼓励。

第二天,我接到了微软的面试邀请——其实我之前根本不敢投这家公司,因为我知道我差得太多,虽然 FE 方面我比多数人知道得多些,但没有实打实的 CS 功底想混过微软的面试比登天还难。微软也非常利索,一个下午三轮面试。第一个面试还是说写个快排吧,语言随便选——可能选 JS 的我是微软历史上的头一个吧……第一面顺利通过。第二面,面试官先和我说,堆你应该知道的吧……我说,不知道……冷场,开始面试官试图向我介绍堆的概念,但后来还是作罢,考了一道 leetcode 上 easy 级别的题,虽然 leetcode 也做过一些,但是写起来还是有些慌,毕竟我最开始对自己的定位就非常清楚,微软对我来说太高了。最后磕磕绊绊算是写出了可以 work 的 code ,二面勉强通过。三面是鸟哥的 team leader ,第一句话是 make an introduction to yourself please ……我知道微软会有 English Test ,但我没想到会这么快……然后磕磕巴巴, uh ……那个…… well ……心里喊着 Mayday Mayday Mayday ……后来面试官贴心地说, OK ,我们切回中文吧 Orz ……考的依然是一道不算难的题,因为是 leetcode 的原题,我的答案也很快就给出来了,但面试官马上提出来了,这个代码会不会有问题?我从头 review 了一遍,感觉没有问题啊,他说如果给定数组里没有目标值怎么办呢?呀, leetcode 里给的前提条件是一定有啊,怎么办怎么办,肯定是我边界条件不严谨!于是开始聚精会神考虑边界条件……这时面试官说你可不可以 confirm 一下呢……当时我恨不得拍死自己,猪脑子啊……在面试官的提示下最后给出了答案……

事情发展到这里也许就该结束了,因为大家看得出来我有多惨,鸟哥也和我说了,他的老板觉得我还是一张白纸,要不要冒险还是需要看上面老板的意思。实话说,那时我已经在考虑以后留在锤子继续写漂亮的页面了,但最后得到的通知是上面的老板还是想面我一下,稍后通知我四面的时间。四面鸟哥给我打了预防针——香港的同事,普通话不太好,平时开会完全是英文……我想了下,能准备的可能真的只有 self introduction 了,所以虽然觉得没什么悬念,但还是认真准备了一份自我介绍。第二天上午面试官和我准时在网上见面,也是让我先做一段自我介绍,由于这次是有备而来,所以没有向上次那么狼狈。接下来面试官和我说的英文我也基本能听个差不多——我相信是面试官有意用了更简单的词汇。当然也有一些专业词汇我不熟悉,面试官都体贴地为我用文字发了出来,通过 Google 搜索知道大概的中文意思,然后做出了回答。其实由于第四面我没有冲着过的心态面对,结果反而聊得很开心,四面结束后不久, HR 通知我,面试官觉得你还 OK ,下午还要进行第五轮面试。这很让我感到意外,但还是那个心态,我能在微软的面试中走到这里,已经心满意足了,这甚至超过了很多 CS 专业的同学!第五轮面试是鸟哥老板的老板的老板……没有太紧张,因为是一位美女,声音和我大学认识的一位计算机老师特别像,感觉非常亲切。她没有和我聊过多的技术问题,她也直接向我说明了,他们知道我计算机基础水平差,但他们现在也在转变想法,如果一个人的学习能力和学习动力强,他们也是非常欢迎的。看到这里,我一下感觉还有戏!于是赶快把我在研一转方向,用两三个月时间就研究明白了计算机视觉的基本算法,并且在后续的几个月里完成了 JS 的计算机视觉库、发表了自己的算法、写好了 paper ,甚至还在图灵社区发表了一篇还算有深度的总结文章。她听后很高兴,因为这些我没有写在简历上,她觉得在某一个层面上又认识了一个新的我。

接着就是我最开始说的那样,鸟哥在微信上通知我,大大大 BOS 已经给了我 Hire ! HR 可能会过些天通知。后来鸟哥告诉我,其实五轮面试面试官给的都是 Hire ,成绩也算不错,但还是那句话,他们都知道我的短处, CS 功底太差。

今天我以一个幸运儿的角色站在这里和大家分享,一是希望和大家分享我的快乐,二是希望大家能从我的经历中得到鼓舞,我相信你们多数人不会比我差。一个人的成功往往不可复制,微软今天给了我这个机会,他们愿意冒这个险,但不代表每个人都能像我这么幸运,大家还是要把基础知识学好,我也会在未来的一段时间里疯狂补 CS 的知识,不负鸟哥和微软的期望。

SOURCE
理解mouseover和mouseout“不稳定”的原因

事情的起因是博主负责的项目需要一个精美的滑块供用户调整图片大小,当然HTML5有现成的range控件博主是清楚的,但如果前端攻城狮如果做起来都那么惬意怎么会成为濒危物种呢?没错,兼容性,我们要兼容性,作为一个有情怀公司的员工,我们不会像某付宝那样放弃1%的用户!不会!

那么多了不说,直接上博主第一版的代码(仅做实例使用,与博主所负责项目的真实代码与外观不同):

See the Pen aOrOjZ by Li Zhe (@Sneezry) on CodePen.

是不是很难用!那个滑块根本不跟着鼠标走啊,十分地卡顿!这不是一个有情怀的公司应该做出来的东西!为了避免有些初入前端的同学看不太懂上面的代码,我来简单带着大家分析一下。HTML和CSS就不说了,我们直接来看看JS的逻辑。

首先滑块控制把手scrollHandle监听了mousedown事件,这样当用户鼠标在滑块上按下后就看着监视鼠标的活动情况。mouseupmouseout事件都绑定在了更大的scroll元素是为了避免用户鼠标移动速度过快移出scrollHandle元素导致事件捕捉失败。

博主在写完代码之后感觉逻辑完美得无懈可击,甚至专门设计了一个区域更大的scroll元素来提高事件捕捉的可靠性,可是事实呢!事实让博主甚是崩溃啊!怎么回事!

为了确定问题所在,博主将监听事件的结果输出到控制台,结果发现,当用户鼠标移出scrollHanlde时,scrollmouseout事件被触发了,但随后立刻又触发了scrollmouseover事件!这让博主一度认为是浏览器的Bug——没事你在那mouseoutmouseover的干啥!但最后证明其实是博主术业不精,对此博主深表愧疚,所以博主事后将W3C上Level 3 DOM Events从头到尾看了一遍。

我相信,对DOM Events了解不够深入的同学遇到这个问题第一感觉一定也认为是浏览器出了问题,因为mouseoutmouseover同时被触发是不合逻辑的。那么博主来和大家一同分析一下这件“诡异”事件是如何发生的。

DOM Events中有个属性叫bubbles,翻译过来叫冒泡。这个属性名看起来很有趣,感觉也有些抽象,但实际上它恰恰生动形象地表达了这个属性的意思。冒泡属性来说明这个事件是否会向下传递,这个性质我相信在很多事件中同学们都或多或少知道,但可能单独提出来同学们对不上号。比如click事件,当点击一个元素时,此元素的父系元素也会接收到点击事件,这就是因为子元素的click事件为冒泡事件,会传递给其父系元素。而bubbles这个词形象地描述出这个过程好比水中的气泡从水底慢慢上浮,只是DOM Events方向相反,传递是从顶向下的,直到传递到bodyhtmlwindow(这三者是传递链中的最后三个,不是并列关系)。

那么上例的scroll元素被浏览器折腾得反复收到mouseoutmouseout事件就不奇怪了,首先鼠标移出了scrollHandlescrollHandle接收到了mouseout事件,由于这个事件是冒泡的,随后便传给了scroll,于是scroll接收到了mouseout事件;之后鼠标移到了灰色或者蓝色进度条上,或者直接移动到了scroll上,无论三者中的哪一个,scroll都会因为冒泡接收到mouseover事件。这就是看起来在上例中mouseovermouseout“不稳定”的原因。

那么冒泡事件还有哪些呢?通过 W3C文档 可以知道,冒泡事件还有beforeinputclickcompositionstartcompositionupdatecompositionenddbclickfocusinfocusoutinputkeydownkeyupmousedownmousemovemouseupselectwheel

问题的原理搞懂了,那么应该如何解决呢?首先所有冒泡事件在传递的过程中,target都是不变的,所以可以直接在scrollmouseout事件中判断e.target === this来过滤其子元素传递过来mouseout事件。等等,这不是正规的解决方法!W3C显然考虑到了博主在开始说明的使用背景,所以有专门的事件来处理这个问题——mouseentermouseleave,这两个事件不冒泡,是我们真正需要用到的方法。或许诸位同学又有疑问了,“你前面不是说有情怀的公司不放弃1%的用户,要做到兼容性吗,这个新标准兼容性可以接受吗,IE全家你问过了吗”,您还别说,这两个事件正是IE发明的,IE全家都支持,甚至包括IE6!所以嘛,不用总吐槽人家小软就会添乱,人家真的给W3C提交了很多有价值的建议标准呢!

下面是改后的Demo,用起来是不是舒爽多了!

See the Pen Pqvqvy by Li Zhe (@Sneezry) on CodePen.

SOURCE
位置飘摇不定的inline-block

在页面排版中,我们经常需要将一些块元素塞进一行里,但是块元素默认都是自己独占一行的,要让它们委屈共用一行需要做些额外的工作。总的来说,有两种方法可以实现这一目的:一个是为块元素添加float浮动属性,另一个是将块元素display显示属性改完inline-block。那么两个方法如何选择呢?我们来看一下W3C对float属性的解释:

If you look in a typical magazine you’ll see images illustrating the articles, with the text flowing around them. The float property in CSS was created to allow this style of layout on web pages. Floating an image—or any other element for that matter—pushes it to one side and lets the text flow on the other side. Clearing a floated element means pushing it down, if necessary, to prevent it from appearing next to the float.

看到没有,W3C给出了一个典型的float应用场景:文字环绕图片混排。也就是说float的出现是为了解决图文混排的问题,所以并不是为了解决我们前面提到的将块级元素塞进一行的问题。也许您看到这里急了:能解决问题的方法就是好方法,那么教条干嘛?好,您说的对,能解决问题的方法就是好方法,但先等等,float用着真这么爽吗?请接着往下看,W3C的文档中还提到了一个clearing的问题,这是什么鬼?简单地讲,就是当使用图文混排时,默认情况下文字会一直环绕浮动元素排列,直到绕完为止,才会从页面左端继续排列。为了更生动地说明这一问题,我做了两个例子供大家查看。首先是没有clearing的情况:

从上面的例子可以看出,雷军老师的照片明显是为了搭配第一段对其生平介绍的文字,但是,紧随其后的《锤子手机——天生傲娇的工匠精神》这个标题很调皮地也来凑热闹了!这样还怎么玩,您倒是下去啊,这图不是给您准备的啊!可是浏览器也犯难了,臣妾做不到啊,说好的绕着排,这不还有位置吗……明白了吧,clearing就是为了解决这事的,在给后面那个h1标题标签添加了clear: left后,它就知趣地自己下去了:

不过这和我们前面提的问题有神马关系呢?有!有很大关系!您如果用float来实现块元素排在一行,默认情况后面的元素也会不明真相地跟着跑上来,就和前例中的h1标签一样。这也是很多同学不知道理由,但都明白要在浮动元素后面添加一个空的clear: both元素的原因。很多同学抱怨浏览器太傻,搞得浮动元素后必须加个clear: both才能正常工作,浏览器表示被冤枉大发了,您用错了方法知道伐!请求此刻浏览器的心理阴影面积。

说了这么多,博主就是在“处心积虑”地劝说正在看博客的您一定要用inline-block啊~您看人家名字都起得这么明显了,人家就是在告诉您,inline-block才是根正苗红的正确方法啊!

好了,前面讲清了inline-block的“正室”身份,下面就该开始批判他的坑了。

考虑如下的代码:

<div style="display: inline-block; background: #ff7234; color: white;">北京</div>
<div style="display: inline-block; background: #48bcff; color: white;">广州</div>

我们心中想象的样子是这样的:

可是!可是!可是实际的情况是这样的:

这特喵的中间那个缝是几个意思啊!嗯,您仔细看看上面的代码,北京和广州两个元素之间是不是有个换行符……对,没错,中间那个缝就是那个傲娇的换行符,人家也要求有身份地位的,作为一个换行符也是有尊严的!我相信此刻您的内心是崩溃的,这尼玛还怎么玩,难道要把所有元素都写在一行?!不用,为了避免给您本就几近崩溃的内心再来一次打击,我先给您吃个定心丸。于是您满眼期待地望着我:怎么搞?

其实您可以这么搞:

<div style="display: inline-block; background: #ff7234; color: white;">北京</div><
div style="display: inline-block; background: #48bcff; color: white;">广州</div>

啥?把下个元素标签的左括号丢到上一行去?嗯,没错,浏览器他会自己处理好的(浏览器:你大爷)。您可能又不高兴了:玩呢!爷是处女座的!噢噢,这种写法好像确实会给处女座同学造成更大的伤害……或者要不您这么改?

<div style="display: inline-block; background: #ff7234; color: white;">北京</div><!--
--><div style="display: inline-block; background: #48bcff; color: white;">广州</div>

您看,是不是好点,咱机智地把换行符注释掉了!(处女座:这特喵的也没好哪里去吧!)

好吧,既然您是一个傲娇,啊不,是严谨的处女座,还有一个终极大招供您使用:将父系元素字号改成0:

<div style="font-size: 0">
  <div style="display: inline-block; background: #ff7234; color: white;">北京</div>
  <div style="display: inline-block; background: #48bcff; color: white;">广州</div>
</div>

等等!客官莫急!这样测试就是啥都没了!为啥?因为您把父系元素字号改成0了,子元素默认继承啊,您得再重新挨个指定子元素的字号。嫌麻烦?不,您是处女座的,为了洁癖这样的付出是值得的!

<div style="font-size: 0">
  <div style="font-size: 16px; display: inline-block; background: #ff7234; color: white;">北京</div>
  <div style="font-size: 16px; display: inline-block; background: #48bcff; color: white;">广州</div>
</div>

您看,是不是这样就好了?或许您在我沉浸在完美解决问题的自我陶醉中突然醒悟:你特喵的逗我,webkit内核的浏览器限制最小字号是12px,哪来的0啊!您别着急质疑,试试就知道了 ;-)

至此,第一个坑解决完毕(啥?还有别的坑?对!)。

接下来就是诡异的垂直位置问题,话不多说,请看例子:

北京:<div style="display: inline-block; background: #ff7234; width: 200px; height: 20px;"></div>

显示的结果如下:

这里在“北京”的右侧放了一个橙色的统计条,这个统计条也许代表北京一年当中雾霾的天数,也许代表北京需要加班的程序员所占的比例等等,具体代表什么我们不关心,我们关心的是,默认情况下这个统计条在垂直方向是怎么对齐的?最起码现在看上去和“北京”二字的位置很不协调。

其实inline-block元素在垂直位置上是与父系元素的基线(baseline)对齐的。啥?基线是啥?怎么程序员们起名字都这么基情?别……别误会,这里的基线和基情没有半毛钱关系。基线指的是文字的基线,想必大家小时候学英文的时候都用过英文的4线本,然后我们工工整整地将字母按照要求写进那四条线相应的位置上,不严谨地说,4线本中,第3条线就是基线——除了y、g等不老实的字母,大部分字母都在这条线的上面。

那么在HTML中,这个基线由什么决定呢?线高(line-height)。咦?这个属性我们不经常手动指定啊,所以没指定的时候怎么办呢?线高如果不指定,会使用默认值,这个默认值是1em,即一个字的高度,而字的高度和font-size有关,如果font-size也没指定,就会使用浏览器的默认值,通常这个默认值是16px。好了,现在我们明白了,inline-block元素在垂直方向上会把自己的基线与父系元素的基线对齐。

但是在上面的例子中我们并不关心基线啊,我们想把右侧的统计条居中,居中,只想居中!好吧,看来我们不得不研究下一个新的东西了:vertical-align。您眼中闪烁着希望的泪光:这个看起来好熟悉啊,我用过text-align,相当好用啊!您……您别高兴得这么早,来看看伟大的vertical-align都支持哪些值:

Values: baseline sub super top text-top middle bottom text-bottom <percentage> <length> inherit

我此刻仿佛听到了您心中千万匹草泥马奔驰而过的声音。是,太多了,所以我体贴地为您做了下面这幅图:

好像还是有些乱,不过已经好多了,对吧。您似乎看到了一个叫middle的值就不想再听我磨叽了,您能改改这个心急的毛病吗,坑多着呢,您不要图样图森破啊,middle不是那么随便用的,您老老实实往下看。

middle这个值很复杂,不是我危言耸听,复杂到下面我们先不讨论它,而是放在最后说。那么除了middle外,基本分为三个类型的定位:基线对齐型、顶部对齐型和底部对齐型。其中基线对齐型的属性值有baselinesubsuperbaseline是默认值,我们在前面已经讨论过了,sub是将元素的基线与父系元素的角标基线位置对齐,super是将元素的基线与父系元素的上标基线位置对齐。顶部对齐型的属性值有toptext-top,分别是将元素的顶部与父系元素的顶部对齐,将元素的顶部与父系元素的内容区域顶部对齐。底部对齐型的属性值有bottomtext-bottom,分别是将元素的底部与父系元素的底部对齐,将元素的底部与父系元素的内容区域底部对齐。

需要注意的有两点,一个是不同类型的值,在对齐时使用的元素参照位置也不同(分别用元素的基线、顶部和底部做对齐参照),第二个是默认情况下,一般toptext-topbottomtext-bottom位置是一样的,但如果元素的内边距(padding)不为0,它们就不一样了。

好了,下面我们来静下心来说说middle这个值。看看w3school上面的解释:把此元素放置在父元素的中部。呵呵,您真是毁人不倦啊。不信?来来,看看这个例子 ,您看那个斜体的“Italic”,它就有middle这个属性值,您看它是在中部吗?

既然不是简简单单地放在中部,那么到底是怎么放的呢?W3C才永远是权威的,所以毫不犹豫地翻W3C文档,W3C是这么解释middle的定位方式的:

Align the vertical midpoint of the box with the baseline of the parent box plus half the x-height of the parent.

将元素的垂直中点,与父系元素的基线加上x-height的一半对齐。这个x-height是神马?我相信您到现在不止崩溃一次了,x-height简单地说就是当前样式下小写字母x的粗略高度。注意!注意!这个值已经明确到某个字母的粗略高度了,所以它不仅仅和字号相关了,还和字体相关!所以说,middle的定位,在不同的字体下都是不同的!您还想用吗?

问题说明白了,但是前面抛出来的问题不解释一下就是耍流氓对不。所以前面的例子到底是咋回事啊?您看,那个“Bold”有一个很大的字号,5em,但是它这么大的字号和它的父系div标签有关系吗?没有!半毛钱关系都没有!而它的父系元素没有指定字号,所以通常来说是16px,基线的位置也是以16px来定的。那么父系的x-height怎么算呢?实话说,没法算,不同系统默认字体不同,好吧,我们就认为小写的x高度差不多是一半字高吧,就是8px,然后根据定义,可怜的“Italic”的中线应该与父系元素基线加上x-height的一半对齐,就是8px的一半,4px。所以,“Italic”的中线就应该在父系元素基线上面4像素的位置上。嗯,结果与我们的分析完美匹配。

那么分析也分析了,打击也打击过了,问题总得解决啊,那个讨厌的统计条到底怎么放到中间啊?首先我们可以肯定地把默认值baseline换掉,因为我们并不关心统计条和基线的关系,那么和基线相关的属性值有baselinesubsuper,以及很遗憾根据上面分析确定的middle,这些都不能用。其实还有我们没有提到的<length><percentage>也是根据基线调整偏移的,所以也不能用。

我们关心的是统计条与文字边缘的位置关系,所以根据您的喜好可以选择text-top或者text-bottom,比如我选择了text-top,那么根据上面的代码,可以知道字高默认16px,统计条高20px,统计条比文字高了4像素,应该再上移2像素做到中心对齐,此时我们可以配合position: relative对定位进行调整,所以最后的代码为:

北京:<div style="display: inline-block; background: #ff7234; width: 200px; height: 20px; vertical-align: text-top; position: relative; top: -2px"></div>

最后的显示结果为:

这个调皮的inline-block终于呆在了它应该呆的位置上,真是傲娇啊……咦?前面的图里好像不小心把天生骄傲的锤子说成了天生傲娇……是不小心的啦~

SOURCE