打造一份网页版简历

Heero.Luo发表于5年前,已被查看7672次

多年前,因为换工作的需要,我得更新简历。但可能是写惯了CSS的缘故,在Word中排版实在是各种不顺手,于是就发挥了作为前端工程师的优势,把简历做成了网页。久而久之,这份简历就成为了我的个人产品,无论是否需要再找工作,每隔一段时间我都会进行更新迭代。

网页版简历有以下这些好处:

  • 无须下载,直接打开;
  • 内容展示形式更丰富,排版更灵活;
  • 可以展示自己的技术能力;
  • 可以通过超链接访问外部资源(如个人作品);
  • 可以让自己登上搜索引擎的榜单(SEO)。

在此基础上,我还给这份简历定下了一个重要目标——「Write once, run anywhere」。兼容PC、手机和平板设备,还可以通过浏览器直接打印

本文将从技术角度描述这样一份简历的开发过程。

设计

说到设计,很多程序员会喊:“我是写代码的,不懂设计。”但俗话说得好:没吃过猪肉,总见过猪跑吧。平时看过这么多网页,还跟大量产品设计稿打交道。看到布局合适的,抄一下;看到配色合适的,抄一下;看到素材合适的,也抄一下。值得注意的是,近年来有一类页面特别适合改造成个人简历,那就是手机厂商每发布一款新手机都会做的手机宣传页。我自己的简历设计就是借鉴了iPhone的宣传页。

此外,大家可能见过下面这两种比较吸引眼球的简历:

然而,尝完鲜之后你会发现,它们的问题也很突出:

  • 那个采用游戏交互的简历,其实没什么实质内容。
  • 那个采用敲代码交互的简历,你确定招聘者愿意花这么多时间去等它敲完?

更重要的是,这种形式的页面打印出来是乱七八糟的。所以笔者并不建议把简历做成这样。

隐私保护

简历上涉及隐私的信息,无非就是姓名和各种联系方式(身份证号、出生日期什么的就没必要写了)。那么这里说的保护,主要为了防止不必要的骚扰(骚扰电话、垃圾邮件等)。

保护措施包括:

  • 有针对性地隐藏信息,不要让所有人都看到;
  • 对信息进行“加密”输出,避免被爬虫抓取。

隐藏信息可以通过网页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条)

发表评论

(必填)

(选填,不公开)

(选填,不公开)

(必填)