多年前,因为换工作的需要,我得更新简历。但可能是写惯了CSS的缘故,在Word中排版实在是各种不顺手,于是就发挥了作为前端工程师的优势,把简历做成了网页。久而久之,这份简历就成为了我的个人产品,无论是否需要再找工作,每隔一段时间我都会进行更新迭代。
网页版简历有以下这些好处:
- 无须下载,直接打开;
- 内容展示形式更丰富,排版更灵活;
- 可以展示自己的技术能力;
- 可以通过超链接访问外部资源(如个人作品);
- 可以让自己登上搜索引擎的榜单(SEO)。
在此基础上,我还给这份简历定下了一个重要目标——「Write once, run anywhere」。兼容PC、手机和平板设备,还可以通过浏览器直接打印。
本文将从技术角度描述这样一份简历的开发过程。
设计
说到设计,很多程序员会喊:“我是写代码的,不懂设计。”但俗话说得好:没吃过猪肉,总见过猪跑吧。平时看过这么多网页,还跟大量产品设计稿打交道。看到布局合适的,抄一下;看到配色合适的,抄一下;看到素材合适的,也抄一下。值得注意的是,近年来有一类页面特别适合改造成个人简历,那就是手机厂商每发布一款新手机都会做的手机宣传页。我自己的简历设计就是借鉴了iPhone的宣传页。
此外,大家可能见过下面这两种比较吸引眼球的简历:
- 游戏式: http://rleonardi.com/interactive-resume/ 。
- 敲代码式: http://strml.net/ 。
然而,尝完鲜之后你会发现,它们的问题也很突出:
- 那个采用游戏交互的简历,其实没什么实质内容。
- 那个采用敲代码交互的简历,你确定招聘者愿意花这么多时间去等它敲完?
更重要的是,这种形式的页面打印出来是乱七八糟的。所以笔者并不建议把简历做成这样。
隐私保护
简历上涉及隐私的信息,无非就是姓名和各种联系方式(身份证号、出生日期什么的就没必要写了)。那么这里说的保护,主要为了防止不必要的骚扰(骚扰电话、垃圾邮件等)。
保护措施包括:
- 有针对性地隐藏信息,不要让所有人都看到;
- 对信息进行“加密”输出,避免被爬虫抓取。
隐藏信息可以通过网页URL参数来控制,有参数时显示隐私信息,没有时则隐藏(注意只把带参数的URL发给信任的人)。示例代码如下:
// URL是否带有privacy参数(例如「http://abc.com?privacy」)
var showPrivacy = /[?&]privacy=?(&|$)/.test(window.location.search);
document.write(showPrivacy ? '张三' : '张先生');
而“加密”信息的方法,则多了去了,例如:
['me', 'abc', 'com'].join('.').replace('.', '@'); // "me@abc.com"
['a13800b', 'c138000d'].join('').replace(/[a-z]/g, ''); // "13800138000"
甚至,你可以把这些信息做成图片。
适配PC和移动设备
一个页面适配多种设备,对前端开发工程师来说并不陌生。
首先是通过媒体查询在不同的窗口宽度下采用不同的布局。
其次,处理高分屏下图片的清晰度问题。鉴于「image-set」、「src-set」的兼容性还不是那么好,解决方案有两种:
- 用两倍或三倍大小的原图,再通过CSS缩小。主要用于内容图片,比如作品的截图。
- 使用SVG格式的图片。主要用于设计素材,比如一些小图标。
最后,不要忘了贴上简历URL的二维码,方便移动设备直接扫码访问。
打印
打印机也是一种设备,可以通过媒体查询适配。但是适配之前,要先搞清楚打印机的世界是怎么样的。
A4纸的尺寸是宽21cm、高29.7cm,但是如果你用Chrome把页面存成PDF(在打印的界面可以存)之后,看到的分辨率是宽595px、高842px。研究了一番之后,我发现这是按72ppi(Pixels Per Inch,即一英寸所含的像素数)换算的:
1in = 2.54cm
21cm / 2.54cm * 72px ≈ 595px
29.7cm / 2.54cm * 72px ≈ 842px
在595px这个宽度下,按照屏幕适配的规则,会采用小屏设备的布局。然而,A4纸的空间比小屏设备的屏幕要大得多,并不适合采用这样的布局,所以就需要大量调整样式。在这过程中,踩进了一个坑:
@media not print and (max-width: 639px) {
/* ... */
}
上面的媒体查询代码,表达的并非「不是打印设备并且宽度不超过639px」,而是「不是宽度不超过639px的打印设备」(关于这一点可以看看Mozilla的解释说明)。
不过生人自有妙计,我们可以把媒体查询改成嵌套式:
@media not print {
@media (max-width: 639px) {
/* ... */
}
}
结果又掉进了另一个坑,有些旧内核的浏览器并不支持嵌套媒体查询。就这样折腾了一番之后发现,根据不同的设备应用不同的样式表文件才是最好的选择:
<link href="./css/style.css" media="not print" rel="stylesheet" type="text/css" />
<link href="./css/style-print.css" media="print" rel="stylesheet" type="text/css" />
那么打印的样式要怎么写呢?首先是「@page」,可以用来修改页面容器的版式,最常用的是指定页面的尺寸及边距:
@page {
size: A4 portrait;
margin: 2.1cm 1.9cm;
}
其次,某些浏览器默认是不打印背景色和背景图片的。对于Chrome,可以加上这段CSS代码强制打印背景(对其他浏览器来说,暂时没有办法):
body { -webkit-print-color-adjust: exact; }
再次,根据需要处理链接。要知道打印出来之后,用手指往纸上戳是打不开网页的。如果你想让别人看到链接地址,也可以通过CSS将其输出到页面上,例如:
a:after { content: '[' attr(href) ']'; }
最后,纸质简历的篇幅不宜过长,可以选择性地隐藏一些内容。
/* style.css */
.only-for-print { display: none; }
/* style-print.css */
.not-for-print { display: none; }
<div class="not-for-print">这是非打印版</div>
<div class="only-for-print">这是打印版</div>
顺带一提,以上所说的这些做法都不兼容IE6- 8等旧浏览器,但是打印店可能还在用XP系统,所以输出个pdf文件去打印是比较保险的。
构建
作为一名现代前端开发工程师,简历怎么能少了构建这一步呢?虽然只有一个页面,但是该做的还是得做。以我自己的简历为例,没有外部JavaScript脚本(保护隐私的逻辑就直接写在页面上了)、也没有用Sass等动态样式语言,因此只需要一些基本的构建(文件名添加Hash、压缩CSS代码、压缩HTML代码等)。
首先,整理一下文件结构:
- src/ 源代码文件夹
- dist/ 发布文件夹
- webpack.config.js 公用构建逻辑
- webpack.dev.config.js 开发环境构建逻辑
- webpack.prod.config.js 生产环境构建逻辑
- package.json
构建用到的npm包如下:
"devDependencies": {
"autoprefixer": "^9.1.5",
"css-loader": "^1.0.0",
"cssnano": "^4.1.0",
"extract-loader": "^2.0.1",
"file-loader": "^1.1.11",
"html-webpack-plugin": "^3.2.0",
"postcss-loader": "^3.0.0",
"webpack": "^4.16.4",
"webpack-cli": "^3.1.0",
"webpack-dev-server": "^3.1.5"
}
公共构建逻辑如下:
const path = require('path');
module.exports = {
entry: [
'./src/main.js'
],
output: {
path: path.resolve(__dirname, './dist')
},
module: {
rules: [
{
test: /\.css$/,
use: [{
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: 'assets/',
publicPath: '/assets/'
}
}, 'extract-loader', 'css-loader', 'postcss-loader']
},
{
test: /\.(jpg|png|gif|svg)$/,
use: {
loader: 'file-loader',
options: {
name: '[name].[hash:8].[ext]',
outputPath: 'assets/',
publicPath: '/assets/'
}
}
}
]
}
};
上文提过,页面没有用到外部JavaScript,但是entry又写了「main.js」。其实这个js文件是空的,它的作用是:
- Webpack的entry不能为空,无论如何都得设一个入口;
- 如果没有入口js,「webpack-dev-server」也就无法注入热更新的逻辑。
接下来看看开发环境的构建逻辑:
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const config = require('./webpack.config.js');
module.exports = Object.assign({
mode: 'development',
plugins: [
new HtmlWebpackPlugin({
template: './src/index.ejs',
inject: true,
minify: {
collapseWhitespace: true
}
})
],
devServer: {
compress: true,
overlay: true,
port: 4550
}
}, config);
为了构建html文件,这里用到了「html-webpack-plugin」,并且要把html文件中对资源的引用改成模板占位符(ejs格式):
<link href="<%= require('./css/style-v2.7.css') %>" media="not print" rel="stylesheet" type="text/css" />
<link href="<%= require('./css/style-v2.7-print.css') %>" media="print" rel="stylesheet" type="text/css" />
最后看看生产环境的构建逻辑:
const config = require('./webpack.config.js');
const HtmlWebpackPlugin = require('html-webpack-plugin');
module.exports = Object.assign({
mode: 'production',
plugins: [
new HtmlWebpackPlugin({
template: './src/index.ejs',
inject: false,
minify: {
collapseWhitespace: true
}
})
]
}, config);
由于页面没有外部JavaScript,而且生产环境下也不需要「webpack-dev-server」,所以「inject」设成了false(仍然会生成main.js,只是没有注入到页面中)。
实例
最后贴一下我简历的截图(790多k,大部分内容打了码)。PC版和打印版的主要区别在于:
- PC版页头的「个人博客」是个超链接;打印版则显示链接地址。
- PC版的技能描述是个柱状图,鼠标移到图上时显示对应描述;打印版则显示描述内容,没有柱状图。
- 考虑到教育经历对多年工作经验的人没有太大作用,所以打印版没有教育经历区块。
- 由于工作经历较长,打印版省略了几家公司的描述。
- 由于项目经验较多,打印版省略了几个重要性相对较低的,且去掉了截图和超链接。
- PC版的二维码描述是「您可以扫描二维码在移动设备打开本页」;打印版的二维码描述是「您可以扫描二维码查看完整版简历」。
后记
本文第一版发表于2015年中,于2018年10月底对内容进行了修改和完善,希望对广大找工作的程序员同胞们有所帮助。
评论 (26条)