All in Web3
Remote
:pencil2: 博客写在 Issues 里
Home Page: http://riskers.github.io/
License: MIT License
egret 是国内的一款优秀的开发游戏开发引擎,去年的时候曾经了解过,做过一个简单的demo,然后就不了了之了,因为实际工作是用不到的。
但是,近来工作中拿到别人拿给我看到的H5案例,很酷很炫的那种,一看源码,全是egret之类的游戏引擎来做的。其实不难理解,这类引擎都是通过canvas绘制界面,性能必然比DOM要好。试想一下这样的页面如果用DOM来做,恐怕做完之后也是卡顿的不要不要的:
本文只介绍最简单的egret,不是教程,具体的教程、API等参见手册。
我觉得egret和其他游戏引擎相比最强大的就是它的工具流,有设计师用的,有开发用的,一应俱全。可以在官网找到。
一般来说,使用上面的工具就可以开始开发我们的小游戏了。
其他几个工具可用可不用。
egret并不是用的JS,而是TypeScript。TS是JS的超集,整体感觉很像Java,据说对C#程序员很友好。
class P2 extends egret.Sprite{
private man: Man;
static initX = -830;
public constructor(){
super();
}
}
TS国内资料比较少,这是我找到的一份中文手册:
不需要先学习TS才能使用egret,完全可以边用egret边学习TS,毕竟TS里的很多东西是和JS一样的。
介绍完背景,就可以开始使用 egret 了。
使用Egret Wing新建一个项目 Hi,点击下一步
选择舞台宽度和高度以及适配方案,点击完成:
可以看到Hi项目的目录
Main.ts
是入口文件构建项目:项目-构建并运行,可以看到最简单的egret项目。
发布:项目-发布,正式包就在 bin-release
中
DisplayObject
,Shape是最简单的显示对象DisplayObjectContainer
,Sprite是最简单的显示容器关系如下:
DisplayObject
|--- DisplayObjectContainer
|--- Sprite
|--- Stage 舞台
|--- Shape 矢量绘图
|--- Bitmap 位图
|--- TextField 文字
显示对象都必须addChild在显示容器中才能够被显示。这里是最简单的例子:
class Main extends egret.DisplayObjectContainer {
public constructor() {
super();
var obj = new egret.Shape;
obj.graphics.beginFill(0x000000);
obj.graphics.drawRect(0,0,100,100);
obj.graphics.endFill();
obj.x = 100;
obj.y = 100;
this.addChild(obj);
}
}
游戏中经常需要知道两个物体是否接触,egret对于碰撞检测是很方便的。
var isHit = obj.hitTestPoint(x,y);
egret的事件机制与DOM类似,分为捕获阶段、目标阶段、冒泡阶段,也有自定义事件以及触发。
这个其实很好理解,不再多说。
egret的动画系统也很方便:
egret.Tween.get(shp).to({
x: 40
},100)
表示100ms内把x移动到40
RES 是egret为开发者准备的一套功能完善的资源加载机制。开发者编写游戏时,无须关心资源加载的细节,只需要指定资源,并且在对应的位置中添加相应地执行代码即可。
在resource中,default.res.json
是资源配置文件:
不用手写,使用 ResDepot
工具即可轻松管理:
使用 b1_png
这张图的时候只要这样其他什么都不用管:
var b1 = new egret.Bitmap(RES.getRes('b1_png'))
var img = new egret.Bitmap;
img.texture = RES.getRes('pic_name');
this.addChild(img);
getRes 返回的数据并不是位图,而是内容数据,也就是位图纹理,对应 egret.Texture。需要设置位图的texture属性为 getRes 返回的数据,图片才能显示。
纹理集,其实就是CSS里的雪碧图。我们可以通过 Texture Merger 轻松制作管理。
比如我们可以用这样的图片:
可以『拼』成这样的动画:
实际上只要这样加载资源即可:
音乐的播放更加简单
var sound = RES.getRes('music_mp3');
var channel = sound.play()
egret的内容远远比上面的介绍多得多,还有粒子系统、龙骨骨骼引擎、3D系统、网络请求等等,可以制作出非常复杂、庞大的游戏。可以看 HTML游戏引擎深度测评 里对egret的介绍就知道现在egret能够做出什么样的游戏。
egret为游戏开发提供了完整的工具流,尤其是资源加载,我在开发的时候很舒服,只用关心游戏逻辑就好了。作为前端来说,掌握egret也算是一个课外实践了。总之,好处多多,赶紧使用吧。
献上我使用egret花了3天时间边学边做的项目:
http://huodong.mobilem.360.cn/0422/index.html
离职了,也该为这三年的职业生涯做一次总结了。
2022 以来的这小半年,算是我的 「Gap Year」,学了大量的东西: Web3、区块链、交易等等。
看看三年前,发的朋友圈:
我现在都记得刚搬到清上园那天,我站在罗森门口,看着街对面办公楼的样子。唰地一下,三年过去了,快到我现在能回想起这三年三十六个月里,几乎每个月做了什么,遇到了什么人,发生什么事我都记得!
我觉得这很可怕,应该是我变老了。因为前几年从来不会感慨什么时间不够的问题,这个玩意今天没搞明白,就明天搞喽,没什么大不了了。这三年不是了,我会开始很功利地学习一些东西,或者好听点,叫有目标地去学习。
先说说当时为什么来这家公司,因为这里接受我转型后端开发。那为什么要转后端呢?现在想来,也是我上一次创业失败后的 PTSD,当时急于需要证明自己以及拓宽自己的技能树和知识面,为下次创业做准备。
转型的结果如何?我觉得还不错,是在自己当时的预期之内。我记得刚来的时候,自己列了一个单子,是我自己根据我当时对后端的理解去写的,也算是我对这一段职业经历的目标。现在想想,那其实也是一份对自己的 OKR,只不过,不知道需要多长时间达到这个程度。
这三年,有几点是我明显进步的:
这次,我耐住了寂寞,第一次在一份工作上待了三年,是职业生涯最长的一段。这期间我一直在思考自己到底想要什么,想要做什么。感觉这三年在公司学到的东西倒没什么,反而自己瞎折腾,自己写代码,掌握了不少东西。
我把每一家公司都当作是去上学,离职的时候就是毕业的时候。毕业的时候,就是我列的 OKR 完成的时候。这也是我几乎没有涨薪,还在这待了三年的理由。
为什么离职?因为入职的时候列的、已经扩充了很多倍的 TODO 在去年底达到了,再待着也不会有提升了,就想去外面的世界看看。当时入职的时候就说这家公司应该是我在北京的最后一家公司了,没想到成真了。
三年里错过了三次机会,一次是创业之前去美团的机会;一次是字节前端的机会;还有一次是字节后端的机会。过去了就是过去了,没什么好说的。如果按照这条路往下走,除了薪水更高,钱包更鼓了之外,好像也没什么大不了的。
为什么不更博客了?这三年开始记笔记,不想写博客了。因为博客比笔记要花好几倍的时间。这是我三年来反反复复修改的知识图谱,这里顺便谢谢 Notion 这么好的软件,让我的大脑省去了不少的负担。
离职去哪里了?
这次出去找工作,主要在看 remote 的机会,有创业的,唯独没有看所谓大厂机会的,对我而言没有什么吸引力了。最让我惊讶的是,remote 工作里,有比较高薪水的,着实让我有些心动,说明世界往一个好的方向前进。
最后选了一个能 remote,薪水还非常有吸引力(一下把这几年没涨的工资补上了)的公司,行业还是我看好的 Web3。最近公司又融了一笔钱,我希望这是好的开始!
唉,年底又是世界杯了,四年一个槛啊,上一次世界杯的时候在创业,再上一次世界杯的时候刚毕业。
蹲下去,是为了跳得更高!
再见了,可惜这个月一直在居家办公,不让去科技园,最后都没法在园区里照几张相片。
使用 redux-thunk、 redux-saga、 redux-observable、 MobX制作同样的一个小项目:
这个项目调用github API查找 github 用户,点击头像之后还可以知道这个用户的 follower 和 following。简单来说,就是调用了三次 API(即三次异步请求)。
本文不是教程,并不会手把手地教你如何使用他们,只是一个指引,一份示例,让你能够了解他们各自的特点,以便选择最适合自己的。
redux-thunk我们在 Redux 中操作异步最简单的方法。配合 async/await,你可以像写同步代码一样进行异步操作。
// action creators
const getUsers = username => {
async dispatch => {
dispatch({
type: 'LOADING'
})
try{
const response = await fetch(`https://example.com/`)
let data = await response.json()
dispatch({
type: 'SUCCESS',
payload: data
})
}catch(error) {
dispatch({
type: 'FAILURE',
error: error
})
}
}
}
dispatch dispatch update
VIEW ----------> function ----------> ACTION object ---------> REDUCER --------> STORE --------> STATE
| (return async dispatch) ^
| |
| |
| |
| dispatch |
|---------------------> ACTION object -----------------------------
VIEW 会 dispatch 一个 ACTION object 或一个高阶函数(返回 async function):
redux-saga 简化了 action creator
,redux-saga 通过创建 Sagas 将所有的异步操作逻辑收集在一个地方集中处理。Sagas 可以被看作是在后台运行的进程,监听发起的 action,然后决定基于这个 action 来做什么:是发起一个异步调用(比如一个 Ajax 请求),还是发起其他的 action 到 Store,甚至是调用其他的 Sagas。一旦出发 sagas 监听的 action,sagas 会通过一系列的effects dispatch (比如 put
)一个 action。
// action.js
export const searchUsers = (username, page) => {
return {
type: CONST.FETCH_GITHUB_SEARCH_USER,
payload: {
username,
page
}
}
}
// App/sagas.js
function* fetchUsers(action) {
let {
username,
page
} = action.payload
yield put({
type: CONST.FETCH_GITHUB_SEARCH_USER_LOADING
})
yield call(delay, 2000)
try {
let response = yield call(fetch, `https://api.github.com/search/users?q=${username}&page=${page}`)
let data = yield response.json()
yield put({
type: CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS,
payload: data
})
}catch(e) {
yield put({
type: CONST.FETCH_GITHUB_SEARCH_USER_FAILURE,
error: "No This User"
})
}
}
export default function* () {
yield takeLatest(CONST.FETCH_GITHUB_SEARCH_USER, fetchUsers)
}
// sagas/index.js
import AppSaga from 'containers/App/sagas'
export default function* rootSaga() {
yield AppSaga()
}
dispatch not match update update
VIEW ----------> ACTION object -----------> REDUCER --------> STORE --------> STATE
| ^
| |
| match |
| |
| (data) |
| |
\| dispatch |
SAGAS ------------> ACTION object
VIEW dispatch 一个 ACTION object:
原理不同:Sagas 不同于 Thunks,Thunks 是在 action 被创建时调用,而 Sagas 会在应用启动时调用(但初始启动的 Sagas 可能会动态调用其他 Sagas),是常驻后台的(因为 generator)。
redux-saga 有cancel、takeLeast这些操作,这是 async 做不到的(这也是 generator 相对 async 的优势)
redux-saga 易测试,比如它可以无阻塞地调用一个 generator(fork)、中断一个 generator (cancel)。这些特性在业务逻辑复杂的场景下非常适用。测试代码详见这里
redux-saga 保持了 action 的原义,保持 action 的简洁,把所有有副作用的地方独立开来(这样action里会很干净)。这种特性让 redux-saga 在业务逻辑简单的场景下,也能保持代码清晰简洁。
你要先了解 rxjs ,然后再往下读。因为 redux-observable 就是让你能够在 Redux 中使用 rxjs。
为了方便理解 rxjs,我做了一个纯 js 版的项目 jsbin,之后能够我们再把它改成 react 版的。
dispatch not match update update
VIEW ----------> ACTION object -----------> REDUCER --------> STORE --------> STATE
| ^
| |
| match |
| |
| (data) |
| |
\| dispatch |
EPICS ------------> ACTION object
Epic 是 redux-observable 的核心,它是一个函数,接收 actions 流作为参数并且返回 actions 流: Actions 入, actions 出。返回的 actions 会通过 store.dispatch()
立刻被分发,所以 redux-observable 实际上会做 epic(action$, store).subscribe(store.dispatch)
。
可以看到,redux-observable 和 redux-saga 的数据流是相似的。关于他们的异同,可以查看这两篇文章,不再赘述:
先看一个 demo,它包含了 @action
@observable
@observer
@computed
@inject
等重要概念。
modify update trigger
Events -----> Actions -------------> State --------> Computed values ----------> Reactions
(@action) (@observable) (@computed) (@observer)
^ |
| |
| (mobx-react) |
|------------------------------------------------------------------------------|
MobX vs Redux: Comparing the Opposing Paradigms 演讲已经介绍,不能看视频的看 MobX vs Redux: Comparing the Opposing Paradigms - React Conf 2017 纪要 也是可以的。
作者: 一波不是一波
转载请注明出处并保留原文链接( #48 )和作者信息。
这篇文章教你怎么用 gitbook + travis 在 github pages 上优雅地发布书籍。
git clone https://github.com/riskers/gitbook-template
cd gitbbok-template && rm -rf .git # 去掉模板中的历史记录
修改模板:
.travis.yml
:
book.json
: 修改 gitbook 相应配置,不是这里的重点,不多介绍。配置结果见 https://riskers.github.io/gitbook-template/ ,可根据喜好自己修改chap01
和 chap02
对应 SUMMARY.md
中的地址,就是这本书的内容了。然后就是在 github 上新建一个项目,并且 push 上去,然后你能看见这样的项目结构:
如果没有注册过 github pages 服务,还要先注册(注册过程略)。
新建 gh-pages
分支:
git checkout -b gh-pages
git push origin gh-pages
在项目『Settings』-> 『GitHub Pages』开启 github pages 服务:
给这个项目开启 Travis 服务: https://github.com/marketplace/travis-ci
最后你应该能看到这个界面:
在个人设置里申请 token 好让 Travis 有权限改这个项目:
然后选择 repo,点击生成按钮:
复制生成的 token,填写在 Travis 的设置-全局变量中,并且取名为 TOKEN
:
添加一行字:
然后 push 上去,可以看到 Travis CI 在跑了:
稍作等待,跑完之后,可以在 gh-pages 下看到 Travis CI 给我们推过来了 gitbook build 之后的内容:
可以在 github pages https://riskers.github.io/gitbook-template/chap01/1-1.html 上看到效果:
完成!
这一阵在用 gitbook 写一本电子书,发现多于两本就要收费了,而老版本是不收费的,所以我最近一直在用老版本。但可能是维护少的原因,老版本的同步总是不及时,于是我放弃了 gitbook 服务。
本来想在本地 gitbook build,然后 push 到 pages 服务,但是这样太不优雅了,就琢磨了一下怎么用 CI 来做这件事,刚好之前在公司折腾过一阵 gitlab ci,很快就解决了。
至此,我做到了在 master 上写 md
,然后 push 到 master, Travis 自动执行 gitbook build
,并且把生成好的文档 push 到 gh-pages
,好让 pages 服务生效。全自动的,是不是很优雅?哈哈。
顺便做个广告,这两本书都是按照这种方式写的:
五一过后,就提了离职,加上北京一直让居家办公,我磨洋工般地把 GithubX 迭代了两个版本,0.0.4 和 1.0.0。
ps. 其他时间在学 rust,感觉之后工作会用到,这次终于入门稍微深入了一点,可能是已经提了离职,心情很放松地去学的缘故。
除了上一篇写的那两点,这次又学到了或者说稍微费了点时间做的:
action -> Service
,Service 会根据用户的选择自己决定是调用 IDB 层还是 API 层。然后用 interface 抽象了 Model 层,这里使用了策略模式,这一部分我觉得我写得算是整个项目里最秀的。chrome.storage.local
是异步的,如果常规思路,就是把实例化这一步放在 async
里,但是这样就无法 export
了。最后用 Top Level Await 解决,其实也就一行,加个 await
就行了!content_page
监听一个 DOM 去搞定用户在点击 star 按钮的时候弹窗,但是这样一旦 Github 的 DOM 改变,我也要变!后来直接换思路,监听 star 时的 API,一旦请求这个 API,就直接弹窗,不用管 DOM 结构了!而且因为是弹窗形式,在整个 Github 网站里都通用!这个想法我自己都觉得挺惊艳的!还衍生出 Github-API 项目:
截至目前,GithubX 一共发布了 4 个版本,有 377 个用户。
总结一下
前两年学的概念太多,有点应接不暇,导致「学而不思则罔」,只学了概念,没有进行反思,没有深入地实践,没有 coding,就会流于表面,没有沉淀。仔细看看,这个项目是三年前我刚入职的时候就建立的,一直拖拖拉拉,到现在离职了才完成。中间经历过很多,但都不足以成为我放弃的理由,尽管中间还是放弃了,这个之后单开一篇来说。
2019 一个字:变,其中包括**的转变(创业失败后的总结),工作的转变(前端做到后端),公司的转变(换了个工作)。
可以看到,今年主要在学 Java,不过今年的非技术书看得太少,2020 争取多看几本。
技术
今年开始做后端开发,也在部门接手了几个项目,开始学习 Java 技术栈。另外,因为对并发感兴趣,想要研究几个不同的并发模式,所以又学了 Erlang。现在能分清 Java 的多线程并发、Golang 的 CSP 和 Erlang 的 actor 模型。
我入职的第一周就开始接手 Java 项目,坦白说,学习一门类 C 的编程语言对于已经有几年编程基础的我来说其实不难,我从 TS 切到 Java 就用了2、3 天。难就难在相关技术栈不是几周就能玩明白的,什么 maven、Spring、RPC、消息队列等等,这些东西不是和语言相关,而是一种**,这要花很多时间。这一点在 学习编程语言的一些经验 已经说明白了。
工作
今年,从创业公司离开来到小米。10 个月的创业经历当是交了一次学费,2018 年过得还是挺丧的。
另外,现在明显感觉到了害怕,因为团队里已经有 97 年出生的同学,明显感觉到自己年纪比较大了,自己还是要多多努力啊。
在 迷思 中有总结。
**
去年我可以说我理解到了**『我首先是工程师,其次是前端工程师』**。今年,我体会到了什么是技术,什么是业务,技术和业务的关系是什么。这点在 谈技术选型:根据业务做技术 已经有总结。
不多说了,前几天在知乎回答过了。
可以理解 OpenResty = Nginx + Lua,可以用 Lua 扩展 Nginx
其中 OpenResty 官方提供了 Lua 组件,我这次使用了2个第三方组件:
之前有个项目没有用前后端分离,整个项目都是 Java 的,前端代码会打包在 jar 包里。每次前端要发布,需要后端来发,这不合理!
所以,改造之。改造很顺利,使用的是 Nginx 方案:
在 CI 阶段,前端打包 -> 整个编译后的文件(HTML/CSS/JS) 全都放在 Docker 容器里 -> 容器就装一个 Nginx -> 搞定
到这里就结束了,但是,这个项目在 JSP 模板里,给前端注入了变量,如:
<html>
...
<script>
var userInfo = <%USER_INFO%>
</script>
</html>
这个 <%USER_INFO%>
会被渲染成:
var userInfo = {
"name": "riskers",
"role": "admin"
}
本来没问题,但现在我们使用了 Nginx 来放置这个模板文件,就会出问题: 因为 Nginx 不会注入变量,用户访问这个页面就会出错: 因为 var userInfo = <%USER_INFO%>
这句会抛错。
这时候,就可以使用 OpenResty 解决问题
先安装 OpenResty, Mac 上直接使用 brew 安装:
brew install openresty
然后安装 lua 模块:
# 让 Nginx 拥有渲染模板能力的组件
luarocks install resty-template
# 让 Nginx 可以发送 HTTP 请求的组件
luarocks install lua-resty-http
这样改造流程变成了:
前端打包 -> 整个编译后的文件(HTML/CSS/JS) 全都放在 Docker 容器里 -> 容器就装一个 OpenResty -> 进入 index.html 前先调用 view.lua -> 搞定
至于 view.lua 干了两件事:
这样还没完,还要将其 Docker 化
Docker 化基本就是本机怎么做的,再在 Dockerfile 里再写一次
在调试 Nginx 配置的时候,一开始每改一部分,要 build 一次镜像,然后在前端编译过程中花很长时间,才能看到调试结果。后面我也有经验了: 在 Dockerfile 里写好要安装的软件后,就可以直接 exec 进去,直接改 Nginx 配置,然后重启,立刻看到效果。这可能也是我自己在 Docker 化时最大的省时间技巧了。
还有一个地方花了我一点时间,如何在两个 FROM 的基础镜像基础上交互,即把上一个镜像(Node)build 出来的 dist 文件夹 COPY 到这个镜像 (openresty) 里。AS
和 COPY --FROM
搭配起来就解决了:
FROM node:lts-alpine as builder
# ...
COPY --from=builder /app/dist /fe
看起来可能很简单,可整个过程花了我2天时间,解决了:
这也是我第一次如此较深入地去了解 OpenResty 并投入使用,完整代码在 https://github.com/riskers/openresty-demo
注: 本文说的 React16 是指当前最新版本 16.5.2 ,而不是指 16.0.0。很多特性都是在 16.0.0 之后陆陆续续加的,而不是在一次性在 16.0 加入的。特别是
getDerivedStateFromProps
在 16.4.0 之后还改过一次。
实例: https://stackblitz.com/edit/react16-ref
之前已经有 string ref
(将被废弃) 和 callback ref
,React16 新加入 createRef
:
// init
this.btnRef = React.createRef();
// access
this.btnRef.current
实例: https://stackblitz.com/edit/react16-new-context
// init
const Context = React.createContext();
// use
<Context.Provider value={/*...*/}>
<Context.Consumer>
{value => {
//...
}}
</Context.Consumer>
新加入 componentDidCatch
/ getDerivedStateFromProps
/ getSnapshotBeforeUpdate
三种生命周期,并且将 componentWillMount
/ componentWillReceiveProps
/ componentWillUpdate
置为 UNSAFE
。
实例: https://stackblitz.com/edit/react-componentdidcatch
实例: https://stackblitz.com/edit/react-getderivedstatefromprops
在组件实例化后和接受新 props 后被调用,需要 return 一个对象来更新状态或返回null表示新的props不需要任何state更新。
在react render()后的输出被渲染到DOM之前被调用。
实例: https://stackblitz.com/edit/react16-fragments
一直以来,React render 只能 return 组件,不能是 string、 array、 boolean等值,这其实限制了开发者的能力。React 16 给我们带来了这些新功能:
// string
render() {
return 'this is string'
}
// number
render() {
return 123
}
// boolean
render() {
return true && <div>abc</div>
}
另外,render return 要求一定要有一个根组件,而开发者就不得不在外层套一个 <div>
,现在 React16 也给了我们方法:
// 方法1: 返回 array,不过每一项必须有 key
render() {
return [
<h1 key="a">a</h1>,
<h1 key="b">b</h1>,
]
}
// 方法2: React.Fragment
render() {
return (
<React.Fragment>
<h1>a</h1>
<h1>b</h1>
</React.Fragment>
)
}
// 方法3: 其实还是 React.Fragment,不过是简写
render() {
return (
<>
<h1>a</h1>
<h1>b</h1>
</>
)
}
实例: https://stackblitz.com/edit/react-portal-tips
React.createPortal
可以让开发者在组件中写逻辑,而在页面的别的位置渲染出来:
render() {
return ReactDOM.createPortal(
<div>
this is a dialog
</div>,
document.body
);
}
之所以会翻译这篇文章是因为我昨天看到@勾三股四的这篇微博,里面推荐的文章就是下面我要翻译的。因为自己一直对响应式图片这个技术很关注,但是一直没有一个很好的总结机会,今天趁着翻译这篇文章总结了,这是本人翻译的第一篇文章,有错误的地方请指出。
最近对responsive image
有一些感悟然后赶紧记下来免得忘了。下面就是我的感悟:
如果你是用像素来固定图片尺寸,又想在不同屏幕密度屏幕上实现响应式图片,可以这样:
<img width="320" height="213"
src="cat.jpg"
srcset="cat-2x.jpg 2x,cat-3x.jpg 3x">
它可以正常运行在所有现代浏览器上,而且在不支持srcset
的浏览器也可以降级到src
。
有一些规则是上面图片所没有提到的:
srcset
里的每一项都是<url> <density>x
结构 ,就像cat-2x.jpg 2x
srcset
里项目的顺序并不重要width
/height
,浏览器会按照屏幕密度展示它本身原始的width
/height
。 比如2x
资源被匹配到了,它会被渲染成本身的 50%width
和 50%height
3x
设备像素比的设备上浏览器渲染的是1x
的图片,可能是因为糟糕的网络环境这个案例使用的是3张一样的图片,只是尺寸不一样,这样我们很难看出区别。所以译者在这里换了这几张图,然后在Chrome中模拟手机调试,更换分辨率,应该就很明显了。
这种方法因为要人为匹配设备像素比,所以
1x
、2x
、3x
、4x
等等,这样HTML就会太臃肿,所以有了下面的新方法。
不同宽度的图片在响应式站点里是很常见的。在这篇博客里,图片内容都是占据了文章100%的宽度,但是文章的宽度并不总是窗口宽度的100%。
为了让浏览器匹配到正确的图片,我们需要知道:
`<img>`的宽度
最后一点是特别困难的,因为图片开始下载是在CSS解析之前的,所以<img>
的宽度不能从页面布局那得到。
<img src="panda-689.jpg"
srcset = "panda-689.jpg 689w,
panda-1378.jpg 1378w
panda-500.jpg 500w
panda-1000.jpg 1000w"
sizes = " (min-width:1066px) 689px,
(min-width:800px) calc(75vw-137px) ,
(min-width:530px) calc(100vw-96px) ,
100vw" >
通过srcset
属性,浏览器知道了哪些图片可用以及这些图片的宽度。
通过sizes
属性,浏览器知道了<img>
相对于一个已知宽度窗口的宽度。
这样,浏览器就可以匹配最佳资源加载了。
你不再需要说明屏幕密度,浏览器自己会辨别。如果浏览器窗口宽度是1066px
甚至更大,<img>
会被定为689px
。在1x
设备浏览器上会下载panda-689.jpg
,但是在2x
设备浏览器上将会下载panda-1378.jpg
。
这里感觉作者并没有解释清楚
sizes
和srcset
的工作原理(参照下面的译者案例来看)。首先,是关于
sizes
的理解:
比如当前窗口800px
,那么sizes
会匹配到(min-width:800px) calc(75vw - 137px)
,则这个<img>
对应的宽度就是800px*0.75-137px=463px
。这个宽度的设定相当于<img src="..." width="463" />知道了
<img>
的width
,然后再看srcset
的w
:
在dpr
为1
的时候,463px
对应463w
,查找srcset
,找到500w
适合它,就显示500的这张图。
在dpr
为2
的时候,463px
对应926w
,查找srcset
,找到1000w
适合,就显示1000的这张图。
一些规则是上面没有提到的:
srcset
里的每一项是<url> <width-descriptor>w
,比如panda-689.jpg 689w
srcset
里每一项的顺序没有影响srcset
包含了一个宽度描述符(w
),则src
会被那些支持srcset
的浏览器忽略掉sizes
里的每一项是<媒体查询> <图片宽度>
形式,除了最后一个仅仅是<图片宽度>
sizes
里的宽度单位都是px
sizes
里的第一个匹配到的媒体查询,所以sizes
里的顺序是很重要的如果你没有指明<img>
的宽度,浏览器也会正常解析。对sizes
精确设置,但是一个不是很确切的宽度也很好。比如
sizes="(min-width:1066px) 689px ,
(min-width:800px) 75vw,100vw"
挑选哪些图片资源放在srcset
里是很困难的,我也没有完全掌握技巧。在上面的例子里,我设置了一个最小尺寸(注:原文中是最大)(689px
),然后给2x
设备设置刚才的两倍尺寸(1378px
)。另外两个设置是在这两个值中间任意取的。我没有设置更小的宽度比如320px
,因为在这一情况下的屏幕密度是2x
或者更高。
srcset
+ sizes
在 Chrome、Firefox和Opera中都兼容。至于其他浏览器,也会很安全地回退到src
属性。不用等待很久,WebKit nightly
和 下一个稳定版本的Edge
就会很好地支持它。
WebKit nightly
是WebKit的mac port
,对于Safari
就像Chromium
对于Chrome
原文中
<img>
有一个内联样式width:100%
,一开始没有注意到的话还以为没有变化呢。上面的案例已经修改过了 -。-还是因为作者使用了内容相同,尺寸的图片,所以我换了图片重新做了一个例子:
这种新方法的
srcset
用来指向提供的图片资源,没有上面方法的1x
、2x
,这个都交给浏览器。例子中就指向了3个尺寸图片。
sizes
用来表示尺寸临界点,用媒体查询定下图片的实际宽度。
和之前的例子类似除了框架的不同宽度的变化。它允许你集中精力对付更小的宽度。
<source>
<img>
<source>
元素上总是被服从的<img>
的<source>
会被使用,所以顺序很重要<source>
,则<img>
被调用<img>
必须出现在<source>
后面<source>
不支持src
,但是支持srcset
一旦<source>
或者<img>
被选中,srcset
和sizes
属性就像之前的例子一样解析。
艺术指导
:剪裁图片内容来适应特定环境,任何时候我们裁剪或是修改图片来适应一个断点(而非简单缩放),都是一种艺术指导。可以看出来,艺术指导比之前的
srcset
+sizes
又多了一层维度:source
的媒体查询。
<picture>
元素在Chrome、Firefox和Opera中兼容良好,在其他浏览器可以回退到<img>
。而下一代的Edge也可能会支持。
--
这个方法可以让你有更优化的方式去提供给支持它们的浏览器。
<picture>
<source type="image/webp"
srcset="snow.webp">
<img src="snow.jpg">
</picture>
type
属性是mime
类型type
和media
、srcset
甚至是sizes
取创建一些惊奇的东西它在Chrome、Firefox和Opera上兼容良好,其他浏览器还可以回退到<img>
。
x
方式,但是这种方法有缺点:
1x
、2x
、3x
屏幕,这样 iPhone 可能加载一张2x
的图片,甚至以后还会加载出3x
、4x
的图片!w
方式就没有上面的问题了,一切都交给浏览器自动判断自动加载w
方式多了一层维度,可以更加精准地加载图片希望上面的文章能够对各种用例起到参考作用,你可以继续看看下面的文章:
与本文结构类似的文章:
补充文章:
我们在做移动端开发的时候经常遇到这样的需求:界面背景要刚刚好在整个屏幕中,不能超出屏幕,而且肯定还有一些元素要固定在界面中某个位置。
比如这样的设计图(720 x 1280
的尺寸),我们不仅仅要这个背景不超出屏幕,而且城堡里的图标要不偏不倚地在城堡中的那个位置。
这样分析一下我们就知道了我们要解决的问题:
现在一般的做法:整张图片当做背景,background-size
为100% 100%
,页面元素百分比绝对定位。
狠狠戳demo
从demo可以看出元素定位的问题用 absolute
+ 百分比
解决了 ,但是背景变形了(把.wrap
的background-size
值改成contain
试试)。为了不让这种变形更加夸张,给.wrap
添加了 min-width
和 max-width
。
这个方法还可以优化一下:
188kb
了。可以把背景图切成几分铺在底层,demoimg
引入,然后 absolute
定位如果允许背景图片一点点地变形以及限定尺寸范围,那么使用上面的方法简单、迅速,很多情况下也都是这么做的。
今天介绍的是一种新方法,背景不会变形且定位准确。
最近在做一个新页面的时候,我不满足于上面的方法,就想看看有没有别的解决办法。要图片在容器中不变形的最好办法就是 background-size:contain
了。看看我们现在的进展,这个页面完美适应窗口,且不会变形。那怎么让元素定位在不确定 contain
以后宽高未知的背景图上呢?
谁说宽高未知的?在我之前的文章CSS Contain&Cover的数学公式里介绍过,之前一直以为没什么用的结论可以在这里用上。
参考上面的文章,我们有了这样的思路:viewport
在这里就是 window
,image
在这里是 .container
。然后加上这个结论(contain
部分)我们不难得算出背景图在 contain 以后的宽、高。
在之前的基础上,添加计算背景图宽、高的逻辑:
var $appContainer = $('.app-list'),
$window = $(window),
winWidth = $window.width(),
winHeight = $window.height();
var rWindow = winWidth / winHeight,
rContainer = 720 / 1280,
scale = 1;
if(rWindow<=rContainer){
var height = winWidth / rContainer;
$appContainer
.width(winWidth)
.height(height)
}else{
var width = winHeight * rContainer;
$appContainer
.height(winHeight)
.width(width)
}
还要在样式上保证 .app-list
是居中的:
.app-list{
position: absolute;
left:50%;
top:0;
-webkit-transform:translate(-50%,0);
transform:translate(-50%,0);
}
这样就可以看到,无论窗口怎么变化 .app-list
始终和 .container
的背景图贴合在一起:
狠狠戳demo
还剩一个定位问题了,很简单,这里就只做了两个 icon
的定位。查看完整demo
至此,你可以随意改变窗口的大小,而页面一直都会保持在整屏中,而且不会变形,其中的元素也可以定位准确,基本上满足了整屏页面的所有问题。
其实,整屏页面就只有两个关键点:背景图片不变形 、页面中元素位置固定,你只要能做到这两点就可以了。但是,你也能看到我们用上面的办法做出的效果两侧会有突兀的背景露出来
我们白忙活一场么?看下图:
背景 contain
以后不就是这两种情况么?
图1是我们现在的情况,图2是背景的宽高比小于容器的宽高比的情况。可以想象,无论什么样的背景,我们在使用了这种方法后要么露出左右的背景,要么露出下面的背景。
虽然我们解决了 背景图片不变形和页面中元素位置固定问题,但是这个问题又在困扰着我们,所谓『鱼和熊掌不可兼得』。
这里介绍的方法其实不是PM们想要的,他们可能想要第一种方法的效果。但其实是设计图的影响,本文demo的设计图其实不适合这种方法。现在看一个适用于这种方法的demo,这种设计恐怕是第一种方法所不能做到的。因为这个设计图的主体是一个手机,所以绝对不能变形,而且就算有背景在两侧或上下露出来,也不影响整体效果。
这里只是给大家多一种思路,具体问题还需要具体对待,不能一概而论。
background-size
的方法今天发现一个库pageResponse就是用这种思路来做 cover
、contain
布局的。
和上文介绍的思路一样,你只要一次定位,终生位置不变。
要搞懂移动端的适配问题,就要先搞明白像素和视口。
在移动端给一个元素设置 width:200px
时发生了什么?这里的px
到底是多长呢?像素是网页布局的基础,但是我们一直在用直觉使用它。
其实存在两种像素:
1. 设备像素
屏幕的物理像素,任何设备屏幕的物理像素的数量都是固定不变的,单位是pt
。
2. CSS像素
在CSS、JS中使用的一个抽象的概念,单位是 px
。
顺便说下,CSS像素也可以称为设备独立像素(device-independent pixels),简称为
dips
,单位是dp
。
那么,我们现在再来说说一个元素 width:200px
以后会怎么样。这个元素跨越了200个CSS元素,200个CSS元素相当于多少个设备像素取决于两个条件:
这两方面后面再解释,先梳理一下手机硬件之间的关系,注意这里使用的都是物理像素。
以 iPhone5 为例,我们已知的是:
1136pt x 640pt
4英寸
326dpi
ppi
,单位是 dpi
(dot per inch)。这里指屏幕水平或垂直每英寸有326个物理像素。原则上来说,ppi越高越好,因为图像会更加细腻清晰。ppi
是可以通过 分辨率 和 屏幕尺寸 计算得到的:
这个网站列出了很多设备的分辨率和屏幕尺寸,并且计算了ppi。
桌面浏览器中,浏览器窗口就是约束你的CSS布局视口(又称初始包含块)。它是所有CSS百分比宽度推算的根源,它的作用是CSS布局限制了一个最大宽度,视口的宽度和浏览器窗口宽度一致。
但是在移动端,情况就很复杂了。
一个没有为移动端做优化的网页,会尽可能缩小网页让用户看到所有东西。这是我的手机查看博客园的样子,你也可以在Chrome中以移动开发模式看到。
浏览器厂商为了让用户在小屏幕下网页也能够显示地很好,所以把视口宽度设置地很大,一般在 768px ~ 1024px 之间,最常见的宽度是 980px。
所以,在手机上,视口与移动端浏览器屏幕宽度不再相关联,是完全独立的,这个浏览器厂商定的视口被称为布局视口。
布局视口我们是看不见的,只知道网页的最大宽度是 980px
,并且被缩放在了屏幕内。
可以这样设置布局视口的宽度:
<meta name="viewport" content="width=640">
媒体查询与布局视口
700px 指的是布局视口的宽度
@media (min-width: 700px){
...
}
document.documentElement.clientWidth/Height
返回布局视口的尺寸
视觉视口是用户正在看到的网页的区域,大小是屏幕中CSS像素的数量。
window.innerWidth/Height
返回视觉视口的尺寸
布局视口明显对用户是不友好的,完全忽略了手机本身的尺寸。所以苹果引入了理想视口的概念,它是对设备来说最理想的布局视口尺寸。理想视口中的网页用户最理想的宽度,用户进入页面的时候不需要缩放。
现在讨论所谓的『最理想的宽度』到底是多少?其实,如果我们把布局视口的宽度改成屏幕的宽度不就不用缩放了么。可以这样设置告诉浏览器使用它的理想视口:
<meta name="viewport" content="width=device-width">
定义理想视口是浏览器的事情,并不能简单地认为是开发者定义的,开发者只能使用。
screen.width/height
返回理想视口的尺寸,有严重的兼容性问题---可能返回两种值:
Screen size tests和Understanding viewport可以测试你的设备的screen.width
值,同一设备的不同浏览器返回的值可能是不一样的。这一情况主要发生在默认浏览器和下载浏览器(如UC、Chrome)之间。
默认浏览器是安卓系统内置的浏览器,长下面那个样子。而且它使用的是Webkit而不是Blink。只有在更新安卓系统的时候才能更新它。直到安卓4.3,Google不再更新。
而下载浏览器都返回的是理想视口尺寸。
缩放是在放大或缩小CSS像素,比如一个宽度为 200px 的元素无论放大,还是200个CSS像素。但是因为这些像素被放大了,所以CSS像素也就跨越了更多的设备像素。缩小则相反。
缩放会影响视觉视口的尺寸
页面被用户放大,视觉视口内CSS像素数量减少;被用户缩小,视觉视口内CSS像素数量增多就行了。这个道理应该是不难想的。
用户缩放不会影响布局视口
注意,这是『用户缩放』,后面会说开发者设置缩放的情况
我们在开发者工具中可以在这里查看缩放比例:
这里的 0.3 是相对于理想视口的。
在下载浏览器中,可以这么算(理想视口与视觉视口的比):
zoom level = screen.width / window.innerWidth
<meta name="viewport" content="user-scalable=no">
<meta name="viewport" content="initial-scale=2">
使用initial-scale
有一个副作用:同时也会将布局视口的尺寸设置为缩放后的尺寸。所以initial-scale=1
与width=device-width
的效果是一样的。
解决各种浏览器兼容问题的理想视口设置
<meta name="viewport" content="width=device-width,initial-scale=1">
在谈到像素的时候,讲到除了缩放,屏幕是否为高密度也会影响设备像素和CSS像素的关系。
在缩放程度为100%(这个条件很重要,因为缩放也会影响他们)时,他们的比例叫做设备像素比(device pixel ratio):
dpr = 设备像素 / CSS像素
可以通过JS得到: window.devicePixelRatio
设备像素比也和视口有关:
dpr = 屏幕横向设备像素 / 理想视口的宽
这一篇介绍了移动端适配需要掌握的知识,先说明了移动端存在的两种像素,然后介绍了三种视口,由缩放对视口的影响引入理想视口,最后说明设备像素比。下一篇介绍现在市面上的适配方案。
下面这些文章可能也会对你有帮助:
安卓开发中,adb是很重要的调试手段。其实,对于前端来说,adb工具也是学习一下的,因为它确实很方便。
比如,我在手机助手中,调试一个安装APP的页面,这个过程中就要反复下载应用安装,然后退出页面卸载,很麻烦,使用adb就只要一条命令就可以卸载应用了。再比如,APP中一般会缓存数据,这样给我们调试也带来很多麻烦,如果不使用adb清除缓存的话,就必须去设置里清除,太麻烦了。
所以来学学adb吧
安装 JDK
下载 SDK
解压 SDK 压缩包,然后进入 android-sdk-macoxs/tools
,双击 android
启动 Android SDK Manager
,安装 build tools
在自己的目录(home directory)中创建 .bash_profile
:
cd ~
touch .bash_profile
在 .bash_profile
中写下:
export PATH=${PATH}:/Users/xxx/android-sdk-macosx/tools
export PATH=${PATH}:/Users/xxx/android-sdk-macosx/platform-tools
路径是不固定的,指向 android-sdk-macosx
目录即可
执行
source .bash_profile
输入 adb 回车,就可以看到adb界面了
只说我最常用的两条命令
卸载应用
adb shell pm uninstall com.example.MyApp
com.example.MyApp
是应用的包名
清除缓存
adb shell pm clear com.example.MyApp
更多的命令请看这里
今天的文章很水,就这样吧
最近两周在做一个给内部使用的桌面软件,使用的是 Electron,现在就这次使用 Electron 的经验,记录一下。
什么是 Electron ?简介说的很清楚:使用 JavaScript, HTML 和 CSS 构建跨平台的桌面应用,就是用前端技术来开发桌面软件。
可以在这里看到用 Electron 开发的应用,其中最知名的就是 VSCode 了。所以,已经有那么多公司在使用这项技术了,我们也可以放心使用。
如果想直接体验用前端技术开发桌面软件,可以按照官方的示例来学习。
如果要在正式项目中使用,可以安装 electron-forge,这是一个成熟的脚手架,集成了打包、发布功能,还可以选择模板(如React、Vue、Angular 等)作为渲染层框架。
建议在熟悉 Electron 技术后自己基于使用的框架调整为自己最舒服的架构。我在实际使用中,发现 React 模板没有安装 redux,以及不支持一些ES6语法,所以自己又基于 electron-forge 做了一套适合自己的模板: https://github.com/riskers/electron-react-boilerplate。
目前,我对 Electron 的理解是:
electron = chromium + Nodejs + Native api
其中:
只要你是前端,并且看懂Electron 应用结构,就已经入门了。
Electron 分为主进程和渲染进程,这点很像 Chrome 插件的开发,渲染进程只是一个界面,但是有一个主进程常驻在程序中。
electron文档汉化做得很好,其实已经不用多说什么了。作为前端,chromium 渲染层和 Nodejs 是我们最熟悉的,剩下的就是根据需要去查怎么调 Native API 了。
2018.09.21 updated
最近对 Electron 有了更新的认识,Electron 在通信上有两种方案:
Electron 存在主进程和渲染进程,是前端们之前从来没有处理过的,需要好好研究。
最近开始写 Python,对于一直写 JS 的我来说,十分不习惯 Python 的版本管理,一个项目的依赖装在了全局。强迫症的我就开始找 Python 的版本管理方案。
install
curl -L https://raw.githubusercontent.com/pyenv/pyenv-installer/master/bin/pyenv-installer | bash
.zshrc
添加:
export PATH="$HOME/.pyenv/bin:$PATH"
eval "$(pyenv init -)"
eval "$(pyenv virtualenv-init -)"
安装不同版本 Python
CFLAGS="-I$(brew --prefix openssl)/include -I$(xcrun --show-sdk-path)/usr/include" \
LDFLAGS="-L$(brew --prefix openssl)/lib" \
pyenv install -v 3.5.0
查看版本
pyenv versions # 查看系统当前安装的python列表
pyenv version # 查看系统当前使用的python版本
设置版本
pyenv global # 设置全局 Python 版本,将版本号写入~/.pyenv/version文件
pyenv local # 设置当前项目 Python 版本,将版本号写入当前目录下的.python-version文件
安装:
python3 -m pip install --upgrade --force-reinstall pip
pip3 install pipenv
.zshrc
添加:
export PIPENV_VENV_IN_PROJECT=1 # 在每个项目的根目录下保存虚拟环境目录.venv
pipenv --two/three # 创建一个虚拟环境
会生成 Pipfile
和 Pipfile.lock
,类似 package.json
和 yarn.lock
。还支持 pipenv --python 3.7.0
这种指定版本的用法,如果本地没有这个版本,就会安装这个版本。
其他用法:
pipenv install [package]
: 安装模块pipenv graph
: 列出项目所有依赖pipenv shell
: 进入 shellpyenv 安装多版本 Python 后,用他切 local 版本,其他包版本的事交给 pipenv。
pyenv install -v 3.5.0
cd envtest
pyenv local 3.5.0
: python -V
: 3.5.0 (设置成功)pipenv --python 3.5.0
pipenv shell
-> python -V
: 3.5.0 (设置成功)你在做移动web开发的时候是不是只是在Chrome下开启移动模式,然后就啪啪啪闷头敲代码了?如果你平时只是做做宣传页,Chrome的移动模式可能就能满足你。但是现在越来越多的应用采用Hybrid的开发方式,这样的话就可能在web页面上调用webview注入的函数,那么,这个页面在Chrome上只会报错,因为我们不在webview里,根本没有注入的那些函数。
以我现在做的项目为例,要在页面里判断在客户端有没有登录,可以这样写:
var isLogin = AndroidWebview.hasLogin() ;
结果可想而知,AndroidWebview是客户端在webview里注入的方法,这里当然会报错了。
这种情况怎么开发呢?回顾一下以前的两种办法 :
真机 + Chrome inspect :Chrome 版本必须高于 32,其次你的测试机 Android 系统高于 4.4
Chrome://inspect
即可找到你的设备可以看到,第一个记录是手机里的浏览器的;第二个是记录是手机助手里的webview。
真机 + weinre : 在你本地创建一个监听服务器,并提供一个JS脚本,需要在需要测试的页面中加载这段 JS,就可以被 Weinre 监听到,在 Inspect 面板中调试你这个页面。
这两种办法都需要真机测试,你可以想像一下你在开发、调试时的流程:
然后你的手不停地在键盘和手机之间切换,多么痛苦。后来,我遇到了Genymotion
。
这是一款安卓模拟器,有了它我们可以在电脑上开启一个安卓机。具体使用我就不细说了,很简单请自行搜索。
这是我在模拟器上安装的手机助手:
而且使用 Chrome inspect
是直接可以调试模拟器中的webview的:
这样,我们就可以不用手忙脚乱地写代码、看手机了,一切都在PC上调试。但是我们在模拟器上看到的是线上代码,我们加一个新功能还要发布代码才能看到效果?
幸好有Charles
这样的工具(Windows
下请使用Fiddler
),Charles
会在本地开启一个代理服务,默认接口8888
。通过这个代理,模拟器上的请求会被转移到电脑上,我们可以任意地去替换请求文件让我们更加方便地调试页面。
Proxy Settings
- HTTP Proxy
- Proxies
- HTTP Proxy
中设置
因为 Charles
只会监听全局和Firefox,为了能监听Chrome,使用Proxy SwitchyOmega插件,增加一个情景模式:
在这个情景模式下,我们就可以抓到在Chrome里的数据了:
注意:Charles默认是不支持https的,我们选择 设置
- Proxy Settings
- SSL
,选中 Enable SSL Proxying
。然后在 Locations
里填写要抓包的域名和端口,点击 Add
,Host
填写域名,如 www.baidu.com ,port
填 443
。具体参考最后的文章。
别忘了,使用Charles的初衷是让我们可以用本地的文件替换线上文件,不用每次修改都要发布。
在Genymotion中,Settins
- Network
(port选9999
是因为我之前在Charles中设置的是9999) :
在开启的模拟器中,设置
- WLAN
- 长按2秒
- 修改网络
,代理设置
改为手动
,主机名为10.0.3.2
,端口为9999
,和上面一致。
然后在模拟器中打开webview页面就可以看到所有请求了
右键保存源文件到本地,然后添加一行alert代码 。
在请求上右键,选择 Map Local
选择刚才修改过的文件
重新载入页面 :
这样,我们利用模拟器+Chrome+Charles
就可以完美开始、调试webview页面了,模拟器当做手机,Chrome insepct
调样式、接口、查看数据,利用Charles
映射本地文件直接查看效果。
至于iOS上的webview调试,模拟器+Safari+Charles
应该也是一样的。我没有试过,试过的人请告知。
今年用一个字表达,就是「淡」,就像一杯白水一样。
今年算是工作这么多年来,出去玩的最多的一年了,毕竟再不用随心飞要过期了。
今年去了南京、昆明、大理、重庆、武汉、上海、三亚,还游了三峡:
因为出去玩得多了,所以看书的时间多了,大多是在睡前和飞机上看的。
看了看读书记录,今年前半段开始密集地看人物传记类书籍:
一贯地爱读历史类和政治类书籍:
没用的技术书:
其他:
还有基本关于量化交易和区块链的书,要么是草草翻过,要么是写得不好,就不列出来了。
每次心态有起伏的时候,就会抓来几本书开始读,这本读腻了,就换一本,时间就消磨过去了。
今年算是艰难的,艰难守住去年的盈利,不谈也罢。
今年不再着眼于工作中的技术,放眼于区块链和量化交易两个点,他们都是计算机技术和其他学科的交叉点,我理解。
区块链的学习应该是夏天开始的,之前总结过,不再赘述。
至于量化交易,刚刚开始学,还没有入门,里面的统计学、金融知识太多,怎么入门,明年再说吧。
2021 计划,目前基本都做到了, 果然计划还是定的细一点比较好实现。
学习上,今年开始全面使用 Notion,不得不说,现在已经离不开它了,实在太方便了。它是我的知识库,是网状的,让我的知识可以铺展开。
今年我的心态有了很大的变化,对生活,对工作。
这一年我无数次问自己,三年前做的决定是不是正确的 —— 因为创业失败的打击让我决心去做后端,三年过去了,学到了不少东西,但是不知道是不是变得更好了。
今年在工作上我比较消极,在一个不那么在乎技术的位置上,其实待得也不怎么舒服。所以,年后我一定会离职了,毕竟到 3 月就待了三年了。我从来没有在一家公司超过三年,这次也不能例外。
今年互联网行业风向一转,仿佛要把前些年赚的钱全部吐出来。整个世界都很浮躁,不过我越来越平静,逐渐地认识到自己能做到什么,做不到什么,也知道自己想要的是什么生活。
「天高任鸟飞,海阔凭鱼跃」吧
今年列 TODO 的比较少,只有这些。
祝自己新的一年顺利吧!
因为 GFW 的存在让我们一直无法畅快地浏览网站、使用软件、开发程序,现在就这几个方面一一展开说明。
外网服务器
你可以自己搭一个,也可以买一个服务,建议买一个,省事很多。我是在 枫叶主机 上买的,速度还不错,价钱也合适。
ShadowsocksX-NG
下载地址: https://github.com/shadowsocks/ShadowsocksX-NG
不会使用的同学可以查看 https://help.fengye.la/replay/sub_mac.html 来学习。
下载地址: https://chrome.google.com/webstore/detail/padekgcemlokbadohgkifijomclgjgif
可以在浏览器中方便地针对某些网站使用 SS 。
下载地址: https://www.proxifier.com
它可以让你自己添加软件到 rules 中,这样你的这些软件就可以使用 SS 来科学上网了。比如 Telegram
、Signal
、 Terllo
等就可以畅快地使用了。
比较麻烦,还要关闭 Mac 的 SIP:
csrutil disable
csrutil status
自己编译安装:
git clone [email protected]:rofl0r/proxychains-ng.git
cd proxychains-ng
./configure
make && make install
cd .. && rm -rf proxychains-ng
修改配置,配置文件在 /etc/proxychains.conf
:
# proxychains.conf VER 4.x
strict_chain
chain_len = 1
proxy_dns
remote_dns_subnet 224
tcp_read_time_out 15000
tcp_connect_time_out 8000
localnet 127.0.0.0/255.0.0.0
[ProxyList]
socks5 127.0.0.1 1086
然后就大功告成了,试试:
最近在玩 Go,安装 golang.org/x 下的安装包的时候发现
proxychains4 go get
竟然不能正确地安装,原因是 proxychains 是通过修改动态加载库来实现代理,但go是静态编译的,因此,proxychains无法代理go。
幸好 ShadowsocksX-NG 提供了 http 代理,在.zshrc
添加:
alias p-on='export http_proxy=127.0.0.1:1087;export https_proxy=$http_proxy'
alias p-off='unset http_proxy;unset https_proxy'
在终端下,p-on
可以打开 http 代理,p-off
可以关闭 http 代理
现在使用 clashX 就够了
本文发布在使用 redux 管理 flutter 应用数据,后续一直会更新,感兴趣的可以关注一下。
最近在学 flutter,边学边记录了一本 写给前端看的 flutter 笔记,感兴趣的小伙伴可以一起来完善他。
redux 是什么?简单来说,就是为了解决 UI 层状态管理的方案,如果不熟悉,请先看文档学习一下,今天的重点不是学习 redux,而是直接用 redux 管理 flutter 的状态。
首先,flutter 和 react 真的太像了,连状态管理都有 redux 方案:
flutter | react |
---|---|
redux.dart | redux |
flutter_redux | react-redux |
redux_thunk | redux-thunk |
另外,甚至还有 redux-epics 能够搭配 rxdart 对标 redux-observable。对于 rxjs 熟悉的人应该也能够很快地开始 rxdart 了,再次证明知识是相似的,技能也是能够越学越快的。
为了不混淆大家,接下来我把 react 中的 redux 称为 react/redux
, flutter 中的 redux 称为 flutter/redux
。
现在我们用 flutter 写一个简单的 redux 例子:
完整代码在 https://github.com/riskers/flutter_notebook_redux ,有过 redux 经验的应该能看明白。使用到了:
项目架构完全是之前按照我之前在 React 中的经验来做的
有几点需要注意:
为了方便,可以在 state 中以一个静态方法初始化:
static AppState initialState() {
return AppState(
count: 0,
clickCount: 0,
);
}
然后在入口文件中调用:
final store = Store<AppState>(
reducers,
middleware: [thunkMiddleware],
initialState: AppState.initialState(), // 调用
);
在 react/redux 中我们习惯这样写 reducer:
const reducer = (state={
loading: false,
data: [],
}, action) => {
switch(action.type) {
case CONST.FETCH_GITHUB_SEARCH_USER_LOADING:
return {
...state,
loading: true
}
case CONST.FETCH_GITHUB_SEARCH_USER_SUCCESS:
return {
loading: false,
data: action.payload.items
}
default:
return state
}
}
重点是 ...state
这样的解构写法,实际是浅复制了 state 对象,而在 dart 中没有这么方便的方法,比如 reducers.dart 中的 copyWith
:
AppState counterReducer(AppState state, dynamic action) {
switch (action) {
case INCREMENT:
return state.copyWith(count: state.count + 1);
case DECREMENT:
return state.copyWith(count: state.count - 1);
}
return state;
}
copyWith
是我们自己写的扩展方法:
AppState copyWith({count, clickCount}) {
return AppState(
count: count ?? this.count,
clickCount: clickCount ?? this.clickCount,
);
}
??
是条件表达式,count: count ?? this.count
表示 copyWith 只要没传进来 count 就把 AppState 实例的 count 值赋给 count,然后再实例化一个 AppState 对象。
参见其他的复制对象的方案
比较有特点的就是 redux_thunk 的异步 action 写法:
ThunkAction asyncIncrement() {
return (Store store) async {
await Future.delayed(Duration(seconds: 3)); // 延迟 3 秒
store.dispatch(INCREMENT);
};
}
async / await 是不是很熟悉?
StoreProvider 很简单,就是在根组件中挂载 store:
@override
Widget build(BuildContext context) {
return StoreProvider<AppState>(
store: store,
child: MaterialApp(
theme: ThemeData.dark(),
home: Home(),
),
);
}
而 StoreConnector 就比较麻烦了,需要先定义一个 ViewModel:
class AppStateViewModel {
final AppState state;
final void Function() onClick;
AppStateViewModel({
this.state,
this.onClick,
});
}
ViewModel 中规定你要在这个组件中使用的 Store 中的数据
class AddButton extends StatelessWidget {
@override
Widget build(BuildContext context) {
return StoreConnector<AppState, AppStateViewModel>(
converter: (store) {
return AppStateViewModel(onClick: () {
store.dispatch(INCREMENT);
store.dispatch(CLICK);
});
},
builder: (context, vm) {
return Padding(
padding: const EdgeInsets.all(8.0),
child: FloatingActionButton(
onPressed: vm.onClick, // trigger!
tooltip: 'Increment',
child: Icon(Icons.exposure_plus_1),
),
);
},
);
}
}
StoreConnector 有两个方法:
converter
: 使用给定的转换器函数将存储转换为 ViewModel,并将 ViewModel 传递给 builder 函数builder
: 承接 converter
返回的数据来使用以 AddButton 组件为例,converter 返回 onClick 函数来触发 dispatch, builder 承接到这个函数,这样就能在发起 dispatch,走 redux 的流程了。
本文只介绍 redux 在 flutter 中的应用,如果你对 React 很熟,你应该能明白 redux 的意义,除了有个全局 store 外,深层组件之间通信也不是问题了,对于 flutter 这种框架也是很有意义的。
当然,直接使用 redux 可能会比较重,这和 react 是类似的,小应用的话可以用全局总线来处理,甚至你可以一个界面就是一个 statefulwidget,直接都用 setState 来改变数据渲染界面,这样每个界面都是独立的数据了。
总体上来说,在 flutter 上使用 redux 还是很麻烦的,好在官方在 2019 roadmap 中提到了要提供最佳实践,让我们期待吧!
这是我在13年初写的文章,当时懵懵懂懂写下了自己对JSONP
的理解。
提到JSONP,我当时在网上找了无数帖子也没有看懂它。那些文章大同小异,都是讲到JSONP原理以后就戛然而止,把我们这些初学者搞得云里雾里。所以,写下这篇文章,希望对大家有帮助!
回答这个问题之前,大家先想想什么是AJAX,JSONP就是一种能够解决AJAX办不到的事情而存在的一种取数据的技术。什么事情是AJAX办不到的呢?就是跨域!
跨域:顾名思义,就是当前网页的地址和我们要取的数据地址不在一个域下。这是因为浏览器都有一个“同源策略”— 两个页面的域名必须在同域的情况下,才能允许通信。
怎么才算一个域呢?
相同域名,相同端口,相同协议(因为不是这里的重点,大家可以请教Google)
“同源策略”的意义:“同源策略”有效地阻止了一些危险行为,比如你进入www.aaa.com
,同时浏览器又开了一个www.bbb.com
,如果这个www.bbb.com
是一个木马网站,在没有“同源策略”的情况下,它就可能嵌入一些代码,来取得你在www.aaa.com
的信息(因为这时两个页面是可以通信的) 。而正是因为有了“同源策略”,刚才可以通信的情况才不会发生。
“同源策略”带来的麻烦:上面的例子是我们在不知情的情况下,保护我们的网络安全的,但如果我们就是要让www.aaa.com
取得www.bbb.com
上的数据,行不行呢?答:不行!还是因为”同源策略”!我们想从自己信任的网页上取得数据都不行,这可怎么办呢?
在需要跨域通信的岁月里,一些卓越的前端工程师们想到了这个”作弊”的办法来逃避”同源策略”。”同源策略”虽然很厉害,阻止了一个页面到另一个页面的通信,可是src
指向的路径它放过了,提到src
,大家是不是想起了<script>
?对,JSONP就是利用”同源策略”的这一”漏洞”来进行”作弊”的。(其实有src
属性的不止有<script>
,还有<img>
和<iframe>
,而<iframe>
也是能够运用JSONP
的)。
下面看看JSONP
的原理:
JSONP
:JSON with Padding
,JSON
大家这都知道,是一种数据通信格式,而”Padding”是什么意思,别急,往下看就知道了。
我们先举一个简单的例子:
www.aaa.com
中:
<script type="text/javascript" src="http://www.bbb.com/abc.js"></script>
<script type="text/javascript">
function abc(json){
alert(json['name']);
}
</script>
www.bbb.com/abc.js
中:
abc({'name':'risker','age':24});
页面会弹出risker
,有感觉了么?
JSONP是这样工作的:像前面所说的那样,我们可以取到www.bbb.com/abc.js
,里面是一个abc
函数,这个函数也会被加载到www.aaa.com
。加载完成后,就应该执行abc
了,然后我们在www.aaa.com
定义abc
函数,这个函数里写一些处理数据的语句。这样其实就简单地实现了跨域访问数据了,这也就是JSONP
的原理了。而JSON with Padding
的意思,就是abc(json)
中的json
:
abc({'name':'risker','age':24})
。
这个JSON对象被包在abc这个函数中当作参数来被处理,而JSON with Padding
这个词很形象地形容了这个过程。
在网上能找到的JSON基本只是介绍到这里就完了,但是这让初学者看不到一个实实在在的例子。所以下面才是这篇文章和其他网上介绍JSON文章不一样的地方,我带给大家一个例子!
大家一定对百度的自动搜索框有印象,它就是一个JSONP的实例:
先查看demo
分析一下:
分析数据地址回顾上面的例子,我们首先要知道数据的来源地址,就是上面的www.bbb.com/abc.js
里的数据。在Chrome中查看Network。然后随便在搜索框里输入点什么,比如s
,观察Network里是不是多了东西,点开它,就是我们输入“s”后传回的数据了:
这个地址是 http://suggestion.baidu.com/su?wd=S&p=3&cb=window.bdsug.sug&from=superpage&t=1365392859833
, 我们分析一下,wd
后面是s
,那就可以断定百度定义wd
是搜索的关键字,cb
是一个回调函数,其他的对我们就不重要了。回调函数是我们取到数据要后执行的函数,相当于我们上面的abc函数。它是可以自己取名的。像http://suggestion.baidu.com/su?wd=S&p=3&cb=succ&from=superpage
表示取到数据后执行succ函数:
这样,我们的数据就包在了succ函数里做一个参数,再次证明了JSON with Padding 的原理。
做<script>
标签,其src指向数据地址:这是要动态生成的,不能把地址写死,要不然取到的都是一样的数据了。所以我们要动态生成<script>
,动态指定src
属性:
var oScript = document.createElement('script');
oScript.src = 'http://suggestion.baidu.com/su?wd='+oTxt.value+'&p=3&cb=succ&from=superpage';
document.body.appendChild(oScript);
不要以为这样问题就解决了,F12一下,就看到生成了好多<script>
!这是因为我们每输入一个字符就动态生成一个<script>
,造成了代码冗余!解决一下:
if(oScript){
document.body.removeChild(oScript);
}
好,这样,我们的搜索框效果就做好了,因为主要讲JSONP部分的工作原理,就不做成百度下拉框那样了,大家可以自己去布局。当然,真正的百度搜索框还要在此基础上涉及事件的冒泡取消等等,就不是这里的重点了,不做阐述。
<script>
的src
属性不受“同源策略”的控制,“作弊”般地巧妙地逃过了浏览器的这一限制。<script>
标签,其src
指向我们的数据地址。地址后面附带一个回调函数(名字一般是callback或者是别的什么,就看后台给我们的是什么了,函数名是我们起的)。然后,声明这个回调函数。这样,只要一引入上面的<script>
标签,就相当于执行了那个回调函数。2017年最大的变化就是换了一家公司,从 360 来到阿里巴巴。来到阿里,证明了自己这两年自己的努力没有白费。来到阿里半年后,逐渐地有了从业务上选型技术的观点:无论多牛逼的技术,只要不符合自己业务的需求,只能放弃。 现在保持之前5年的习惯,写一篇关于自己这一年的总结。
来到阿里做一个前端,算是我的一个心结。因为当年毕业找工作的时候,就是想去阿里,可惜当时人家不要我。来这一年,对 React 技术栈更加深入,MobX、rxjs也有过调研。而在团队中,我是负责工程化的,这一年来,做了几个工程化的包,现在在做UI自动化测试的东西。流水帐记一下,主要就是:
2017年年初的时候,给自己定的计划是:
现在来看,只有第一点做到了,不过还有一些其他的额外收获,比如:
今年我学过的语言有 C / OC / Swift / Haskell,不过都没有很深的开发经验,说白了就是没有代码量的累计,所以都忘了。
这样看来,今年有些混,没怎么学习新知识。明年再接再厉吧。
现在博客 star 数是 832,watch 数是 246,比去年多了好多,谢谢大家关注。
今年只写了几篇博客:
数量不多,不过都是干货,也都是自己实践、学习后写出来的。而且大量的思考,我都没有写在博客里,而是写在了简书和gist里。
今年只给团队分享了 CSS Modules: https://riskers.github.io/share/share/css_modules.htm
今年读数量减少很多,只有16本:
技术类
非技术类
今年一共去了4次杭州:
夏天的那次绕着西湖走了一圈,很累,很美。
恩,就这样吧。
本文介绍现在最常用的前端工程化工具:
EditorConfig 是一套用于统一代码格式的解决方案,官网介绍很简单。简单来说,EditorConfig 可以让代码像是在同一个编辑器里打开的。
在项目跟目录下新建 .editorconfig
配置文件,配置项目有:
配置文件的格式如下:
root = true
[*.js]
charset = utf-8
end_of_line = lf
indent_size = 2
indent_style = space
insert_final_newline = true
trim_trailing_whitespace = true
这样,只要团队内都使用这样的一份 .editorconfig
,再根据我们用的编辑器去安装相应的 EditorConfig 插件就行了。主流编辑器都支持,比如:
前端检验工具有:
所以,很明显 ESLint 是现在大家首选的检验工具。
使用很简单:
npm install --save-dev eslint
{
"scripts": {
"lint": "eslint src --ext .js"
}
}
执行 npm run lint
就会检查 src 目录下的 .js
文件。
.eslintrc
:{
"extends": "airbnb"
"rules": {
"semi": ["error", "always"],
"quotes": [2, "double"]
}
}
npm run lint
即可eslint-config-airbnb 是根据 airbnb 规范所制作的 ESLint 配置。
git hook是你每次进行git相关操作的时候,都会触发的一些与该操作对应的事件,并运行相应的脚本代码,实现例如格式化提交信息,发送提交邮件,运行单元测试等功能。
git hook分为client side和server side,其中,client side的git hook是在创建git项目的时候,就会自动产生,并且保存在名为 .git/hooks
目录下:
可以看到有很多种 hook ,有 .sample
后缀表示还没有开启。我们可以做个试验,修改 .git/hooks/pre-commit.sample
为 .git/hooks/pre-commit
,并且修改内容为:
之后,我们执行 git commit
的命令时:
我们可以利用 git hook 做很多事情,比如 ESLint 代码,如果没有通过,就不允许 commit 。但是 git hook 是 需要 shell 编程脚本。
我们前端是不熟悉的,还好有 pre-commit 和 husky 帮助我们。下面以 pre-commit 为例,介绍怎么配置 hook:
{
"scripts": {
"lint": "eslint src --ext .js,.jsx",
"precommit-msg": "echo 'Pre-commit checks...' && exit 0"
},
"pre-commit": [
"precommit-msg",
"lint"
]
}
这样,我们就在每次 commit 之前依次执行 precommit-msg
和 lint
,如果 lint
出错,则不会成功 commit 。
这些都是约束团队编码的工具,能够帮助团队更好地协作,让整个团队的代码看起来像是一个人写的。
lint-staged 可以取到修改的文件,这样就可以只 lint 你本次修改的文件而不是所有文件:
// commit 之前得到修改的文件,然后进行 eslint 命令
"lint-staged": {
"src/**/*.{js,jsx}": "eslint"
},
"scripts": {
"precommit": "lint-staged",
}
再配合 VSCode 的 ESLint 插件,不要太舒服
上文已经过时,自从 prettier 出现后,已经可以代替 editorconfig 了,而且 prettier 还能集成进 eslint 。
最让我高兴的是,我最近发现 ESLint 通过 typescript-eslint-parser 可以检查 TypeScript 了,也就是说可以不用 tslint 了,这玩意当时让我很抓狂。
详细的不聊了,本来想更新一篇,但是看到 https://github.com/AlloyTeam/eslint-config-alloy 已经说得这么清楚了,就没有必要再制造水文了。
注: 这篇文章部分内容已经过时,vw 方案(见文末)已经足以适应各种场合,且兼容性很好。
上一篇介绍了像素和视口这些基本概念,现在接着移动端的适配方案。
推荐一篇文章:MobileWeb适配总结,里面说到的三种布局方法已经说的很详细,还分别做了demo,我就不做了,这里说说三种方案的原理以及我使用中的感受,希望各为互补,大家理解是最重要的。
之前做过PC页面的人聊的最多的就是『兼容』,这是因为浏览器之间的差异引起的,不再多说。而移动端是基本没有『兼容』的问题的,全是CSS3,简直不要太开心。可是『适配』问题随之而来。
什么是『适配』?做PC页面的时候,我们按照设计图的尺寸来就好,这个侧边栏200px,那个按钮50px的。可是,当我们开始做移动端页面的时候,设计师给了一份宽度为640px的设计图。那么,我们把这份设计图实现在各个手机上的过程就是『适配』。
那么,我们怎么开始呢?目前有三种方法:
这三种方法的核心都是视口的确定,现在以实现这个设计图为例说明。
这也是目前使用最多的方法,垂直方向用定值,水平方向用百分比、定值、flex都行。腾讯、京东、百度、天猫、亚马逊的首页都是使用的这种方法。
随着屏幕宽度变化,页面也会跟着变化,效果就和PC页面的流体布局差不多,在哪个宽度需要调整的时候使用响应式布局调调就行(比如网易新闻),这样就实现了『适配』。
原理
这种方法使用了完美视口:
<meta name="viewport" content="width=device-width,initial-scale=1">
这样设置之后,我们就可以不用管手机屏幕的尺寸进行开发了。
设计图、页面宽度、viewport width使用一个宽度,浏览器帮我们完成缩放。单位使用px即可。
目前已知荔枝FM、网易新闻在使用这种方法。有兴趣的同学可以看看是怎么做的。
原理
这种方法需要根据屏幕宽度来动态生成viewport
,生成的 viewport 基本是这样:
<meta name="viewport" content="width=640,initial-scale=0.5,maximum-scale=0.5,minimum-scale=0.5,user-scalable=no">
640 是我们根据设计图定下的,0.5 是根据屏幕宽度动态生成的。
生成的viewport告诉浏览器网页的布局视口使用 640px,然后把页面缩放成50%,这是绝对的等比例缩放。图片、文字等等所有元素都被缩放在手机屏幕中。
这个gif图说明了一切:
这也是淘宝使用的方案,根据屏幕宽度设定 rem
值,需要适配的元素都使用 rem
为单位,不需要适配的元素还是使用 px
为单位。
具体使用方法见使用Flexible实现手淘H5页面的终端适配
上文提供了sass和postcss的px2rem转换方法,我这里给出less的px2rem
。因为less不支持函数,所以需要安装插件 less-plugin-functions
,然后就简单了:
.function{
.px2rem(@px,@base:72px){
return: @px / @base * 1rem;
}
}
这样使用:
div{
width: px2rem(100px);
}
使用这个方法的库:
原理
实际上做了这几件事情:
rem
的大小,即给<html>
设置font-size
<html>
设置data-dpr
这么设置的好处是可以让不同设备的rem
或px
都显示一样的长度。
设置rem的意义在于得到一个与屏幕宽度相关的单位,本来vw
是最合适的,但是因为兼容性的问题,只能使用rem
来做。这样,让不同设备的rem
显示一样的长度。
vw是CSS3引入的单位,1vw = 1%窗口宽度
上面的布局我们可以这样:
html{
font-size: 屏幕宽度 / 10;
}
.btn{
width:8.75rem;
height:1.25rem;
}
这样,无论屏幕宽度是多少,.btn
都是相对于屏幕的这么宽,做到了适配。
这两个设置其实是干的一件事,就是适配高密度屏幕手机的px
单位。
.a{
font-size:12px;
}
[data-dpr="2"] .a{
font-size: 24px;
}
[data-dpr="3"] .a{
font-size: 36px;
}
而缩放是动态的,这样,不同设备下的px
显示一样的长度。
之前说过CSS像素和物理像素与缩放、dpr都有关系,这里说明:
在普通手机上,
.a
字体设置为12px;在dpr是2的手机上,
[data-dpr="2"] .a
字体为24px,又因为页面缩放50%,字体为还是12px
坦白说,我不觉得第一种方案能就称为『适配』方案,因为太笨了,只能做一些列表等简单排列的样式。
但是一些复杂的活动页的布局,用它可能就力不从心了:
这是我曾经做过的一个页面,『PK』要和左右两张图平行,而且下面的『不怒自威』、『义薄云天』和下面的战斗力位置都要固定,不能有差。如果用第一种方案,可能各个元素就要绝对定位,然后各种百分比来定位了。且不说计算麻烦,而且辛苦一番最后的结果尺寸是和设计图有出入的。
但是,第二种和第三种方案就绝对不会有这种情况,不会和设计图有差。再说第二种和第三种方案的区别,目前我唯一知道的区别就是第三种方案更加灵活,有两种单位可以使用,想让元素适配的时候就用rem
,想让文字不缩放的时候就用px
。
如果你没有明白我以上讲的,可能是我太啰嗦了,你可以看看下面的那些文章。当然,最好的理解方式就是用这些方案做几个页面,到时候就明白了。
其他文章
这两篇文章看着简单,尤其是上一篇讲视口,花费了将近一周的时间去翻阅资料。本来以为很简单的东西,才发现有那么多名堂。好了,后天年会,祝我中大奖吧。
PPT
vw方案: https://www.w3cplus.com/css/vw-for-layout.html
demo: http://huodong.m.taobao.com/act/layouttestvw.html
只要明白 CSS 的 vw
单位是什么意思就明白这个方案了,可以看作是第三种方案的完美版,先定死 viewport,再通过 vw 取代 rem。
background-size
的contain
和cover
是怎么用的,大家应该都明白。但是里面也有一些有趣的数学关系。
上面就是我们对于 rimage (图片宽高比)、rviewport (容器宽高比) 的定义。
stretch : 把图片的宽高强行设置为容器的宽高
注: h'image、w'image、r'image分别为图片改变后的高、宽、宽高比。之后文章这些名词也是这个意思,不再解释。
stretch
的方式可想而知后果:
那么保持怎样的数学关系才能保证图片放进容器之后不会变形呢?
答案也是明显的:
r'image = rimage
接下来介绍的两种方法就是不会变形的,也就是说能够上面的公式对于它们来说是已知条件。
contain : 让图片适应容器,我们把它“装”进容器,同时也会留下空白。就像我们看电影时的"黑边"。
对于contain
方法来说,也只有图片放进容器后的高度( h'image )是未知的,我们来算一下:
如果不知道contain为什么是这样的建议先看看
background-size
cover : 也可以让图片“遮”住容器。
和contain
对应,cover
方法要来算一下 w'image
不知道大家注意到没有,刚才我们推导contain
的 h'image 和cover
的 w'image 时使用的图片的宽高比总是大于容器的宽高比。
这导致了什么?导致了我们推导时使用的 条件3
是不一定正确的。
额,这么说我也有点晕,看图:
可以看到,我们只考虑了 rimage > rviewport的情况。
我们考虑rimage < rviewport后加完整了,图片放进容器之后的宽、高如下:
这样我们就求到了图片在应用background-size
属性之后在容器中实际的宽、高。
现在讨论图片放进容器后的图片与容器的比例关系hidden
,这样我们就可以以此关系让图片随着容器的变化而变化。
注意,hidden
是一个小于1的比例,至于为什么要这样设定后面有解释。
以contain
布局为例,rimage > rviewport :
而以cover
布局为例,rimage > rviewport :
以此类推,得到所有情况的 hidden
这样可以看到四种可能性,但是别忘了我们在上面可是推导过 w'image 、h'image 。
所以hidden
最终的结果是:
可以看出来,hidden
就只有两种结果,rimage / rviewport 或 r viewport / rimage,而且这个数是小于1的(这是上面就确定的)。
所以,hidden
的计算可以简化为:
你可能想,搞了半天,这到底能干吗?直接用background-size
不就好了,为什么还要得到具体的宽、高,得到了伸缩比又能怎么样。
我也想了想,如果只是图片,似乎上面都是废话。但如果是DOM呢?这是不是就是一种布局方式?
我也不知道,知识有时候就是这样。当你需要用到的时候,你才觉得有用。
狠狠戳demo
运用技术点:
box-shadow
制作出"点点"(这个思路很重要)steps
制作打点的出现、消失效果currentColor
属性可以让点和字的颜色保持一致,可以修改.demo
的color
试试效果animation
默认以ease
方式过渡,会以在每个关键帧之间插入补间动画,所以动画效果是连贯性的。ease
,linear
等之类的过渡函数都会为其插入补间。但有些效果不需要补间,只需要关键帧之间的跳跃,这时应该使用steps
过渡方式。本文后面有案例。
简单地说,就是原本一个状态向另一个状态的过渡是平滑的,steps
可以实现分布过渡。steps
用法 :
steps(n,[start | end])
n是一个自然数,steps
函数把动画分成n
等份。
step-start
steps(1,start)
,动画分成 1 步,动画执行时以左侧端点为开始 ;step-end
没懂对不对?我也没懂,上面是官方的说法。接着往下看吧
可以在demo中查看step的区别:狠狠地戳下去
steps(4,start)
与 steps(1,start)
的最大区别就是每次动画"跳跃"的时候,即从 0% -> 25% 的时候,steps(1)
直接一步到位,瞬间从0%的状态跳到25%的状态,而steps(4)
会逐渐走4步,即从 0% -> 25% 要跳 4 步 !
慢慢体会一下,应该就知道steps
的作用了吧
先戳 demo
这里就是steps(1,start)
,动画又是只有 0%(100%) 、 50% 两个状态,所以直接一步跳跃,直接走完。
你可以改成
steps(4)
,就更能知道steps
的作用了
先看看人人网首页的效果:
再戳demo
一步一步分析:
首先,我们不加动画,就像s1
;
然后,加animation
但是没有steps
,像s2
那样。这样很奇怪是不是,就是因为没有steps
,动画是连贯的,所以我们看到了跑马灯似的效果:
最后,当然是我们的终极效果s3
,因为设计师把我们看到的类似 Flash 的动画逐帧导出成一张大图,再加上合适的steps
和动画时间,就看到了人人网首页的那般顺滑的动画效果!
注意,为了保持最后停止的状态,还要加一个 forwards
,这里不是本文重点,就不细说了。
再考考你有没有搞清楚
steps
:为什么图片是20帧,可是设置成steps(12)
呢?因为
steps
是设置的每一步动画的跳跃步数,而不是整个动画的跳跃步数。举个例子:@-webkit-keyframes ani{ 0%{...} 50%{...} 100%{...} } .xxx:hover{ -webkit-animation:ani 2s steps(10) ; }上面的代码指的是
0%
50%
之间有10个跳跃,50%
100%
之间有10个跳跃。而不是0%
~100%
之间有10个跳跃。
呼呼,终于理清了steps
的作用。感觉这个过程就是渐进增强的体验,一开始用户只能体验s1
,后来有了css3
,可以体验美妙的动画了,就像s3
。但愿以后我们能够做到让每一个用户都满意。
我们都知道JS是没有办法监听到Android机上的物理返回键的,所以我们要"变通"地实现这个功能。
在webview中实现这个功能比较简单,因为客户端是可以监听到返回键的,所以客户端只要给webview留一个接口就可以了。这是手机助手的监听物理返回键的接口:
/**
* 截获物理返回键
* setOnBackKeyDownListenner(int type); void
* type : 1 截获 0 释放
*
* 按下物理返回键触发事件
* onBackKeyDown
*
*/
// 弹窗时
AndroidWebview.setOnBackKeyDownListenner(1);
showPopup();
// 按物理返回键时客户端回调页面函数
function onBackKeyDown(){
// 释放物理返回键
AndroidWebview.setOnBackKeyDownListenner(0);
hidePopup();
}
openPop
事件执行的时候弹窗出现,然后开始监听物理返回键
window.openPop = function() {
$('.mask').css('display','-webkit-box').addClass('js-close');
AndroidWebview.setOnBackKeyDownListenner(1);
}
onBackKeyDown
是按下物理返回键触发的事件,可以看到按下返回键后执行closePop
并且不再监听返回键
window.onBackKeyDown = function(){
// 关闭弹窗
window.closePop();
// 释放物理返回键
AndroidWebview.setOnBackKeyDownListenner(0);
}
可以看出来,webview下的监听物理返回键还算是"比较"简单的
看下面的案例,这个页面有4个子页面,这里用hash记录所处的页面,有这么几个好处:
hashchange
的事件触发,可以记录页面状态。比如页面在#page2
,这时候用户刷新了页面,页面还是会在page2
;history
里的,也就是说按下浏览器的后退键或者手机的物理返回键是可以回退到上一个状态的,这样在回退的时候我们利用hashchange
不是也就做到了"变相"地监听了返回键么?Flutter 近来几个月炒得大热,对于移动开发者来说是挑战可能也是机遇。至此,跨平台开发 领域算了一下,有:
我列出这个表的时候,有些头皮发麻,不知道对于我这个想通过跨平台开发进入移动原生开发的人来说该选择哪个。之前只了解过一点 react native,感觉开发者体验并不好,想要 inspect 元素还要再开一个 devtool。这次,索性就直接试试 flutter。
现在记录一下我作为前端开发者玩 flutter 的体会。
按照文档 的顺序来做,没什么好说的,我搭建环境还算顺利,如果你遇到问题,可能需要学会科学上网。概述一下我的安装过程(Mac),一切以官网为准:
下载 Flutter 源码(注意,是 beta 分支):
cd ~
git clone -b beta https://github.com/flutter/flutter.git
然后在 .bashrc
(如果你用 zshrc 的话就是 .zshrc
) 中修改环境变量,将 flutter 项目根目录下 bin 路径加入环境变量 PATH:
export PATH=$PATH:/Users/riskers/flutter/bin
flutter --version
验证是否安装成功
检查你的机器上欠缺的环境
因为是跨平台开发,所以你需要安装 iOS 和 Android 开发环境:
flutter doctor
就会看到下图,x 号是你需要安装的环境,这里因为都已经安装好了,所以都是对号。
给 VSCode 安装插件: Dart 和 Flutter,这样就能够用 VSCode 开发 Flutter 了。
创建新应用:
运行新应用
按下 F5
进行调试,选择你的设备(需要你提前准备好模拟器或者真机)
如果你运行 Android 项目遇到问题,查看这个回答
然后是 hot reload:
hot reload 已经是标配了,如果 hot reload 都没有,也太差劲了。
目前 layout 的 inspect 只支持在 Android Studio或IntelliJ IDEA的 Flutter插件。
更多的实践会在未来慢慢总结出来,敬请期待。
一直以来,我使用 reeder3 + Inoreader 订阅了很多博客,但是4月的时候发现不能再添加新的订阅了,它收费了。想到每天早上再也不能一来就看新闻,就很烦,于是开始研究替代方案。现在我使用的是 Tiny Tiny RSS + fever + reeder3 的方案,到现在也没发现什么问题,就记录一下,分享出来。
以下分为本机搭建和远程搭建两部分,纯属傻瓜配置,可一步一步来。
docker-compose.yml
:curl -L "https://raw.githubusercontent.com/riskers/tty-fever-rss/master/docker-compose.yml" -o ./docker-compose.yml
我将
docker-compose.yml
文件放在了 github上,其中有注释的部分需要自己修改一下
docker-compose up -d
open localhost:181
admin
password
在插件中启动 fever
:
然后可以看到并且设置密码:
我自己因为有多台设备,所以本机搭建还是不方便,就在阿里云 ECS 上搭建了。购买 ECS 后,安装 docker 和 docker-compose,过程略。
其他都和本地搭建都一样,只不过需要在 ECS 的安全组中暴露 181:
最后,展示一下效果:
参考文章:
2020 是多么玄幻的一年,不知道多少年后会不会有人记得这一年世界上出了这么多事情。
现在简单总结一下我的 2020
今年对 Java 技术栈更加了解了,不过前端倒有些生疏了,现在有些朋友问我前端的问题,我都有点没有反应过来了。
这点就不再多说,意义不大,列个单子:
今年的投资还是挺成功的:
这还只是美股,港股和A股还有基金也还行。
以至于最近有点飘了,觉得赚钱有点容易,还是要老老实实的啊。
这是今年最大的事情了,本来 5 月份的婚礼因为疫情延期到 10 月,不过最后也还算顺利。
总的一年上来看,工作整体平平,技术上自我感觉有一定长进,更加看中同事之间的交流。现在的角色更像扳手,哪里需要就往哪里拧一下。
说到列新年 TODO LIST 这种事情,我已经坚持了将近十年,现在拿出十年前的清单看看,竟然挺励志的: 『过英语四级』、『找到实习』、『学习一门后端语言』、『涨薪50%』等等,还是挺有意思的。
每年这个时候是最爽的,把去年的目标全选复制,然后新建一个文件贴进去,把完成的划掉,然后把今年想做的加上去。现在核对去年的清单,果然和往年一样,有超出预期的,有没有达到的,也有根本没有开始的,不过这样随心而为的感觉也挺好的。
Docker 大家应该都听过,不过可能对于我们前端来说用得比较少,后端开发和运维应该是对它很了解了。我从去年开始了解到 Docker 的好处后,对它一直很有兴趣。最近,用了一个比较整块的时间研究了一下,这篇文章记录下来。最近2周的空闲时间,看了一下 Docker ,记录一下,免得以后忘了。本文的代码都在 https://github.com/riskers/docker-demo 可以找到。
对于我们前端来说,这样比较好理解:构建好的镜像只要就是 package.json
,里面只要写好需要的包名和版本号,任何人拿到这份文件都可以获取对应的包,并且能够运行这个程序。但是,我们忘了一个很重要的点,Node 和 npm 也是有版本的啊!举个例子,Node 在 7.x 以上才支持 async
,如果我们的服务器上 Node 的版本是 4.x ,那么部署上去肯定是不行的,然后查个半天 BUG ,才发现是 Node 版本的问题。Docker 就是帮我们解决这种问题的,它能够把 Node 版本也能够像 package.json
那样记录在配置文件中。
判断 docker 是否能正常工作
docker info # 返回所有容器和镜像的数量、基本配置等
运行容器
docker run image-name
比如 docker run ubuntu
,会先检查本地是否存在 ubuntu 镜像,如果本地没有该镜像的话,那么 Docker 就会查看 Docker Hub 中是否有该镜像,找到的话就会下载该镜像并将其保存到本地。
随后,Docker 在文件系统内部用这个镜像创建了一个新容器,该容器拥有自己的网络、IP地址,以及一个用来和宿主机进行通信的桥接网络接口。
使用容器
docker run -t -i ubuntu
会进入容器,然后 exit
离开容器,容器就停止运行了。
-i: Keep STDIN open even if not attached
-t: Allocate a pseudo-tty
但容器还是存在的,docker ps -a
查看所有容器(正在运行的、已经停止的)
docker ps
只会列出正在运行的容器。
容器命名
Docker 会为每一个容器自动生成一个随机 id,我们也可以自己为容器指定名称。
docker run --name your_container_name -i -t ubuntu
容器名称不允许同名,可以使用 docker rm
删除同名容器。
重新启动已经停止的容器
docker start your_container_name / your_container_id
docker restart your_container_name / your_container_id
获取信息
docker inspect your_container_name / your_container_id
附着到容器(重新启动并运行一个交互式会话shell)
docker attach your_container_name
守护容器
docker run -d your_container_name #创建守护容器
docker top your_container_name #查看容器内进程
docker exec your_container_name touch a.txt #在容器内部运行进程
docker stop your_container_name #停止容器
删除容器
docker rm your_container_name/id
列出镜像
docker images
拉取镜像
docker pull
docker store 上每个镜像都有很多个标签
docker pull ubuntu:12.04
表示拉取 12.04 这个标签
构建镜像(Dockerfile + docker build)
一般来说,我们不是真正地创建新镜像,而是基于一个已有的基础镜像,构建新镜像而已。
FROM ...
RUN ...
# 指定容器内的程序将会使用容器的指定端口
# 配合 docker run -p
EXPOSE ...
docker history images [name]
从新镜像启动容器
docker run -d -p 4000:80 --name [name]
可以在 Dokcer 宿主机上指定一个具体的端口映射到容器的80端口上
推送镜像
docker push [user_name]/[image_name]
删除镜像
docker rmi [user_name]/[image_name]
本文大部分是我读《Docker开发指南》时做的笔记,并结合实际自己做了 demo。
因为我接触 Docker 的时间很短,关于一些概念可能会有一些错误,欢迎指正。
最后啰嗦一下,其实我作为一个前端在工作中根本用不到 Docker ,只是为了兴趣才来看看,万一以后能用到呢。我的拖延症很严重,本来这是去年就要学的,拖到最近才学。
14年毕业,目前在老家 Remote,全栈工程师
大学毕业前的二十多年都在西安度过。
大学时有迷茫,不知道将来要干什么,但觉得学习总不会有错。奈何对本专业(通信)不感兴趣,所以,设计(PS)、视频特效(AE)等也都有了解。
这么度过了两年多,直到大三决定开始进入前端领域。当时没有什么想法,就觉得能实现自己的设计图挺好的。
毕业后顺理成章地进入前端行业。
目前为止(2023),待过一线大公司(Ali)、没落的公司(360)、创业公司、500 强(xiaomi),现在在一家公司 remote 中。
基本日更
今天同事(妹子)遇到一个 Zepto
的事件委托的问题来问我,我当时也懵了,后来解决了。问题还是比较坑的,拿出来分享一下。先看看是什么问题:
为什么?!为什么事件委托在 .a
上可是却也触发了 .b
上的委托。看着妹子求知的眼神,我胸中一阵气短。猜想着是 .a
委托事件最后换了 class
,DOM立刻更改了,就在 .a
事件后触发了 .b
。所以我立刻让她这样改一下就可以延缓DOM更改:
$doc.on('click','.a',function(){
alert('a事件')
var $this = $(this) ;
setTimeout(function(){
$this.removeClass('a')
.addClass('b')
},30)
})
然后就正常了 页面2。
虽然妹子对我一阵赞许,可是我心里还是隐隐不安,回来通过咨询大牛和看源码知道了这是什么原因。
先看看这个页面 页面3
查看源码我们可以看到,页面3 和 页面1 几乎一模一样,就是在 .a
和 .b
的事件委托顺序不一样:
那为什么 页面3 就可以正常呢?就是因为 Zepto 的事件委托和我们想象中的事件委托是不一样的。
Zepto
的事件委托是:
在代码解析的时候,所有document的所有
click
委托事件都依次放入一个队列里,click 的时候先看当前元素是不是.a
,符合就执行,然后查看是不是.b
,符合就执行。
这样的话,就导致如果 .a
的事件在前面,会先执行 .a
事件,然后 class
更改成 b
,Zepto
再查看当前元素是不是 .b
,以此类推。
这就是 页面1 出现BUG的原因,而 页面2 之所以也能解决这个问题是因为 class
变化实在延迟之后,click 事件当时没有检测到 .b
。
看看 Zepto 的事件部分是怎么写的。可以看到是用$this.each
循环绑定在 $this
上的事件。对应在我们的例子,就是 document
上绑定的事件都被塞进一个队列中。
再看看 jQuery
的事件委托:
document上委托了2个 click 事件,click 后判断是否当前符合条件(选择符),然后把事件拿出来执行。
这是符合我们一般的认知的,也是那个妹子那样写代码的原因。你不妨把页面1的 Zepto
换成 jQuery
看看。
这是一个 Zepto
和 jQuery
不同的地方,以后要注意了。
长期招聘前端 Web 开发工程师,请邮件简历 [email protected]
2015年是工作的第一年(去年8月来公司后直到今年的2月才算正式到现在的部门,中间发生了很多事就不多说了)。这一年里,我算是真正入了前端的门,在知识体系上算是真正了解了前端的水有多深。
今年6月的时候,下决心买了 Mac ,使用了几个月以后,也习惯了 『Mac生活节奏』:
今年学会了怎么做 Chrome 插件,还做了两个玩意:
今年我找回了看书的习惯,这是我的书单,有技术的,有小说,不过看得还是太少了,2016 再加油吧。
今年重新开始写博客,以前的博客用的是 wordpress ,很慢,渐渐的就不想写了。今年工作中有一些东西想写下来,就在 github 里写了。
今年开始喜欢李宗盛的歌,李宗盛的歌词就像白话,语句简单却总是意义深长,今年,渐渐听懂了李宗盛。以前听人说过,每个男人心里都有一个李宗盛,你没有听懂他的歌只是你还没有长大。
今年看了一些电影,印象最深的是『西西里的美丽传说』和『教父』,尤其是『教父』里地那句『男人应该以家庭为重』。
最近在翻译 Chrome 开发者工具 的文档,1月中应该就完成了。
就是这样,希望自己2016变得更强。最后,放上公司的吉祥物。
在很长的一段时间里,我都在尝试各种各样的管理软件帮助我管理时间:
还有一些记笔记的地方:
因为各种各样的原因,导致我一直没有用起来,有时候还是感觉像无头苍蝇,很多事情让心里压力很重。
最近,改进了一下自己的管理模式,记录在这里。
而 Trello 和 gist 最终的流向是 MWeb,每一个 Trello 卡片归档后应该放在 MWeb 对应的文件夹里。比如我最近在搞 DevOps,我就在 Trello 里建了一个 DevOps
的看板:
我会分别针对 Docker、k8s、 CI 等等知识分别建立列表,之后,我把需要我掌握的资料、知识点都放在列表里作为卡片存在。边学习一个卡片的知识,就边在这个卡片里记录下来:
学完一个就会归档一个,之后在 MWeb 里把对应的知识点记录下来:
这样子,就做到了: 认识有了终点
尽量清除列表里的卡片,清除一个,就是学会了一个。看板可以留下来,因为以后肯定还有要深入的地方,你可以还这个看板里加列表。
横向地,你可以有多个看板,这样你就可以很清楚地看到自己学过的领域:
将近一年前我学习了一段时间的 docker,并且记录了一个流水账一般的笔记。之后一直没有正经在项目中使用过,最近一个项目中需要使用,又再次学习了一遍,再次记录一点。
固化 这个词我都加粗了,因为我没想到更适合的词。
我看到不少文章中在 Dockerfile 里都使用 COPY
命令把源代码放置在 docker 镜像里,但是就像上面说的,docker 容器只应该提供一套环境,不应该和代码强关联。所以,代码和环境应该使用 volume
命令来连接起来。
推荐使用 kitematic.com,这是官方的一个 GUI 软件,使用方便,对于我这种虽然了解概念,但是命令行不熟的人来说很有用。
比如你新启动一个项目,需要使用 mysql。你可以自己在本机上安装 mysql,还要想着本机的 mysql 版本和服务器上 mysql 版本是不是一致的问题。或者,你可以使用 docker,一条命令就搞定:
docker run -it -p 33306:3306 --name mysql -e MYSQL_ROOT_PASSWORD=123456 -v ~/mysql:/var/lib/mysql mysql
然后我们在 kitematic 中可以看到:
端口映射和 volume 一目了然,然后我们试试这个 mysql 有没有映射到本机的 33306 端口:
这是可以连接上的:
并且,我们可以看到数据已经保存到本地而不是容器里:
通过这篇文章能够深刻理解 background-position 的百分比。
正文开始:
通过这篇文章我要教大家解决一个曾经很困扰我的麻烦问题。我们要使用百分比的background-position
值来解决一些问题。
摆放图片
通常在容器里摆放图片是给出具体图片的top
、left
相对容器的top
、left
的值。
在CSS中很容易做到。
在容器里使用<img>
标签
.container{
position:relative;
}
.container img{
position:absolute;
top:12px;
left:20px;
}
或者可以使用background-position
.container{
background-position:12px 20px;
}
在容器里移动
现在你想让图片在容器里面移动而且还不能超出容器边界。你肯定是要简单算一算图片top
、left
的最大值。
然后得到left
值的范围是 0 到 container_width - image_width
,同样也可以得到top
值的范围。
图片比容器大
到目前为止,我们讨论的问题都很简单。现在,我们要看看图片比容器大的情况。容器必须要被图片填满。
同样我们可以算出left
值的范围是 0 ~ container_width - image_width
,只不过这次container_width - image_width
是负值。
你可以搞明白正值和负值的关系,也可以凭直觉搞定。当你看到12px 20px
你很容易知道图片是怎么放置的。但是,你看到-12px -20px
就比较难想明白了。
不变量
好了,现在你已经写好了位置点并且没有任何问题。现在,因为某些原因,我们不用长方形容器了,用正方形容器。那么之前的那些位置值就不那么合适了。
我们之前计算的值不再有效,因为现在情况变了。你想要改变图片和容器大小也是一样的道理。
可以从图中看到,如果使用固定的值,那么一旦改变某些条件,那么就可能会让已经写好的布局乱掉。
定义
我们要换一个确定图片位置的方法了。当图片的左边框和容器的左边框挨着时,left
是0%
。当图片右边框和容器的右边框挨着时,left
是100%
。
这两个例子分别就是 0% 和 100% 的情况:
我们很容易得到两者之间的值
left = (container_width - image_width) * percentage
范围检测
这个方法最方便的就是我们不用再算图片相对容器的范围。它就是 0 ~ 100 。
不变量
我们画两个轴,一个对于容器,一个对于图片。如果我们设置值为60%
,则两个轴的60%
会重合在一个点上。
就像上面的图片一样,这个新的方法在不同比例大小情况下也工作得很好。
水平和垂直
如果你细心的话你会注意到图片和容器一样大的话,两个轴会完全重合。设置 30% 还是 80% 都不重要。
再看看数学公式
left = (container_width - image_width) * percentage = 0 * percentage = 0
你只需要设置两个值left
、top
就行了。
一开始,我没有明白百分比值是怎么对background-position
作用的。我真的有点迷惑,因为使用百分比让我不能直观地感受到变化。然而,后来我发现使用百分比解决图片定位是极其方便的。
想想今年,从6月开始,微信小程序、C 、密码学、Chrome 插件、Angular、rxjs、Docker、CI、k8s、eggjs,这些都玩了。
还有就是保持初心,我首先是工程师,其次才是前端工程师,在后半年稍闲的时候开始关注更多的后端知识,容器相关的东西,然后就是开始学习 C 和计算机基础,没事刷刷 leetcode ,学学数据结构和算法。
简单说说今年的进步和蜕变。
今年正式使用 Node 写简单的后端,使用的是 eggjs + TS, 写起来真爽!
在创业公司,使用 Docker 和 gitlab-ci 正式搭建起来 CI 环境,明年需要实践 CD 和 k8s
简单地入门了,还顺便学习了 dart 语言
今年除了 TypeScript 外,还学习了 Go 和 C。
学 Go 主要是想做一些系统编程
Go is modern Python
Rust is modern C++
学 C 那就完全是想用它实现数据结构了
写在博客的都是干货,至少我自己认为是。
又看了看,果然简书上写的都是废话,絮絮叨叨像个老太太。
最近大半年在创业公司, 干的却不怎么是前端的活。可能是因为创业公司的属性,而且主要是老板们也没想好要干什么。感觉看似忙忙碌碌的大半年,却好像什么也没有得到。
为什么离开阿里?答案都在这里: https://www.jianshu.com/p/cb27ef093402
等了一年多,房子终于在国庆前下来,现在还在装修。
唯一推荐《血观音》,这是今年看过的最精彩的电影
前端现在有 PWA 、webAssembly、Flutter 这些热点可以玩,不过我明年应该会把重心放在后端上面。
另外,基础很重要,在今年学习 k8s 的时候,就涉及到 cgroups 这个 Linux 概念了,对于我来说,很难理解。还是要好好学习 CS 基础。
这一部分,可以参考我在知乎的回答
今天我刚好工作满五年,为什么记得这么清楚,因为8月8号是北京奥运开幕式。那时候 360 让我选择是7月8号还是8月8号,我果断选择了8月8号,在那个炎热的夏天又多玩了一个月。现在想想,要是那时候选择7月入职,可能后面很多人很多事就经历不到了。
刚入职的第一个月是校招培训,可能也就是大家说的『洗脑』,不过整整一个月还是很开心,不仅仅上了两周的课,最后两周还是 hackson ,其中也有很多很多难忘的回忆,这是在 hackson 自己做的一款 App:
当时小组里的同学如今也都各奔东西,祝愿他们都发展顺利,事业有成。
在 360 度过了两年半的时间,记得刚工作那会,什么东西都不知道,不知道怎么和产品经理通过,不知道还原设计稿有那么多东西要考虑,不知道怎么和领导沟通。那时候,只能每天晚上下班以后,在公司看书学习,到了10点去健身房锻炼一会,然后洗个澡回家。焦虑感持续了将近一年,这期间学会在 google 上找自己的答案,学会了怎么和同事交流,学会了怎么看一本书。在往后的一年,也就是 2016 年,感觉一直在重复自己前一年的工作,能力没有得到进步,所以在年底终于鼓足勇气出去面试,得到了我人生中第二份工作。
2017 年的时候来到阿里,不得不说,这里几乎一切比 360 来得要正规,360 是草莽的,一个项目可能就是一个后端、一个前端、一个设计师、一个测试就搞定了,一切的流程都是能省则省,能简则简。而阿里,定下一个项目,要评审需求、定排期、定 PM(我们当时的 PM 是这个项目中某一个开发同学兼任)、定开发人力、定测试人力等等,然后一个项目下来,你可能会发现大量的时间不是在 code ,而是和各种角色的人沟通,以及在各种钉钉群里回复。在阿里的一年半的时间,我技术长进不少,但是我学到更多的是非技术层面的东西。我**上成熟不少,我以前一直认为技术就是一切,但是阿里会告诉你,技术很重要,但是其他的东西更重要。『阿里就是一个江湖』,不知道是谁最开始说的,我深深赞同。在阿里,压力很大,除了正常的业务需求外,还有 KPI 的压力,这些让我有点吃不消,甚至让我深深地焦虑和畏惧。
今年初,我又来到小米,做一个全栈开发者,希望这里可以职业生涯最重要的一站。
2014.8.8 - 2019.8.8,时间就是这么飞快,已经在北京蹉跎五年的光景,从刚毕业的愣头青,到现在猛然发现已近而立。五年,不快不慢,不长不短,有些事情更加坚定,有些事情也逐渐放下。
只望,不负岁月,不负青春,不负自己。
今天才有心思写2016年的总结,一直构思了很久,不知道怎么写,写些什么。总是觉得时间很快,快到自己什么都没干一年就又过去了。
如果说 2015 年是混混沌沌度过的,那么 2016 年就是有计划、有目标地开始提升自己。我就随意想想,记下流水帐。
今年我开始养成了看书的习惯,一半是技术方面的书,一半是小说什么的。
之所以单列出来,是因为我今年无意中读了他的《解忧杂货店》之后一发不可收拾地喜欢上了他的书
数了数,将近读了40本书,也算是及格了吧,希望今年继续保持
今年去了一趟张家界,以后争取每年出去旅游一次,多出去看看,见识见识。
2016 年又是一个前端飞速变化的年份:
模块化:我从 15 年底自己使用 webpack,16年陆陆续续用在很多项目中
还使用 webpack 开发了一个 es6 的库 https://github.com/riskers/Turntable
MVVM:React、Vue、Angular 三分天下的趋势已经明了,不用纠结于学哪一个,更不要同时学,只要学精一个,其他的不是问题。(我是 React 党,今年有计划陆续写一些文章记录最近半年的学习成果)
Node:我认为现在 Node 有 3 个作用:
es6:哦,不对,应该叫 es2015 因为 babel 而开始普及
因为以上种种工具,前端工程师沦为配置工程师
用 Node 作为中间层,前后端完全分离之后,这个页面是 React 渲染还是 Node 渲染都是前端自己决定的。后端只负责把接口安全、迅速就够了。所以,开发 web 以后会越来越像开发客户端。后端只提供接口,前端负责浏览器内所有的东西。你想让这个页面避免白屏、能够SEO,就使用 Node 获取数据然后渲染页面;你想做 SPA ,就使用 React 开发,然后直接放服务器上就行。当然,还可以用让 React 在服务端渲染,这样这个应用即是 SPA ,有良好的体验,还能够避免白屏、能够 SEO,这部分也是我最近在研究的。
博客在 16 年都没怎么更新过,一来是觉得有些东西自己也掌握得不好,就都没有发在博客上,都记录在了笔记上。新年争取多写几篇有质量的博客。
我会在业余时间学学客户端开发,以及服务端开发,因为我觉得不应该给自己设限,我首先是一个开发工程师,然后才是一个前端开发工程师。我不排斥任何技术,因为技术是给业务服务的。
我每年的跨年夜都会写一个明年的 todolist,今年已经5年了,有点可怕。
又到年底了,2022 这一年真的是忙碌且充实。前两天刚刚阳过,没什么精神工作,就总结一下这一年。
先来个全年流水帐总结。
过年期间因为西安疫情严重,所以有生以来第一次没有和父母一起过年,除夕和媳妇吃火锅。
上半年在北京,3月的时候,因为公司有人确诊,所以一整个楼层的人被集中隔离。我在一个酒店房间待了 2 周。5 月的时候,防疫政策愈发严格,整个 5 月,我都没有出小区。
当时的心情 down 到极点,想着要离开北京,但是一直舍不得这里的工资待遇。于是,无意中发现了一些远程工作机会,随着了解,发现远程工作比自己想象得多得多。终于,大概在 5 月底的时候,找到了达到自己预期的工作,可以 remote,工作内容也是自己感兴趣的。最终把入职时间定在了 6 月 6 号,定好高铁票,回老家了!
2022 在北京最后一张照片:
于是经过了半年的试用期,在 12 月的时候,成功转正。这半年,因为没有通勤时间,而且基本没有浪费时间的没有意义的会议,所以我在完成工作之后有大量的时间去做自己感兴趣的事情。心情不好的时候,就打打游戏;心情好的时候,就看看书,练练英语。
世界杯决赛那晚,也是我新冠发烧最严重的一天,我庆幸自己没有睡过去,没有错过这场精彩的比赛。自己这些年早就养成了习惯,每年元旦的时候,写下自己过去这一年的总结,现在已经坚持了十年。每次世界杯结束的时候,写下这四年自己的变化,算是在不同时间维度上总结一下自己。
这次,世界杯又结束了,而上一届的时候还在创业呢。现在比当年,工资涨了一大截,能力我觉得也涨了不少,已经学会不站在执行者的角度看待工作问题。还会享受生活了,不是每天悲摧地去上班,而是满怀期待地去开始每一天。
现在的工作是全栈,前端、后端 55 开。
关于 remote 的机会,还是要学好英语,因为,远程工作多数是美国公司,或者有美国同事,英语绝对是加分项!建议每个人都要学好英语,这样,你就可以和全世界的人竞争,不用 996 也可以拿到一份体面的工资。
还有一点不爽,书房的桌子比较小,能放的东西比较少,可能还需要再买个升降桌。
在开源项目上来说,做了 GithubX,之前已经总结过,不多说了。
这个项目在某种程度上来说,也顺利地帮我找到了现在这个工作。
还有就是,这个项目帮我搞到了 JetBrains 一年的 Licence:
今年明显电影看得少了,有时候宁愿看些老电影,列一下我觉得今年看过的比较值得看的。
8 月买了 XBOX XSX,到现在已经通关了好几个游戏:
XGP 真的无敌,这些基本都是在 XGP 上玩的,已经续费一年了。
因为长时间在家,饭需要自己解决。因为实践得多,这半年感觉厨艺大涨。
9 月,通过一个项目,把这几年落下的前端知识都补回来了(虽然不愿意承认,也足以说明前端没什么核心科技,233),还顺带学习了 Web3 DApp 开发。
年底的时候,临近圣诞假期,我这边比较闲,思绪又乱了起来,就开始学些乱七八糟的东西,比如口琴、微积分(越发地觉得数学很重要)。
今年新学习的编程语言:
上面都算是输入,其实今年还有一些输出的,算是近几年输出最多的一年了:
今年的学习状态比之前好太多了,尤其是回家的这半年,作息规律,感觉记性都变好了。在北京的时候,有段时间学啥都记不住,现在基本心态变好,都能学了!
去年列的计划基本都实现了,印象中还是第一次年度计划能实现得这么完整。
2022,Never See Again,恶心的一年赶紧过去吧。
2023 计划先列在这里,有些少,也比较抽象,后面可能再填充。毕竟,年度计划这种东西会随时变化的。
最近开始学习 Flutter,那就不可避免地开始 dart 的学习,除了语法的学习,就是要熟悉它的技术栈和模块化了。
这样也方便以后给 Flutter 生态贡献力量。
文本主要介绍 pub 模块管理工具在 dart 项目中的使用,pub 对于 dart 相当于 npm 对于 Node.js。
在新项目中新建立 pubspec.yaml
文件,相当于 Node 中的 package.json
:
pub 目前不支持自动生成(类似
npm init
的功能),只能自己手写
pubspec.yaml 格式
name: pub_study
version: 0.0.1
description: pub study by rxdart
dependencies:
rxdart: ^0.20.0
environment:
sdk: '>=2.0.0 <3.0.0'
authors:
- riskers <gaoyibobobo@gmail.com>
homepage: https://github.com/riskers/dart-pub
执行 pub get
安装 rxdart
模块,然后会自动创建 .packages
和 pubspec.lock
两个文件:
pubspec.lock
: 相当于 Node 的 yarn.lock
,是用来锁版本的,关于其是否加入版本库,要看你的 package 是 library 还是 application
# Generated by pub
# See https://www.dartlang.org/tools/pub/glossary#lockfile
packages:
rxdart:
dependency: "direct main"
description:
name: rxdart
url: "https://pub.dartlang.org"
source: hosted
version: "0.20.0"
sdks:
dart: ">=2.0.0-dev <3.0.0"
.packages
: 加入 .gitignore
中
# Generated by pub on 2019-01-07 18:42:25.471988.
rxdart:file:///Users/gaoyibo/.pub-cache/hosted/pub.dartlang.org/rxdart-0.20.0/lib/
pub_study:lib/
执行 pub deps
可以看到项目的依赖:
将代码写好:
| - lib
| - src
| - print_string.dart
| - print_number.dart
| - pub_study.dart
pub publish --dry-run
检查是否能发布:
然后就可以发布了 pub publish
,按照提示点击链接,就可以看到:
在 https://pub.dartlang.org 可以看到:
测试,新建文件 pubspec.yaml
:
name: test
dependencies:
pub_study: ^0.0.1
执行 pub get
:
新建 test.dart
:
import 'package:pub_study/pub_study.dart';
void main() {
print_string();
}
执行:
这个时代太棒了,什么知识都可以在网络上找到,之后就是筛选的烦恼。现在总结一下我的学习方法论。
首先,明确自己学一个技术的目的,这样才能有的放矢。不要含糊地定义目标为「学会」,什么叫学会?是得心应手地处理工作叫「学会」,还是能实际上手做一些小功能叫「学会」,还是了解了几个 API 叫「学会」。
我觉得,针对问题解决问题最重要!而不是看到一个新玩意,就去「学会」!搞不好你以为「学会」了,可没有实践,最后也会一事无成。
前些年,我就陷入这个怪圈。陷入选择怪圈,想要同时 TS 和 Golang,结果贪多又没有实践,今天学的明天就忘,自己也很痛苦。幸好,最近写 GithubX 又找到了写代码和学习的感觉!让我知道了:学习最好的效果就是用学到的去做一个东西出来,也最牢靠!
我上大学之前,准确地说,是大二之前,是不知道编程这回事的。大一暑假,我自己打工赚了不到两千块钱,加上之前攒的生活费,给自己买了一台笔记本,是神舟的,我记得很清楚。
大二,学习 PS、PR,给学生会做做海报和宣传视频。现在想想,要是一直坚持下去,不知道现在自己会不会是一个设计师或者影视后期呢?
大三,学习 CSS,开始切一些页面练手。当时也不知道怎么获取知识,就是把图书馆里相关的书都借回来,对着里面代码敲。结果是可想而知的,没有什么效果。之后发现其实有些人的博客写得特别好,愿意分享出来,尤其是张鑫旭的博客,我当时几乎把他每一篇文章都看了,然后敲下来。
大三暑假,去了北京实习两个月,住在蚂蚁间里。让我明白了多人是怎么协作的,也让我知道了 Node.js 和 Angular.js 两个框架。
大四,继续夯实基础,然后找工作。其实我在这个阶段还处于没开窍的阶段,也根本不会知道未来工作会有什么困难在等着我。
2014/8 - 2017/1,第一份工作移动 Web 前端。这个时候主要是用 jQuery 开发业务,至于 React、Vue 这些框架都还没有,JS 模块化主要靠 RequireJS 或 SeaJS,工程化是 Grunt 或 Gulp 这些工具。可以说,我是花了一年左右的时间,才把这些东西怎么用,怎么组装搞明白的。因为公司里没有技术氛围,我属于 free farm 状态,技术学习途径主要依然是看博客和看书,还处于「量变」积累的过程。
2017/1 - 2018/4,第二份工作 PC Web 前端。这个团队就比较正规了,还是 React 技术栈,这里我算是进入正规军了。构建规范化、流程化,也有技术分享了。不过,后面因为部门变动,我就离开了。这个阶段开始买电子书而不买纸书了,因为搬家太重,而且电子书可以搜索。
2018/5 - 2019/3,创业,前端。这依然是一份目前我面对的最困难的一份工作,因为是创业,我什么都要做,而且没有规范,没有指标,没有工作量。唉,很是心类的一个阶段。不过,这个时候的我,已经不会再对自己的发展迷惘了,心中有了大概的学习计划。不管公司如何,我自己总是在进步的,当时想法就是这么简单。离开的时候,我心里是比较从容的,因为我已经知道我要往全栈的方向发展了。
2019/3 - 2022/5,Java 后端,转型了。后端知识之复杂,体系之庞大,是我之前没有遇到过的,是全新的领域,我之前的那点 Nodejs 储备量根本不够用了。所以,我用了几乎一年的时候,把常见的框架、组件都搞明白怎么用了,第二年的时候才有了点后端开发的感觉。我很少买书了,除了经典书籍,我现在学什么基本都通过 Google 和 Youtube 学习了,而且现在微信读书有太多电子书资源了!
可以看到,我几乎每去一家公司,都是去学东西的,也总能学到东西。
对于大部分开发任务,我们没有必要掌握一门编程语言的所有细节。好办法是花尽量少的时间去掌握日常开发所需要的 80%,而不是花 80% 时间去掌握平时比较少见的 20%。
比如我在用 Java 开发的时候,其实 2 天我就把 Java 的语法过了一遍,然后基本就可以开发业务了,但是,直到一年后,我才能完全明白这些是怎么回事。
学习一门技术的时候要快速掌握其最核心的部分,抛弃细枝末节,直接动手实现目标,中途遇到不会的再 Google 即可。这样才是高效快速的学习方式(缺点就是基础可能不牢靠,需要自己整理知识图谱来巩固)。
学习过程从来不是线性的,所以如果你看别人给出一个最佳的学习路径,比如看完某本书,然后再看另外一本更深入的,然后再买个什么课的,就比较坑了。
知识的学习一定是螺旋向上的。学习任何技术都不会是线性的,是螺旋式的,要积累量变到质变的能量。这个能量,可能是累计的代码量,可能是若干个知识点等等。是理论和实践相互交叉在一起向上的。
还要逐渐把自己心中的 Roadmap 勾勒出来,不断完善。大脑里保存一份记忆宫殿,在用到的时候,取走对应的那部分,我努力做到这个样子。
我觉得要学一门新技术,最快的办法就是看完文档之后,自己试着做些小项目,如果还想继续深入就做一些更大的项目,或是参与到开源项目中去。而绝对不是细细地扣着文档来学语法或者功能点,这样不是不行,而是有点笨拙。一定是做的时候学,是最快的!光看语法有什么用,没有项目要自己去找项目,技术不练怎么精进。
没有参与开源项目是我上面说的我在迷茫时期犯的最大的错
通常是技术不难学,但是领域知识比较难学,因为要实践某些领域知识需要相关环境和工作内容。
比如前端,JavaScript 难么,不难,但是学完你就能干前端了么?还差得远呢。什么 npm、DOM、MVVM 框架一上来,你就懵了。
比如后端,Java 不难,但是学完你能干后端么?还需要数据库、框架、分布式知识的加持。
比如 DApp,Solidity 其实也不难,但是需要了解背后成体系的区块链知识。
上面说了「怎么」学习,现在说说「为什么」。
有人问过我,说他做了三年 Java,想要转 Go,但是又怕 Java 退步了。我其实觉得挺不可思议的,为什么会退步呢?
要知道学得越多,就会学得越快,当然前提是你之前的真的学会了。
我告诉他,退步说明你 Java 还不牢靠吧,但是如果只为了「守」住什么技术,而不去学别的技术,这恐怕是懒惰的表现。
努力是最大成功因素,绝大多数的人努力的程度,都还用不到去拼天赋的程度。编程其实有点像卖油翁的,最后,可能就是「无他,唯手熟尔」的境界。
很多新手还是像在学校一样,看书就从第一章开始看。不得不承认,出了学校,就已经不存在让你可以成体系学习一个东西的时间和机会了。工作中,全部都是业务的学习。
那怎么学呢?不停地写,遇到不会的就查!这是最快的学习方式。但是,这样获得的知识不牢靠,形成不了体系。这时候就需要自己整理知识图谱了,我是用的 Notion 做整理,获取知识途径可以是 Youtube、书、博客等等。不断地优化 Notion 的同时,就是自己不断思考和整理知识碎片的时候!
而且,看书要带着问题去看,看书是为了解决问题,而不是单纯地去学。
古人说,「学而不思则罔,思而不学则殆」,放在这里:只顾着学习新玩意,赶时髦技术,就会让自己忙忙碌碌,但却无所适从,甚至有让身体的勤劳掩盖头脑的懒惰的嫌疑;而空有想法,却从不去实践,只会让自己陷入焦虑,毫无意义。
有过两段迷茫的时期,一段是刚工作 free farm 的时候,一段是刚转型后端时候。虽然两段时间跨得比较久,但也有共同点,都是在我刚进入一个新领域的时候。
之前有一段时间,我就陷在这里。那时候光想着学概念,一点都没想着要找个实际项目来做。导致 TS 一直没学会,还不如我最近 2 个月做 GithubX 学的 TS 多呢。
那一年也不是自己学不进去后端的那些东西。就像前几年,前端的那些不也是学不进去么?比如当时想学重绘重排这些东西,到现在忘了;比如当时学的 Docker,直到真正做了后端才学会。
对于我来说,整体知识了解之后(一般是看书或者文档),直接开始上手写个小项目,遇到不会就查,记录在 Notion 上。项目做完会有正反馈,可以刺激大脑。积累一定量不会的知识之后,就可以进阶了,尝试把他们在 Notion 上归类。让知识成体系,解决上面说的知识不牢靠问题。
当然,即便整理出来了,也不能保证学会,还要时时练习才行,让大脑形成肌肉记忆。
现在我不会因为同时学很多东西,而忙乱得一匹了。做到慢就是快!
主要的原因还是我学会了整理知识:Notion 帮了我大忙,网络式的知识图谱,是我的第二大脑。这也是我已经不怎么写博客的原因了。我可以在1分钟内,找到我遗忘的东西。
如何构建知识图谱呢?不要给自己设限。比如最开始做前端的时候,学跨域的解决方案,看到可以 Nginx 转发,就觉得那不是我干的。到后来,Docker、数据库都会用了,人还是不要给自己设限。
我的复盘方式就是写周记,每天的想法都记在 Dayone 上,每周末对着 Dayone 整理一下这一周的学习成果。
我是从初中开始学英语,否没有学会音标。因为老师讲的不好,自己就没有学会。上学时,如果听不下去老师讲的,就只能自己看书,而英语这种东西又不是看书就能看会的。
而最近又在学英语,照着一个**大学老师的课程学音标,收获极大,绝对超过了我之前十几年对音标的理解。
真的感谢网络时代,让我又有了学习的快乐。
这篇文章是从原博客转载过来的,是2013年写的,有些不对的地方请指出。
这是一篇我自己关于负margin的理解,今天因为做项目用到了负margin,几经找资料,终于搞懂了,就赶紧记下来,免得忘记了!
盒子最后的显示大小等于盒子的border
+padding
+正margin
,而负margin
不会影响其大小。
margin为负且盒子static时:
怎么样实现上面菜单栏的选中状态下没有下边框的效果?
一般的思路是每个菜单栏设置边框,选中的状态没有下边框
其实还可以这样,边框不是上面菜单栏的,而是下面内容块的:
明白了把,所以只要给菜单栏设置margin-bottom:-1px
就可以让下面的内容块上移1px,刚好让菜单栏的背景色盖住那个1px的边框。
如果选中状态没有背景色,就悲剧了:
请看 demo
现在看这个例子没有明显展示出负margin的能力,再看下面的
再看一个width没有设置,通过负margin加宽的元素的布局例子,这是很常见的例子,如果不用负margin,就会很麻烦呢
demo
因为BFC有这个特性:
元素在设定
width
时,添加border
、padding
、margin
会导致元素变宽;但是在没有设定width
时,元素会自动填满父元素,添加padding
、border
、margin
会使元素变窄,减少量等于他们三个之和。
demo
以上是网上资料总结,我的总结就一句话:left、top不论正负自己动,right、bottom不论正负别的元素动!正的向外,负的向内!考虑问题的时候还要考虑到盒子的特性问题!!
PS:遇到问题只要先想想什么是margin,margin的作用是什么,则负margin的工作原理则迎刃而解!
使用 webpack 已经将近一年了,期间用它构建过4、5个项目,踩过一些坑,现在用自己的理解记录下来。
我现在教你如何一步一步搭建 webpack 开发的多页面项目。本文项目地址在 https://github.com/riskers/generate-pages-tutorial 。
首先需要安装:
git clone https://github.com/riskers/generate-pages-tutorial
这里使用的是 webpack 1.x,webpack 2 见文末
注意每一步的
webpack.config.js
和pageage.json
cd 1_multi_pages
npm install
npm run build
查看 webpack.config.js
可以其实就是配置多个 entry 而已,可以看到 dist
下生成编译好的文件:
|--- dist
|--- page1
|--- main.js
|--- page2
|--- main.js
这里的目录层级和 entry 中的模块名(page1/main
、page2/main
)对应。
打开 page1.html
和 page2.html
就可以看到我们的js模块生效了。现在进入下一步!
通过上一步,我们已经解决了 JavaScript 模块的问题,而页面中还有 CSS 。webpack 默认是只处理 JavaScript 的,所以我们要引入 css-loader
和 style-loader
来处理 CSS。
cd 2_css
npm install
npm run build
{
test: /\.css$/,
loaders: ['style', 'css']
}
loader 是专门处理某些模块的处理器。webpack 只能处理 js ,为了能够处理 CSS ,就需要 css-loader
;而为了能够在页面中插入 CSS ,还需要 style-loader
。
打开 page1.html
就可以看到 css 生效了。
{
test: /\.less$/,
loaders: ['style', 'css', 'less']
}
如果使用的是 less ,就需要安装 less
和 less-loader
。
打开 page2.html
就可以看到 less 生效了。
{
test: /\.scss$/,
loaders: ['style', 'css', 'sass']
}
如果使用的是 sass ,就需要安装 node-sass
和 sass-loader
。
打开 page3.html
就可以看到 less 生效了。
module: {
loaders: [
{
test: /\.css$/,
include: ROOT + '/src/page4',
loaders: ['style', 'css', 'postcss']
}
]
},
postcss: function() {
return [autoprefixer]
}
如果使用的是 sass ,就需要安装 postcss-loader
,这里是以 autoprefixer
为例。
打开 page4.html
就可以看到 less 生效了。
以上方法都是用 JS 生成 CSS,但是实际上,我们需要的是 CSS 文件,可以使用 extract-text-webpack-plugin
来解决。
打开 page5.html
可以看到效果
上面2节我们已经掌握 JS 模块和 CSS 模块的处理,并且能够让 CSS 独立生成文件了,现在我们觉得每次修改代码然后 build
再刷新浏览器这个过程实在太慢了,而且也没必要每修改一行代码,就生成新文件,这是构建速度慢的主要原因。
webpack-dev-server
是 webpack 自带的一个开发服务器,支持热替换、代理等等功能。
cd 3_reload
npm install
npm run dev
打开 0.0.0.0:8888/page1.html
,你就可以看到页面了。而且无论你修改 main.js
、 style.css
或 tpl/page1.html
都会让浏览器自动刷新。
这里使用了:
pages/tpl
内文件时,会自动在 pages/html
内生成对应的文件require
html 文件,做到每修改一次 tpl 文件,浏览器自动刷新一次页面还有一点值得注意,因为 reload 功能是开发时才需要的,所以我们在 build
的时候要把这部分剔除,cross-env
和 DefinePlugin
的配合可以做到这点。
cross-env ENV=DEV webpack-dev-server --inline --hot --quiet --progress --content-base pages/html --host 0.0.0.0 --port 8888
process.env.ENV
这个环境变量注入 ENV
中new webpack.DefinePlugin({
'ENV': JSON.stringify(process.env.ENV)
})
main.js
中就可以区分是开发环境还是生产环境了:if(ENV == 'DEV') {
require('pages/html/page1.html')
}
如果你要在 webpack 中配置 ES2015 的开发环境,需要 babel 的帮助:
cd 4_es2015
npm install
npm run dev
然后在 webpack.config.js
中:
{
test: /\.js$/,
loader: "babel",
exclude: /node_modules/
}
注意 exclude: /node_modules/
很重要,否则 babel 可能会把 node_modules
中所有模块都用 babel 编译一遍!
并且,在根目录下新建 .babelrc
:
{
presets: [
"es2015",
"stage-0"
],
plugins: [
"transform-runtime",
"add-module-exports"
]
}
然后我们就可以写我们可爱的 ES2015 了:
import './style.css'
import { log } from '../common/index.js'
cd 5_library
npm install
npm run dev
CommonsChunkPlugin 是 webpack 自带的插件,能够把公有模块提取出来:
plugins: [
new webpack.optimize.CommonsChunkPlugin('common','common.js')
]
在 HtmlWebpackPlugin
中加入 common/index.js
模块,我们就可以看到 common/index.js
模块被提取到了 common.js
中。否则的话,page1/main
和 page2/main
中都会打包 common/index.js
。
实际开发中,我们还会在页面使用 <script>
引入一些常用库,比如 jQuery
,那么我们需要
// 当在 js 中 require jQuery 时,实际上是指向 `window.jQuery`
externals: {
jQuery: 'window.jQuery'
}
然后我们就可以在 page1/main.js
中使用 jQuery
了:
import $ from 'jQuery'
$('body')
.append('<p>this is jQuery render</p>')
.css('color', '#FFF')
当然,对于 jQuery
这种每个页面都会使用到的库来说,每次都要 import $ from 'jQuery'
显得很不优雅。可以这样配置:
plugins: [
new webpack.ProvidePlugin({
$: 'jquery'
})
]
这样就可以像 page2/main.js
中那样了:
$('body')
.append('<p>this is jQuery render</p>')
.css('color', '#3f3f3f')
经过上面几个步骤,我们基本上已经完成了 webpack 的开发环境搭建,但是 pages 里全是静态页面,而我们后端实际上使用的可能是 PHP、Python 甚至是 Node 渲染的动态页面。
现在我们要解决的问题是把现有的 webpack 开发环境和已有的后端环境结合起来,我们这里使用的是 webpack-dev-server
的 proxy
功能:
devServer: {
proxy: {
'*': {
target: 'http://localhost:8000'
}
}
}
cd 6_proxy
npm install
npm run dev
为了模拟一个后端环境,这里使用 PHP 自带的 server 在 8000 端口开启服务:
php -S 127.0.0.1:8000 -t ./pages/html
然后打开 http://0.0.0.0:8888/page1.php
就可以看到页面被 webpack-dev-server 代理过来了。你可以在 pages/html
下得到正式的已经配置好资源路径的页面。
执行 npm run build
后,你会在 pages/html
下得到相应 CDN 地址的资源路径,CDN 地址可以在 npm scripts
下配置:
"scripts": {
"build": "cross-env CDN=http://cdn.a.com webpack"
}
到此为止,一个 webpack 搭建的多页面开发环境已经完成了,还有一些与 webpack 无关的话题要注意一下。
ESLint 是代码检查工具,这里不多介绍了。如果你使用的是 es2015 ,记得安装 babel-eslint
就好。
pre-commit 是一个很好用的工具,你可以使用它强制性地让团队成员在 commit 代码前执行任何命令(ESLint、测试等等)
"scripts": {
"lint": "eslint app/src/ app/stylesheets"
},
"precommit": [ "lint" ]
注意使用时要先安装:
node node_modules/pre-commit/install.js
我还建立了项目 https://github.com/riskers/generate-pages ,它包括了最最基本的 webpack 开发多页面骨架,感兴趣的也可以看看。由其是 map.js 和 模板文件的映射,这种思路应该可以帮你少写很多代码。
最重要的是,赶紧动手开始使用 webpack 吧!
希望这份教程帮到了你 -_-
最近,webpack 2 终于正式发布了,之前大概看过他的 beta 版本,但是没有正式发布之前我始终没有去实践,这两天得空把 https://github.com/riskers/generate-pages-tutorial 上的代码更新到了 v2 版本。总体来说,webpack2 默认支持 module、提供 tree shaking 是2个比较让我在意的新功能,其他的以后再细细研究了。
最近开发了一款增强 Github 体验的 Chrome 插件 - GithubX
代码也开源了: https://github.com/riskers/github-plus-extension
解决了我个人的一个痛点:Github 原生对 stars 和 gists 无法分组和打标签,这样让我每次在查一些不常用库的时候,总是会花很长时间查找。
可以在后台在 star 分组和打标签了:
然后在 github 页面展示出来:
上面是对已经 star 过的项目,如果你有新的 star,会自动弹窗让你操作:
这个项目其实应该 2 年前就做完了,一直拖到现在,也主要是自己的拖验症比较严重。
这次迭代了 3 个版本,差不多 2 周一个版本,主要用业务时间来做这件事情。
学到了什么
复盘
这次技术选型的思考:一开始想得太多,导致进度很慢。
存储选型
最开始本地数据是存在 localStorage 里的,不过后面分组的时候,要做到类似 One To Many 的关系查询,会很麻烦,我勉强克服了。直到要打标签的时候,我绝望了,因为这是 Many To Many 关系,用 localStorage 太麻烦了。于是,使用 indexedDB 解决这种问题。
状态管理选型
一开始我认为这只是一个小项目,没打算上 redux。所有的状态管理都放在 context 里,结果,因为数据管理太复杂,使用 context 反而让状态越来越复杂,得不偿失,于是用 redux 替换。查看这个 commit会更清楚。
说了这么多,如果你感兴趣,不妨试试吧。下载地址
八年了,借着离开北京这个机会,总结一下职业生涯和自己的生活
上一篇文章,也总结了我的工作历程,不再赘述。
一个公司的岗位可以分为前端、后端、数据、产品等等,但是人不应该给自己设限。
我一路走来,各个类型的公司都待过,大公司,创业公司,二线公司。前端、后端也都做过。也都是不想自己一直在一个领域一直做下去,我想多看看,多学学。
庆幸自己切了好几次技术栈,从前端到后端,再到 Web3,每次都抱着热情,都是那个时候自己最喜欢干的。
大多数程序员都认为自己不用关心业务数据,我也是,一直到去年,我的目标还是把编程技能搞好。
直到最近半年,我才考虑行业的问题,思考自己的职业发展,思考自己的未来。看来,不光是知识是螺旋的,人的认知也是。
我把自己的人生当作一家公司来经营,建立了 slack 频道:
除非你为公司创造的价值一直变大。其实,就算你做到了这一点,只要有人不这么认为,也做不到一直涨。
这也是有理论依据的,是之前看一个 HR 分享的观点。
一定不要留恋所谓「大厂」或者所谓「高级职位」的光环,在你没有利用价值的时候,一脚就被踢开,没有谁是不可替代的。
而且,大厂就一定是人很多,在饼能不断做大的时候,大家可以其乐融融一起冲。但是,在业务停滞甚至萎缩的时候,吃相就一定很难看了,职场就是江湖。
在一家公司工作的理由,只有两个:钱多或者能学到东西,如果都没有,尽早撤吧。公司付给你薪水,而你的劳动恰好能为公司创造价值,这样就能双赢,就能长久合作下去。
养老,有人说找个不忙的公司养老,比如外企。
但是,自己可不能停止学习啊。没办法,这一行就是这样,停止学习就是自己扼杀了自己的职业生涯。
还是要骑驴找马的,万一找到合适的呢?
其实,我是很建议大家每年都出去看看机会的。多面面,看看外面的行情,也了解了解自己的行情。
不要在一个地方呆得太安逸了,小心「死于安乐」。
对于工作的态度,我比较特别,并不是像大多数人那样,希望在这家公司升职加薪。我总是平常心,抱着上学的态度来到一家公司,当我觉得没什么东西可以学的时候,就应该毕业离开了。
有人说,长大成熟就是和自己和解的过程。30 岁了,在北京的八年,已经深深地认识到自己能做成什么事,做不成什么事,自己的能力上限在哪里,哪些事情是努努力能做到,哪些事是只能想想的。
这次,我选择换个赛道看看别的机会。希望这是个好的 30 岁开始。争取下次换工作,不用面试!
为什么不买房?当年一直想在北京有个房子,真正有了钱可以买的时候,又犹豫不决了。就是觉得太亏了!在北京一个首付,在老家差不多直接全款大平层了,何必呢?
所以,累了,回家了。
本来想着最多最多在北京待三年,结果三年又三年,三年又三年,八年了。
我和媳妇决定绝不会在北京买房,买了房就失去了很多种可能,背着房贷会让自己裹步不前。当然,这也是因为穷的。
我不会赌,赌的机会成本太大了。万一输了,就万劫不复。
最后让我下定决心一定要离开的北京的事情是,连续两年年底,被租房中介坑了,报警的过程中遇到的事情,让我对北京再无任何信任和留恋。
我其实考虑过润到外面,但是,成本太大,拖家带口的,先不考虑。
我和他们比就啥也不是,苦也算不得什么:
前两天读《出师表》,到「苟全性命于乱世,不求闻达于诸侯」的时候,我眼里有泪花了。是年纪到了么?
一路走来,有时候想想,觉得自己特别不容易。在北京这个地方,人生地不熟的,真的很难。
所有人都在看着你飞得高不高,没有人管你飞得累不累。
我在上大学之前,是不知道世界上有苹果这家公司的,是室友的一个 iTouch 让我知道这个小小的东西有多了不起。也不知道手机是 3G 的,想想还是挺白痴的。
因为来自小地方,所以我知道「钱」的重要性,也特别能省钱和攒钱。我庆幸自己进入社会八年之后,还有一份少年的心,一份没有被污染的心。可能,这也叫幼稚吧。
现在回头看,我幸运地选择了编程这条路。这对于我这种没人脉、没资源、情商又不高的人来说,简直是最好的选择了。只要一台电脑,我就可以干活了。而且,从我现在的状态来看,我可以一直干下去!
毕业八年,给自己鼓掌!
30 岁,对自己认识越来越清晰,知道自己想要什么生活。所以,安逸和自由的生活!
也对「财务自由」有了另一份理解,或许,在满足自己生活的条件下,随意地、舒服地做一份工作,这也是一种自由吧。
其实自己兴趣真的挺广的,还有时间的话,还想要学学:
今年找工作,发现 remote 的工作机会比自己想像的要多得多。
我见有人把 remote 取名叫旅行办公,WOR(working on road)。我之后会一直全职远程,不会再坐班。因为一旦开始,就回不去了。
为什么选择这家公司呢?因为看好 Web3 行业,薪资也还满意,在加上可以 remote,我就直接回家乡了。至少也能学到东西,先进入这个行业再说。
争取做个斜杠青年或者中年,工作的时候是工程师,下班是自媒体?不知道,试试呗。毕竟在家办公,时间很充裕。
我好像工作以来,从来没有为钱烦恼过,明明赚得也不多,可就是不烦恼,可能是心态好吧,呵呵。
我一个普通本科,放在人堆里普通得不能再普通的人,能够完全靠自己在西安买了房子,结了婚,这已经是我的极限了。
凭什么还要求那么多呢?
自己又不是名牌高校毕业,没出国深造过,英语不好,情商一般,资源没有的人,能活成现在这个样子已经不错了。
承认自己的平庸吧,未来加油!毕竟,怎么过不是一辈子呢。
A declarative, efficient, and flexible JavaScript library for building user interfaces.
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google ❤️ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.