-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsearch.xml
248 lines (248 loc) · 172 KB
/
search.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
<?xml version="1.0" encoding="utf-8"?>
<search>
<entry>
<title><![CDATA[使用requestAnimationFrame实现通知栏滚动]]></title>
<url>%2F2020%2F05%2F19%2F%E4%BD%BF%E7%94%A8requestAnimationFrame%E5%AE%9E%E7%8E%B0%E9%80%9A%E7%9F%A5%E6%A0%8F%E6%BB%9A%E5%8A%A8%2F</url>
<content type="text"><![CDATA[requestAnimationFrame是什么window.requestAnimationFrame() 告诉浏览器——你希望执行一个动画,并且要求浏览器在下次重绘之前调用指定的回调函数更新动画。该方法需要传入一个回调函数作为参数,该回调函数会在浏览器下一次重绘之前执行。如果想让浏览器在下一次重绘前继续更新下一帧动画,需要在回调函数中再次调用window.requestAnimationFrame()。回调函数执行次数通常是每秒60次,但在大多数遵循W3C建议的浏览器中,回调函数执行次数通常与浏览器屏幕刷新次数相匹配。为了提高性能和电池寿命,因此在大多数浏览器里,当requestAnimationFrame() 运行在后台标签页或者隐藏的<iframe> 里时,requestAnimationFrame() 会被暂停调用以提升性能和电池寿命。回调函数执行时,会被传入一个时间戳参数,表示当前执行回调函数的时刻。如何使用1234let frameId = window.requestAnimationFrame(callback)// 停止动画window.cancelAnimationFrame()优缺点和setTimeout(callback, 16)类似,但又不同,requestAnimationFrame是通过浏览器刷新频率决定执行的最佳时机,动画不会出现卡顿现象。优点动画保持60fps(每帧16ms),浏览器内部决定渲染的最佳时机API简洁标准,维护成本低缺点动画的开始/取消需要开发者自己控制浏览器标签未激活时,一切都不会执行老版本浏览器不支持IE9Node.js不支持,无法用在服务器的文件系统事件实现滚动通知栏以下基于React实现了一个文字可滚动的通知栏:1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677import React, { Component } from 'react'export default class NoticeBar extends Component {  constructor (props) {    super(props)    this.state = {      closed: false    }    this.marquee = null    this.aFrameId = null  }  componentDidMount () {    if (window.requestAnimationFrame && this.props.marqueeText) {      let scrollWidth = this.marquee.parentElement.offsetWidth      let textWidth = this.marquee.offsetWidth      let right = 0      let marquee = () => {        right++        if (right >= textWidth - scrollWidth + 30) {          right = 0        }        this.marquee.style.right = right + 'px'        this.aFrameId = window.requestAnimationFrame(marquee)      }      if(textWidth > scrollWidth) this.aFrameId = window.requestAnimationFrame(marquee)    } }  componentWillUnmount () { // 为了提高性能,组件卸载前,记得停止动画    window.cancelAnimationFrame(this.aFrameId)    this.aFrameId = null  }  onClose = () => {    if (this.aFrameId) {      window.cancelAnimationFrame(this.aFrameId)      this.aFrameId = null    }    this.setState({ closed: true })  }  render () {    const { mode, marqueeText, onClick, children } = this.props    const { onClose } = this    const { closed } = this.state    if (closed) return null    return (      <div className="notice-bar">        <div className="notice-bar-icon"/>        <div className="notice-bar-content">          {!!children && children}          {            !!marqueeText && !children && (              <div className="notice-bar-marquee-wrap">                <div className="notice-bar-marquee" ref={ele => this.marquee = ele}>                  {marqueeText}                </div>              </div>            )          }        </div>        {          mode === 'link' && (            <div className="notice-bar-operation" onClick={onClick}>              <i className="icon-link"/>            </div>          )        }        {          mode === 'closable' && (            <div className="notice-bar-operation" onClick={onClose}>              <i className="icon-close"/>            </div>          )        }      </div>    )  }}123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960.notice-bar{  width: 100%;  height: 30px;  border-radius: 6px;  background: #ffe37e;  font-size: 12px;  color: #222;  display: flex;  align-items: center;  padding: 0 0 0 9px;  & > .notice-bar-icon{    background: url("../img/notice-bar-icon.png") center no-repeat;    background-size: 100%;    width: 9px;    height: 9px;    margin-right: 6px;    flex-shrink: 0;    flex-grow: 0;  }  & > .notice-bar-content{    margin-right: 12px;    white-space: nowrap;    overflow: hidden;    text-overflow: ellipsis;    flex: 1;    & .notice-bar-marquee-wrap{      overflow: hidden;      & .notice-bar-marquee{        white-space: nowrap;        display: inline-block;        position: relative;      }    }  }  & > .notice-bar-operation{    height: 100%;    display: flex;    & .icon-close{      background: url("../img/notice-bar-close.png") center no-repeat;      background-size: 100%;      width: 12px;      height: 12px;      display: block;      align-self: flex-start;      flex-shrink: 0;      flex-grow: 0;    }    & .icon-link{      background: url("../img/notice-bar-link.png") center no-repeat;      background-size: 100%;      width: 12px;      height: 12px;      display: block;      align-self: center;      flex-shrink: 0;      flex-grow: 0;      margin-right: 12px;    }  }}]]></content>
<categories>
<category>Web前端</category>
</categories>
<tags>
<tag>React</tag>
<tag>requestAnimationFrame</tag>
</tags>
</entry>
<entry>
<title><![CDATA[什么是thunk?]]></title>
<url>%2F2019%2F05%2F07%2F%E4%BB%80%E4%B9%88%E6%98%AFthunk%EF%BC%9F%2F</url>
<content type="text"><![CDATA[问:什么是thunk?答:第一次听到这个词是通过redux-thunk言归正传,当第一次听到Redux Thunk时,令人非常困惑。很可能就是因为thunk这个词。所以我们首先要弄清楚这一点。thunkthunk是函数的另一个词,但它不仅仅只是我们所认识的函数。它是另一个函数返回的一个特殊的函数。如下所示:123456function wrapper_function() { // 这就是thunk,因为它把一些任务推迟做 return function thunk() { console.log('do stuff now'); };}你可能已经见过这样的代码,只是你不把它叫做thunk。如果想要执行“do stuff now”部分,你必须调用两次wrapper_function()()。redux-thunk那么如何应用在Redux。如果熟悉Redux,下面这些概念对你来说应该不陌生:actions,action creators,reducers和middleware。如果不熟悉的话,建议先看下Redux基础教程。在Redux中,actions只是一个普通的JavaScript对象,并且这个对象中包含type属性。除此之外,可以包含任何你想要的——描述要执行的action。1234567// 1. 普通对象// 2. 有type属性// 3. 其他任意内容{ type: 'USER_LOGGED_IN', username: 'dave'}因为一直手工编写这些对象很烦人(更别说容易出错),Redux通过“action creators”这个概念解决:123456function userLoggedIn() { return { type: 'USER_LOGGED_IN', username: 'dave' };}两个完全相同的action,只是现在抽象了一层,你需要通过调用userLoggedIn函数创建。如果想在应用的不同位置触发同样的action,可以很方便的使用action creator。烦人的ActionsRedux所谓的“actions”其实并没做任何事,它们只是普通的对象。如果让他们做点事岂不是更酷?比如说,API调用或者触发别的actions?因为reducers应该是纯函数,不会改变其作用域范围之外的任何东西,所以我们不能在其内部做API调用或者触发actions。如果你希望action执行某些操作,则这部分代码存在某个函数。这个函数(thunk)就是这一系列操作。如果action creator能够返回一个函数(一系列的操作)而不是一个action对象岂不是更好,就像下面这样:12345function getUser() { return function() { return axios.get('/current_user'); };}如果有一种方法可以告诉Redux把函数当作actions处理。当然,这正是redux-thunk做的:它是一个middleware,系统中所有的action都会经过它,如果action是一个函数,就会调用那个函数。上面那一小段代码中遗漏了一些东西,那就是Redux会传递两个参数给thunk函数:dispatch:可以用来触发新的actiongetState:访问当前的state12345678function logOutUser() { return function(dispatch, getState) { return axios.post('/logout').then(function() { // 假设我们声明了一个叫 userLoggedOut 的 action creator,现在我们触发它 dispatch(userLoggedOut()); }); }}依赖当前state,getState函数在决定是否获取数据还是返回缓存数据时非常有用。确实非常小的库redux-thunk库的所有代码如下:1234567891011121314151617function createThunkMiddleware(extraArgument) { return ({ dispatch, getState }) => next => action => { // 触发每个action这里都会被调用 // 如果是函数就调用它 if (typeof action === 'function') { return action(dispatch, getState, extraArgument); } // 否则继续按正常处理 return next(action); };}const thunk = createThunkMiddleware();thunk.withExtraArgument = createThunkMiddleware;export default thunk;如果项目中安装了redux-thunk,并且把它当成中间件,触发的每个action都会进入这部分代码。如果action是函数就调用那个函数(不管函数返回什么),否则将它传递给下一个中间件,最终会交给Redux(netx(action)完成这部分工作)。项目中使用redux-thunk如果项目中已经安装了Redux,只需两步就可添加redux-thunk。首先,安装包:1npm install --save redux-thunk然后在使用redux的代码部分,引入redux-thunk中间件,添加到Redux中。12345678910// 你可以已经引入了createStore,还需要引入applyMiddlewareimport { createStore, applyMiddleware } from 'redux';// 引入`thunk` 中间件import thunk from 'redux-thunk';import rootReducer from './reducers/index';// 使用applyMiddleware创建thunk中间件const store = createStore( rootReducer, applyMiddleware(thunk));确保thunk作为applyMiddleware参数调用,否则不起作用。这样你已经完成了所有设置,现在可以dispatch任何函数。]]></content>
<categories>
<category>React</category>
</categories>
<tags>
<tag>React</tag>
<tag>redux</tag>
<tag>redux-thunk</tag>
<tag>thunk</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2017:清白之年]]></title>
<url>%2F2017%2F12%2F30%2F2017%EF%BC%9A%E6%B8%85%E7%99%BD%E4%B9%8B%E5%B9%B4%2F</url>
<content type="text"><![CDATA[转眼又到了年终,我原本不想写今年的总结,因为不想让自己内心再经历这一段痛苦的回忆。这一年,经历了太多,也失去太多。2016年12月底,我和朋友定好了元旦去黄山看新年日出,突如其来的噩耗,让我不知所措。堂姐告诉我,父亲得了很重的病,当时,我脑子一片空白。后来我打电话问我姐具体的情况,电话中,我没控制住自己的情绪,姐安慰我说,要等明天去县医院的检查结果。那一晚,我一直祈祷事情没那么糟。第二天检查完,姐姐让我定好江苏省肿瘤医院附近的宾馆,我就感觉到父亲的病情很不好。当晚,爸妈、姐姐、二叔一起到了南京,我看到县城医院核磁检查报告:胰腺癌晚期,最严重的癌症,这对于我们全家是一次沉重的打击。1月4号,父亲接受了手术治疗,手术很成功。从那开始,我白天在公司工作,晚上回医院陪护,工作的时候也不在状态,还一直安慰自己,“一切都会好起来”。就在我以为一切真的会好起来的时候,母亲的胃疼的越来越严重,我陪她去鼓楼医院做了胃镜检查,结果很不理想,”怀疑胃Ca”,但还需要等病理报告。提着心过了一周,病理报告确诊,赶紧带着母亲去鼓楼医院找医生。因为医院需要家属签字,所以作为儿子的我,担起照顾母亲的责任,姐姐留下来照顾父亲。1月22号,母亲接受手术治疗,因为正好快要过年,母亲刚做完手术,所以我陪着母亲在鼓楼医院过了一个今生难忘的年。父母术后身体渐渐恢复,为了能照顾到他们,劝他们去姐姐家住段时间,他们最终也接受了。从这之后,我白天努力集中精力工作,虽然人偶尔还是会不在状态。每天晚上我都会给他们打一个电话,听到他们的声音,我的心才能平静。鼓楼医院的医生让我联系省肿瘤医院为我妈安排化疗,而我爸的主治医生不建议给我爸化疗,虽然不愿接受这个现实,但真的只能听天由命。每次来南京化疗,我都会早早地站在医院那边的路口等她,每次看到她,我都难掩心中的泪水。看到医院那么多接受化疗的病人,我能体会到那种痛苦,吃不下东西、呕吐,常人也无法忍受。四月份开始,父亲渐渐感觉腹部开始疼痛,因为当时手术的时候就已经确定转移了,我知道应该是复发了,我们也安慰他,说是术后炎症,带他去挂了消炎水。然而,疼痛还是会让他吃不下饭,渐渐的,人越来越消瘦,我因为工作,也很少回去看他,他也不让母亲和二叔打电话给我。每次接完家里的电话,我都控制不住自己的情绪,因为我觉得对不起他们,没尽到做儿子的责任,平时对他们的关心太少。9月23号晚上父亲离开了我们,我也不能再叫他一声爸爸,但我永远记得父亲走之前交代给我的两件事。树欲静而风不止,子欲养而亲不待。现在回想,失去了才知道痛的滋味,遗憾太多,这些遗憾也会跟着我一辈子,我所能做的,就是好好照顾我的母亲,尽我最大的努力。虽然今年经历了太多痛苦,但他们不是我颓的理由。书没读几本,新知识也没学多少,发现自己越来越失去竞争力,也时常自我反省。对我来说,2018年会是一个新的开始,我要以全新的姿态,勤奋工作,坚持学习,努力赚钱,以积极的态度面对生活。]]></content>
<categories>
<category>随笔</category>
</categories>
<tags>
<tag>年终总结</tag>
</tags>
</entry>
<entry>
<title><![CDATA[flexbox布局的响应式表格]]></title>
<url>%2F2017%2F07%2F06%2Fflexbox%E5%B8%83%E5%B1%80%E7%9A%84%E5%93%8D%E5%BA%94%E5%BC%8F%E8%A1%A8%E6%A0%BC%2F</url>
<content type="text"><![CDATA[HTML table元素做响应式表格是非常难用的,我们需要大量的样板和嵌套的HTML来解决这样一个简单的问题。我们来探讨一种使用div和Flexbox的替代方法。这将给我们带来的好处是能够创建在所有尺寸屏幕上都看起来很棒的响应式表格。首先,解决方案将使用SUIT CSS以模块化方式写入Sass。我们将使用一些Sass库帮助我们完成任务。autoprefixer用于帮助我们生成必要的Flexbox CSS供应商前缀,以及Breakpoint来帮助我们做media query。如果你喜欢用CSS,请随时将生成的CSS从链接复制到Sassmeister示例。我们需要的是构建Table组件的3个基本类。首先,我们需要Table类,它将使用Flexbox来使所有的子节点(行)按列布局。接下来,我们需要一个Table-row类,它将使用Flexbox来使其所有的子节点(行/列)按行排列,而不需要包装。最后我们需要Table-row-item,它基本上是表格组件的一个单元格。现在,我们需要的是一个Table-header类,我们可以添加到任何行元素,以给它一个标题的样式。根据这些标准,我们可以为我们的组件编写HTML和Sass,如下所示。1234567891011121314151617181920<div class="Table"> <div class="Table-row Table-header"> <div class="Table-row-item">Header1</div> <div class="Table-row-item">Header2</div> <div class="Table-row-item">Header3</div> <div class="Table-row-item">Header4</div> </div> <div class="Table-row"> <div class="Table-row-item" data-header="Header1">row1 col1</div> <div class="Table-row-item" data-header="Header2">row1 col2</div> <div class="Table-row-item" data-header="Header3">row1 col3</div> <div class="Table-row-item" data-header="Header4">row1 col4</div> </div> <div class="Table-row"> <div class="Table-row-item" data-header="Header1">row2 col1</div> <div class="Table-row-item" data-header="Header2">row2 col2</div> <div class="Table-row-item" data-header="Header3">row2 col3</div> <div class="Table-row-item" data-header="Header4">row2 col4</div> </div></div>12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455@import "breakpoint";.Table { $light-color: #ffffff; $dark-color: #f2f2f2; $md: 500px; display: flex; flex-flow: column nowrap; justify-content: space-between; border: 1px solid $dark-color; font-size: 1rem; margin: 0.5rem; line-height: 1.5; // .Table-header &-header { display: none; @include breakpoint($md) { font-weight: 700; background-color: $dark-color; } } // .Table-row &-row { width: 100%; &:nth-of-type(even) { background-color: $dark-color; } &:nth-of-type(odd) { background-color: $light-color; } @include breakpoint($md) { display: flex; flex-flow: row nowrap; &:nth-of-type(even) { background-color: $light-color; } &:nth-of-type(odd) { background-color: $dark-color; } } // .Table-row-item &-item { display: flex; flex-flow: row nowrap; flex-grow: 1; flex-basis: 0; padding: 0.5em; word-break: break-word; &:before { content: attr(data-header); width: 30%; font-weight: 700; } @include breakpoint($md) { border: 1px solid $light-color; padding: 0.5em; &:before { content: none; } } } }}在Sassmeister尝试这个例子根据此实现,我们可以轻松创建响应式表格。每行简单地将项目的数量分解成等于列。这使我们能够灵活地创建具有不同列数的表格。然而,同样的好处也有缺点。我们的表格组件的问题是每个列的宽度相同。如果我们有的列包含的数据比其他列更宽或更窄会发生什么呢?幸运的是,Flexbox也使得这个很容易实现。我们可以简单地添加一些实用的工具类来设置不同列的Flexbox增长率。123456// generate Flexbox grow-rate utility classes@for $i from 1 through 10 { .u-Flex-grow#{$i} { flex-grow: i; }}1234567891011121314151617181920212223242526272829303132333435363738<div class="Table"> <div class="Table-row Table-header"> <div class="Table-row-item u-Flex-grow2">Long Header1</div> <div class="Table-row-item">Header2</div> <div class="Table-row-item">Header3</div> <div class="Table-row-item">Header4</div> <div class="Table-row-item u-Flex-grow3">Longer Header5</div> <div class="Table-row-item">Header6</div> <div class="Table-row-item">Header7</div> </div> <div class="Table-row"> <div class="Table-row-item u-Flex-grow2" data-header="Header1">row1 col1</div> <div class="Table-row-item" data-header="Header2">row1 col2</div> <div class="Table-row-item" data-header="Header3">row1 col3</div> <div class="Table-row-item" data-header="Header4">row1 col4</div> <div class="Table-row-item u-Flex-grow3" data-header="Header5">row1 col5</div> <div class="Table-row-item" data-header="Header6">row1 col6</div> <div class="Table-row-item" data-header="Header7">row1 col7</div> </div> <div class="Table-row"> <div class="Table-row-item u-Flex-grow2" data-header="Header1">row2 col1</div> <div class="Table-row-item" data-header="Header2">row2 col2</div> <div class="Table-row-item" data-header="Header3">row2 col3</div> <div class="Table-row-item" data-header="Header4">row2 col4</div> <div class="Table-row-item u-Flex-grow3" data-header="Header5">row2 col5</div> <div class="Table-row-item" data-header="Header6">row2 col6</div> <div class="Table-row-item" data-header="Header7">row2 col7</div> </div> <div class="Table-row"> <div class="Table-row-item u-Flex-grow2" data-header="Header1">row3 col1</div> <div class="Table-row-item" data-header="Header2">row3 col2</div> <div class="Table-row-item" data-header="Header3">row3 col3</div> <div class="Table-row-item" data-header="Header4">row3 col4</div> <div class="Table-row-item u-Flex-grow3" data-header="Header5">row3 col5</div> <div class="Table-row-item" data-header="Header6">row3 col6</div> <div class="Table-row-item" data-header="Header7">row3 col7</div> </div></div>在Sassmeister尝试这个例子本文翻译自http://inlehmansterms.net/2014/10/11/responsive-tables-with-flexbox/]]></content>
<categories>
<category>前端开发</category>
</categories>
<tags>
<tag>CSS</tag>
<tag>flex</tag>
</tags>
</entry>
<entry>
<title><![CDATA[理解AngularJS Providers]]></title>
<url>%2F2017%2F05%2F27%2F%E7%90%86%E8%A7%A3AngularJS-Providers%2F</url>
<content type="text"><![CDATA[AngularJS 1.x提供了如下5个provider方法,使用这些方法能创建可被注入的服务。但是如何使用这些方法,以及这些方法之间有什么区别呢?所以这篇博客重点记录下这些方法的区别,来帮助自己理解它们。valuefactoryserviceproviderconstantvalue这个方法用来定义常量,并且能在运行阶段注入Controller中。比如我们可以创建一个简单的服务叫”clientId”,并提供一个用于身份验证的id。123456var myApp = angular.module('myApp', []);myApp.value('clientId', '12345678');myApp.controller('DemoController', ['clientId', function DemoController(clientId) { this.clientId = clientId;}]);12345<html ng-app="myApp"> <body ng-controller="DemoController as demo"> Client ID: {{demo.clientId}} </body></html>注:常量值可以为JS的任意基本类型factoryvalue方法非常简单,但缺少创建服务时常常需要的一些重要功能。所以factory方法增加了以下功能:使用其他服务的能力服务初始化延时初始化factory方法使用0个或多个参数(其他服务)的函数构建新的服务,并且此函数的返回值为工厂方法创建的服务实例。注:AngularJS中所有的服务都是单例,注射器injector最多使用这些provider方法一次来创建服务对象,并将它们缓存下来。同样的例子,我们通过factory实现一下:123456789101112131415myApp.factory('clientId', function clientIdFactory() { return '12345678';});// 这里只是返回一个字符串,可能用value更好点。如果需要运算,可以这样写myApp.factory('apiToken', ['clientId', function apiTokenFactory(clientId) { var encrypt = function(data1, data2) { return (data1 + ':' + data2).toUpperCase(); }; var secret = window.localStorage.getItem('myApp.secret'); var apiToken = encrpty(clientId, secret); return apiToken;}]);factory方法可以创建任何类型的服务,比如JavaScript基本类型,对象字面量,函数或者自定义类型的实例。service可能我们经常需要使用自定义类型编写面向对象的代码。12345678910function UnicornLauncher(apiToken) { this.launchedCount = 0; this.launch = function() { this.lanuchedCount++; };}myApp.factory('unicornLauncher', ['apiToken', function(apiToken) { return new UnicornLauncher(apiToken);}]);上面是通过factory实现对象实例化,然而这里需要我们自己初始化实例,所以Angular提供了service这个方法,它接受一个构造函数作为参数。1myApp.service('unicornLauncher', ['apiToken', UnicornLauncher]);providerprovider是核心方法,其他方法都是在基于它的语法糖。它的作用是在应用启动前提供可配置的API。通常用在一些可重用的服务,并且这些服务在不同的应用程序之间需要不同配置。123456789101112131415161718myApp.provider('unicornLauncher', function UnicornLauncherProvider() { var useTinfoilShielding = false; this.useTinfoilShielding = function(value) { useTinfoilShielding = !!value; }; this.$get = ["apiToken", function unicornLauncherFactory(apiToken) { // let's assume that the UnicornLauncher constructor was also changed to // accept and use the useTinfoilShielding argument return new UnicornLauncher(apiToken, useTinfoilShielding); }];});myApp.config(["unicornLauncherProvider", function(unicornLauncherProvider) { unicornLauncherProvider.useTinfoilShielding(true);}]);在应用启动之前,AngularJS调用config方法对服务完成配置,运行阶段会初始化所有的服务,所以,在配置阶段是访问不到服务,即不可注入。一旦配置阶段结束,就不能与provider交互。constant由于config函数在没有服务可用的配置阶段运行,所以value方法创建的值或对象也无法访问。所以为了在配置阶段能访问到一些常量,引入了constant函数,用法和value类似。特殊用途对象由Controller,Directive,Filter,Animation创建的对象,和factory写法类似,在函数中返回对象实例,Controller对象除外。总结注射器使用这些方法创建两种类型的对象:服务和特殊用途对象五种方法定义如何创建对象:value,factory,service,provider,constantfactory和service是最常用的方法,它们之间唯一的区别是,service对于创建自定义类型的对象更好,而factory方法可用生成JavaScript基本类型和函数provider是核心方法,其他所有方法只是其上的语法糖provider是最复杂的方法,除非你需要构建可重用的代码块,否则最好别用它除Controller之外,所有特殊用途的对象均通过factory方法定义]]></content>
<categories>
<category>前端开发</category>
</categories>
<tags>
<tag>AngularJS</tag>
<tag>Provider</tag>
</tags>
</entry>
<entry>
<title><![CDATA[青岛三日游攻略]]></title>
<url>%2F2017%2F05%2F13%2F%E9%9D%92%E5%B2%9B%E4%B8%89%E6%97%A5%E6%B8%B8%E6%94%BB%E7%95%A5%2F</url>
<content type="text"><![CDATA[市南区栈桥、海军博物馆、鲁迅公园、小青岛、第一海水浴场、八大关、音乐广场、五四广场、青岛奥林匹克帆船中心市北区啤酒一条街、青岛啤酒博物馆、天幕城、劈柴院、崂山Day1如果到酒店还早,就去登州路和或者台东步行街转转登州路啤酒一条街、青岛啤酒博物馆、天幕城Day2崂山南线:香港东路或东海东路 ——> 沙子口 ——> 登赢 ——> 流清河 ——> 太清宫 ——> 上清景区或巨峰景区(时间允许)交通:旅游专线、公交104、113、304如果回来比较早,可以去五四广场、奥林匹克帆船中心香港中路:家乐福、佳世客 家乐福对面阳光百货、佳世客后面海信广场,百丽广场Day3海底世界、八大关、栈桥、中山路、劈柴院民俗小吃街,王姐烧烤,高家锅贴,商业街:台东商业区、香港中路、中山路商业区、李村商业区中山路夜景很美中山路(美达尔烧烤)、圣弥额尔教堂]]></content>
<categories>
<category>旅行</category>
</categories>
<tags>
<tag>青岛</tag>
<tag>自由行</tag>
<tag>旅游攻略</tag>
</tags>
</entry>
<entry>
<title><![CDATA[RESTful web API文档生成器]]></title>
<url>%2F2017%2F05%2F10%2FRESTful-web-API%E6%96%87%E6%A1%A3%E7%94%9F%E6%88%90%E5%99%A8%2F</url>
<content type="text"><![CDATA[问:开发业务模块代码最重要的是什么?答:API接口文档如果你是后台开发,是否会有以下困扰:开发API接口,还要通过wiki写接口文档,严重影响效率接口不是一次就能定下来的,后续可能还要维护,所以还需要去修改文档如果你是前端工程师,是否有过下面的困扰:返回的数据怎么少字段,后台什么时候改了接口我们总是吐槽文档不全,文档写的不好,然而却没有想到开发的同时顺便把文档也写了。这篇博文的重点就是为大家介绍一款生成RESTful web API文档的神器——apidoc,GitHub 4000多star。这款神器通过源代码中的注释生成最终的文档,所以需要按照规范编写注释。安装和使用前言文中的例子全部使用Javadoc-Style编写,也可以在所有支持Javadoc的语言(如C#, Go, Dart, Java, JavaScript, PHP, TypeScript等)中使用。其他语言可以使用它们特定的多行注释。123/** * This is a comment. */安装1npm install apidoc -g使用1apidoc -i myapp/ -o apidoc/ -t mytemplate/使用mytemplate/下的模板文件,为myapp/目录下的所有文件创建api文档,并保存在apidoc/目录下。如果不带任何参数,apiDoc为当前目录以及子目录下的所有.cs, .dart, .erl, .go, .java, .js, .php, .py, .rb, .ts文件创建文档,并保存在./doc/目录。命令行参数12# 查看命令行参数apidoc -h列出部分重要参数:参数描述示例-f, –file-filters通过正则过滤出需要解析的文件(可以使用多个-f)。默认为.cs, .dart, .erl, .go, .java, .js, .php, .py, .rb, .ts。仅解析.js和.ts文件:apidoc -f ".*\\.js$" -f ".*\\.ts$"-i, –input源文件或项目目录apidoc -i myapp/-o, –output存放生成的文档的目录apidoc -o apidoc/-t, –template为生成的文档使用模板,也可以创建和使用自己的模板apidoc -t mytemplate/Grunt模块作者也为大家开发了一款grunt的打包工具http://github.com/apidoc/grunt-apidoc。1npm install grunt-apidoc --save-dev模板apiDoc默认模板:使用了handlebars,Bootstrap,RequireJS和jQuery为输出的api_data.js和api_project.js文件生成html页面。apiDoc默认使用一个复杂的模板,支持如下功能:版本管理:查看不同版本的API比较:查看一个API的两个版本之间的区别你也可以使用自己创建的模板,为apiDoc生成的文件api_data.js, api_project.js或者json格式的文件api_data.json, api_project.json。模板源代码:https://github.com/apidoc/apidoc/tree/master/template扩展apiDoc也可以扩展自己的参数,具体细节请查看apidoc/apidoc-core项目的lib/parsers/, lib/workers/目录。配置apiDoc提供了两种配置方式,要么在工程根目录添加apidoc.json文件,要么在package.json中添加apidoc字段。apidoc.json1234567{ "name": "example", "version": "0.1.0", "description": "apiDoc basic example", "title": "Custom apiDoc browser title", "url" : "https://api.github.com/v1"}package.json123456789{ "name": "example", "version": "0.1.0", "description": "apiDoc basic example", "apidoc": { "title": "Custom apiDoc browser title", "url" : "https://api.github.com/v1" }}apidoc.json配置字段字段描述name工程名称。如果apidoc.json中不包含此字段,则由package.json确定version工程版本号。如果apidoc.json中不包含此字段,则由package.json确定description工程描述。如果apidoc.json中不包含此字段,则由package.json确定title文档页面标题urlapi前缀,如:https://api/github.com/v1sampleUrl测试api方法的表单请求链接更多细节请查看@apiSampleRequestheader    title被包含的header.md文件的导航文本(查看Header/Footer)    filename被包含的header.md文件(必须为markdown文件)的文件名footer    title被包含的footer.md文件的导航文本    filename被包含的footer.md文件(必须为markdown文件)的文件名order输出的api名和api分组名的顺序列表,未定义名称的api自动在后面展示。"order": ["Error", "Define", "PostTitleAndError", "PostError"]apiDoc默认模板特定的配置字段字段类型描述templateforceLanguageString禁用浏览器自动语言检测,并设置一个特定的语言。例如:de, en。可选语言withCompareBoolean开启与旧版本API比较,默认truewithGeneratorBoolean在页尾输出生成信息,默认truejQueryAjaxSetupObject为Ajax请求设置默认参数Header/Footer在apidoc.json添加header和footer。12345678910{ "header": { "title": "My own header title", "filename": "header.md" }, "footer": { "title": "My own footer title", "filename": "footer.md" }}示例继承你可以将文档中多次使用的部分放到一个定义中。如下:12345678910111213141516171819202122232425262728293031323334353637383940414243444546/** * @apiDefine UserNotFoundError * * @apiError UserNotFound The id of the User was not found. * * @apiErrorExample Error-Response: * HTTP/1.1 404 Not Found * { * "error": "UserNotFound" * } *//** * @api {get} /user/:id Request User information * @apiName GetUser * @apiGroup User * * @apiParam {Number} id Users unique ID. * * @apiSuccess {String} firstname Firstname of the User. * @apiSuccess {String} lastname Lastname of the User. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * { * "firstname": "John", * "lastname": "Doe" * } * * @apiUse UserNotFoundError *//** * @api {put} /user/ Modify User information * @apiName PutUser * @apiGroup User * * @apiParam {Number} id Users unique ID. * @apiParam {String} [firstname] Firstname of the User. * @apiParam {String} [lastname] Lastname of the User. * * @apiSuccessExample Success-Response: * HTTP/1.1 200 OK * * @apiUse UserNotFoundError */继承只能包含一层,多层级将会降低行内代码的可读性,增加复杂度。版本控制保存之前定义的文档块,用于不同版本的接口比较,因此前端开发很容易看到哪些地方有变化。在修改接口文档之前,将历史文档块复制到文件_apidoc.js。所以在每个文档块设置@apiVersion非常重要。apiDoc参数结构体参数@apiDefine用于将文档块定义为可复用的整体,并且可以包含在api文档块中。除了其它定义块,一个定义的块中可以包含所有的参数(如@apiParam)。@api必须包含这个标记,否则apiDoc将会忽略文档块。定义块@apiDefine不需要@api。1@api {method} path [title]字段描述method请求方法名:DELETE, GET, POST, PUT, …。更多信息请查看Wikipedia HTTP请求方法path请求路径title 可选用于导航的简称123/** * @api {get} /user/:id */@apiDefine定义一个通用块或者权限块,每个块中只能有一个@apiDefine,使用@apiUse导入通用块。12@apiDefine name [title] [description]字段描述name块或值的唯一名称title 可选简称,只用在命名函数中,如@apiPermission和@apiParam(name)description 可选下一行开始的详细说明,可以多行,仅用在命名函数,如@apiPermission123456789/** * @apiDefine MyError * @apiError UserNotFound The <code>id</code> of the User was not found. *//** * @api {get} /user/:id * @apiUse MyError */123456789/** * @apiDefine admin User access only * This optional description belong to to the group admin. *//** * @api {get} /user/:id * @apiPermission admin */@apiDeprecated标记废弃的API1@apiDeprecated [text]字段描述text多行文本123456789/** * @apiDeprecated *//** * @apiDeprecated use now (#Group:Name). * * Example: to set a link to the GetDetails method of your group User * write (#User:GetDetails) */@apiDescriptionAPI的详细描述1@apiDescription text字段描述text多行描述文本123456/** * @apiDescription This is the Description. * It is multiline capable. * * Last line of Description. */@apiError返回的错误参数1@apiError [(group)] [{type}] field [description]字段描述(group) 可选所有参数按这个名称分组,如果未设置此字段,默认为Error 4xx,可以使用@apiDefine设置标题和描述{type} 可选返回类型,例如:{Boolean},{Number},{String},{Object},{String[]}(字符串数组)等field返回的标识符description字段描述1234/** * @api {get} /user/:id * @apiError UserNotFound The <code>id</code> of the User was not found. */@apiErrorExample返回的错误示例,输出为格式化的代码12@apiErrorExample [{type}] [title] example字段描述type 可选响应格式title 可选示例简称example详细示例,支持多行文本12345678/** * @api {get} /user/:id * @apiErrorExample {json} Error-Response: * HTTP/1.1 404 Not Found * { * "error": "UserNotFound" * } */@apiExampleAPI的使用示例,输出为格式化的代码12@apiExample [{type}] title example字段描述type 可选代码语言title示例简称example详细示例,支持多行文本12345/** * @api {get} /user/:id * @apiExample {curl} Example usage: * curl -i http://localhost/user/4711 */@apiGroup应该一直使用,定义API文档块所属的分组。分组用于生成输出中的主导航,结构定义不需要@apiGroup1@apiGroup name字段描述name分组名称,也用作导航名称1234/** * @api {get} /user/:id * @apiGroup User */@apiHeader描述传递给API的请求头,类似@apiParam,只不过输出文档在参数之上。1@apiHeader [(group)] [{type}] [field=defaultValue] [description]字段描述(group) 可选所有参数按此名称分组,如果未设置,默认为Parameter,你也可以在@apiDefine中定义名称和描述{type} 可选参数类型,例如:{Boolean},{Number},{String},{Object},{String[]}(字符串数组)等field变量名[field]带括号标识此参数可选=defaultValue 可选参数默认值描述 可选字段描述1234/** * @api {get} /user/:id * @apiHeader {String} access-key Users unique access-key. */@apiHeaderExample请求头参数示例12@apiHeaderExample [{type}] [title] example字段描述type 可选请求格式title 可选示例简称example详细示例,支持多行文本1234567/** * @api {get} /user/:id * @apiHeaderExample {json} Header-Example: * { * "Accept-Encoding": "Accept-Encoding: gzip, deflate" * } */@apiIgnore@apiIgnore块不会被解析,如果你在源代码中留下过期或者未完成的api,并且不想将其发布到文档中,这个标识符非常有用。将其放在块的顶部1@apiIgnore [hint]字段描述hint 可选忽略原因的简短信息1234/** * @apiIgnore Not finished Method * @api {get} /user/:id */@apiName应该永远使用,定义API文档块的名称,名称将用于生成输出文档中的子导航。结构定义不需要@apiName1@apiName name字段描述nameAPI的唯一名称,可以为不同的@apiVersion定义相同的名称1234/** * @api {get} /user/:id * @apiName GetUser */@apiParam定义传递给API的参数1@apiParam [(group)] [{type}] [field=defultValue] [description]字段描述(group) 可选所有参数按此名称分组,默认为Parameter,也可以在@apiDefine中定义名称和描述{type} 可选参数类型,例如:{Boolean},{Number},{String},{Object},{String[]}(字符串数组)等{type{size}} 可选变量大小信息{string{..5}} 最多5个字符的字符串{string{2..5}} 最少2个字符,最多5个字符的字符串{Number{100-999}} 介于100和999之间的数字{type=allowedValues} 可选变量允许的值{string="small"} 只能包含”small”的字符串{string="small","huge"} 包含”small”或”huge”的字符串{number=1,2,3,99} 允许为1,2,3,99中的一个值{string {..5}="small","huge"} 最多5个字符的字符串,并且只能包含”small”和”huge”field参数名[field]带括号标识此参数可选=defaultValue 可选参数默认值description 可选参数描述12345678910111213141516/** * @api {get} /user/:id * @apiParam {Number} id Users unique ID. *//** * @api {post} /user/ * @apiParam {String} [firstname] Optional Firstname of the User. * @apiParam {String} lastname Mandatory Lastname. * @apiParam {String} country="DE" Mandatory with default value "DE". * @apiParam {Number} [age=18] Optional Age with default 18. * * @apiParam (Login) {String} pass Only logged in users can post this. * In generated documentation a separate * "Login" Block will be generated. */@apiParamExample请求参数示例12@apiParamExample [{type}] [title] example字段描述type 可选请求格式title 可选示例简称example详细示例,支持多行文本1234567/** * @api {get} /user/:id * @apiParamExample {json} Request-Example: * { * "id": 4711 * } */@apiPermission权限名称,如果用@apiDefine定义名称,生成的文档将会包含额外的名称和描述1@apiPermission name字段描述name权限唯一的名称1234/** * @api {get} /user/:id * @apiPermission none */@apiSampleRequest此参数配合apidoc.json配置中的sampleUrl参数使用,如果配置了sampleUrl,所有API方法都将有api测试表单,并追加在@api结束位置。如果未配置sampleUrl,仅包含@apiSampleRequest的方法有测试表单。如果在方法块中配置了@apiSampleRequest url,这个url作为请求地址(当它以http开头,会覆盖sampleUrl)如果配置了sampleUrl,并且想在指定方法不包含测试表单,可以在文档块中配置@apiSampleRequest off。1@apiSampleRequest url字段描述url测试api服务地址@apiSampleRequest http://www.example.com@apiSampleRequest /my_test_path@apiSampleRequest off12345678910111213141516171819202122232425262728// Configuration parameter sampleUrl: "http://api.github.com"/** * @api {get} /user/:id */// Configuration parameter sampleUrl: "http://api.github.com"/** * @api {get} /user/:id * @apiSampleRequest http://test.github.com/some_path/ */// Configuration parameter sampleUrl: "http://api.github.com"/** * @api {get} /user/:id * @apiSampleRequest /test */// Configuration parameter sampleUrl: "http://api.github.com"/** * @api {get} /user/:id * @apiSampleRequest off */// Configuration parameter sampleUrl is not set/** * @api {get} /user/:id * @apiSampleRequest http://api.github.com/some_path/ */@apiSuccess成功返回的参数1@apiSuccess [(group)] [{type}] field [description]字段描述(group) 可选所有参数按此名称分组,默认为Success 200,可以在@apiDefine中定义名称和描述{type} 可选返回类型,例如:{Boolean},{Number},{String},{Object},{String[]}(字符串数组)等field返回标识字段description 可选字段描述1234567891011121314151617181920212223242526272829/** * @api {get} /user/:id * @apiSuccess {String} firstname Firstname of the User. * @apiSuccess {String} lastname Lastname of the User. */// 带(group)示例/** * @api {get} /user/:id * @apiSuccess (200) {String} firstname Firstname of the User. * @apiSuccess (200) {String} lastname Lastname of the User. */// 带对象示例/** * @api {get} /user/:id * @apiSuccess {Boolean} active Specify if the account is active. * @apiSuccess {Object} profile User profile information. * @apiSuccess {Number} profile.age Users age. * @apiSuccess {String} profile.image Avatar-Image. */// 带数组示例/** * @api {get} /users * @apiSuccess {Object[]} profiles List of user profiles. * @apiSuccess {Number} profiles.age Users age. * @apiSuccess {String} profiles.image Avatar-Image. */@apiSuccessExample成功响应的信息,按格式化代码输出12@apiSuccessExample [{type}] [title] example字段描述type 可选响应格式title 可选示例简称example详细示例,支持多行文本123456789/** * @api {get} /user/:id * @apiSuccessExample {json} Success-Response: * HTTP/1.1 200 OK * { * "firstname": "John", * "lastname": "Doe" * } */@apiUse包含一个@apiDefine定义的块。如果与@apiVersion一起使用,将包含相同的或最近的一个1@apiUse name字段描述name定义块的名称12345678910/** * @apiDefine MySuccess * @apiSuccess {string} firstname The users firstname. * @apiSuccess {number} age The users age. *//** * @api {get} /user/:id * @apiUse MySuccess */@apiVersion配置文档块的版本,也可以在@apiDefine中使用,具有相同组和名称的API,可以在生成的文档中比较不同的版本,因此你或前端开发人员可以回溯自上一个版本以来API中的更改1@apiVersion version字段描述version支持简单的版本控制(主版本号.次版本号.补丁号)。更多信息请参考http://semver.org/1234/** * @api {get} /user/:id * @apiVersion 1.6.2 */到此为止,你应该对apidoc熟悉了一大半了吧!其实作为开发者,一定要养成写注释、写文档的习惯,也是程序员进阶的必要条件。谨以此文鞭策自己。]]></content>
<categories>
<category>Node.js</category>
</categories>
<tags>
<tag>apidoc</tag>
<tag>RESTful API</tag>
<tag>文档</tag>
</tags>
</entry>
<entry>
<title><![CDATA[Node.js aes-128-cbc加密和解密]]></title>
<url>%2F2017%2F05%2F06%2FNode-js-aes-128-cbc%E5%8A%A0%E5%AF%86%E5%92%8C%E8%A7%A3%E5%AF%86%2F</url>
<content type="text"><![CDATA[Java程序中经常使用的AES加密模式是AES/CBC/PKCS5Padding,在Node.js中对应的是aes-128-cbc加密算法。为此,我们需要引入Node.js的crypto模块,详细说明请查看官方文档。12345678910111213141516171819202122232425262728293031323334353637383940'use strict';const crypto = require('crypto');const ALG_STRING = 'aes-128-cbc', KEY = 'wjl891014#gmail.com', IV = [ 0xcb, 0x53, 0x03, 0x0f, 0xe0, 0x79, 0x9d, 0xdc, 0x80, 0xa9, 0x83, 0xf1, 0x03, 0xb6, 0x59, 0x83 ];const md5sum = str => { return crypto.createHash('md5') .update(str) .digest() .slice(0, 16);};const encrypt = str => { const key = md5sum(KEY); // key和IV 必须是16位或32位 const cipher = crypto.createCipheriv(ALG_STRING, key, Buffer.from(IV)); cipher.setAutoPadding(true); const cipherChunks = []; cipherChunks.push(cipher.update(str, 'utf8', 'binary')); cipherChunks.push(cipher.final('binary')); return Buffer.from(cipherChunks.join(''), 'binary').toString('base64');};const decrypt = str => { const key = md5sum(KEY); const decipher = crypto.createDecipheriv(ALG_STRING, key, Buffer.from(IV)); decipher.setAutoPadding(true); const cipherChunks = []; cipherChunks.push(decipher.update(Buffer.from(str, 'base64').toString('binary'), 'binary', 'utf8')); cipherChunks.push(decipher.final('utf8')); return cipherChunks.join('');};const data = 'bd2983b21dd2aeb1e1453ab0273b4dc';console.log(`加密前:${data}`);const encryptData = encrypt(data);console.log(`加密后:${encryptData}`);const decryptData = decrypt(encryptData);console.log(`解密后:${decryptData}`);]]></content>
<categories>
<category>Node.js</category>
</categories>
<tags>
<tag>AES</tag>
<tag>aes-128-cbc</tag>
</tags>
</entry>
<entry>
<title><![CDATA[CentOS ftp服务搭建]]></title>
<url>%2F2017%2F05%2F04%2FCentOS-ftp%E6%9C%8D%E5%8A%A1%E6%90%AD%E5%BB%BA%2F</url>
<content type="text"><![CDATA[好记性不如烂笔头每次想要windows和linux互传文件时,都得去搜索“Linux ftp服务安装配置”,重复的次数太多,还是把它记录下来吧!!!安装vsftpd ftp1yum -y install ftp vsftpd备份vsftpd原有的配置文件12cd /etc/vsftpd/cp vsftpd.conf vsftpd.conf.origin创建密码明文文件123vim /etc/vsftpd/vftpuser.txt# brucewar# password根据明文创建密码DB文件1db_load -T -t hash -f /etc/vsftpd/vftpuser.txt /etc/vsftpd/vftpuser.db查看密码数据文件12file /etc/vsftpd/vftpuser.db# /etc/vsftpd/vftpuser.db: Berkeley DB(Hash,version9,native byte-order)创建vftpd的guest账户1useradd -d /ftp/private -s /sbin/nologin vftpuserNote:这一步可能会创建/ftp/private失败,可以手动创建文件夹,并将它的权限赋予vftpuser12mkdir -p /ftp/privatechown -R vftpuser.vftpuser /ftp打开/etc/pam.d/vsftpd, 将auth及account的所有配置行都注释掉,添加如下内容:12auth required pam_userdb.so db=/etc/vsftpd/vftpuseraccount required pam_userdb.so db=/etc/vsftpd/vftpuser打开/etc/vsftpd/vsftpd.conf,将anonymous_enable=YES改为anonymous_enable=NO,在最下面添加如下内容:12345virtual_use_local_privs=YESguest_enable=YESguest_username=vftpuserchroot_local_user=YESallow_writeable_chroot=YES设置vsftpd开机启动1systemctl enable vsftpd重启vsftpd服务1systemctl restart vsftpd配置防火墙和SELinux12345firewall-cmd --permanent --zone=public --add-service=ftpfirewall-cmd --reloadgetsebool -a | grep ftpsetsebool -P ftpd_full_access on查看vsftpd服务状态1systemctl status vsftpd]]></content>
<categories>
<category>Linux</category>
</categories>
<tags>
<tag>Linux</tag>
<tag>CentOS</tag>
<tag>ftp</tag>
</tags>
</entry>
<entry>
<title><![CDATA[细胞卫士]]></title>
<url>%2F2017%2F03%2F30%2F%E7%BB%86%E8%83%9E%E5%8D%AB%E5%A3%AB%2F</url>
<content type="text"><![CDATA[最近身体不舒服,啥事不想做,与血常规检查接触的也多。发现自己把高中学的生物知识都还给老师了。所以有必要来普及一下血液中重要的细胞的作用。白细胞:杀灭细菌和病毒并监控身体的稳定,白细胞又分为下面4项中性粒细胞:白细胞中最重要的成分,起吞噬和杀菌作用淋巴细胞:专门对付难缠的病毒,病毒感染时,淋巴细胞一般会升高嗜碱性粒细胞:机体过敏时,牺牲自己分泌大量的趋化因子,引起过敏反应嗜酸性粒细胞:抑制过敏产生的副作用红细胞:运输氧气和营养到全身器官,再把身体内的废物运出去血红蛋白是红细胞中的一种蛋白,专门负责运输氧气,血红蛋白降低,意味着贫血。血小板:凝血,止血身体健康是最重要的,请爱惜自己!]]></content>
<categories>
<category>生活</category>
</categories>
<tags>
<tag>常识</tag>
</tags>
</entry>
<entry>
<title><![CDATA[仿知乎头像上传]]></title>
<url>%2F2017%2F02%2F09%2F%E4%BB%BF%E7%9F%A5%E4%B9%8E%E5%A4%B4%E5%83%8F%E4%B8%8A%E4%BC%A0%2F</url>
<content type="text"><![CDATA[啥都不说, 直接先来一张效果图……小伙伴们,别急,咱们先来分析一下主要功能点:图片预览图片拖拽图片缩放图片裁剪图片预览图片预览的功能技术方案是将用户的图片文件转成Base64编码并设置到<img>标签的src属性,获取图片文件的Base64编码需要通过HTML5的新特性FileReader,具体代码如下:123456789101112getImgBase64: function(imgFile, cb){ if(!window.FileReader){ alert('系统暂不支持针对你的浏览器的文件上传功能,建议使用最新版的Chrome!'); return false; } var reader = new FileReader(); reader.onload = function(){ cb && cb(reader.result); }; reader.readAsDataURL(imgFile); return true;}图片拖拽图片拖拽的功能需要借助鼠标事件mousedown, mousemove, mouseup,这里贴出mousemove里的处理:12345678910111213141516171819202122232425262728293031323334353637383940414243$(document).on('mousemove', function(e){ e.preventDefault(); var $thumb = $('.slider-thumb'); if($thumb.hasClass('moving')){ // 调整大小 var left = parseFloat($thumb.css('left')); if(self.lastPosition){ var max = $thumb.siblings('.line').width() - $thumb.width(); (e.pageX >= self.lastPosition.x) ? (left >= max ? left = max : left += (e.pageX - self.lastPosition.x)) : (left <= 0 ? left = 0 : left += (e.pageX - self.lastPosition.x)); $thumb.css('left', left); } self.currentRadio = left / $thumb.siblings('.line').width() + 1; self.resizeImg(self.currentRadio); } var $imgs = $('#avatarEditorDialog').find('img'); if($imgs.hasClass('moving')){ // 移动图片位置 var left = parseFloat($imgs.css('left')); var top = parseFloat($imgs.css('top')); var width = parseFloat($imgs.css('width')); var height = parseFloat($imgs.css('height')); if(self.lastPosition){ var leftMin = -(width - BASE - 30); var topMin = -(height - BASE - 30); (e.pageX < self.lastPosition.x) ? (left <= leftMin ? left = leftMin : left += (e.pageX - self.lastPosition.x)) : (left >= 30 ? left = 30 : left += (e.pageX - self.lastPosition.x)); (e.pageY < self.lastPosition.y) ? (top <= topMin ? top = topMin : top += (e.pageY - self.lastPosition.y)) : (top >= 30 ? top = 30 : top += (e.pageY - self.lastPosition.y)); $imgs.css({ left: left, top: top }); } } self.lastPosition = { x: e.pageX, y: e.pageY };});代码中可以看出,图片拖拽也是有边界的,计算最小的left和top值。图片缩放图片缩放很简单,主要在宽高比固定的条件下,调整图片的宽度和高度。1234567891011121314151617181920resizeImg: function(radio){ var $imgs = $('#avatarEditorDialog').find('img'); var height = $imgs.height(); var width = $imgs.width(); if(height > width){ $imgs.css({ width: BASE * radio, height: (BASE * height / width) * radio, top: -((BASE * height / width) * radio - 310) / 2, left: -(BASE * radio - 310) / 2 }); }else{ $imgs.css({ height: BASE * radio, width: (BASE * width / height) * radio, top: -(BASE * radio - 310) / 2, left : -((BASE * width / height) * radio - 310) / 2 }); }}图片裁剪这里的裁剪工作是在前端完成的,需要借助Canvas的一些接口。12345678910111213141516var canvas = document.createElement('canvas');canvas.id = 'avatarCanvas';var ctx = canvas.getContext('2d');var $img = $('.avatar-editor-window-inner img');var originalWidth = parseFloat($img.attr('data-original-width'));var originalHeight = parseFloat($img.attr('data-original-height'));var nWidth = $img.width();var nHeight = $img.height();var x = (30 - parseFloat($img.css('left'))) * originalWidth / nWidth;var y = (30 - parseFloat($img.css('top'))) * originalHeight / nHeight;canvas.width = BASE * originalWidth / nWidth;canvas.style.width = canvas.width;canvas.height = BASE * originalHeight / nHeight;canvas.style.height = canvas.height;ctx.drawImage($img[0], x, y, canvas.width, canvas.height, 0, 0, canvas.width, canvas.height);var avatar = canvas.toDataURL($img.attr('src').match(/data:(.*);base64/)[1] || 'image/jpg');drawImage方法的功能是将图片上的某个点作为锚点,绘制指定的宽度和高度到Canvas标签。完整的demo代码请移步至brucewar/avatar-upload有心的读者可能已经发现,知乎的头像上传功能已经换了一种实现方案。不错,这还是之前的方案,通过上下两张图片实现预览时的边界半透明效果。]]></content>
<categories>
<category>前端开发</category>
</categories>
<tags>
<tag>JavaScript</tag>
<tag>头像上传</tag>
</tags>
</entry>
<entry>
<title><![CDATA[windows下node版本管理:nvm-windows]]></title>
<url>%2F2017%2F01%2F13%2Fwindows%E4%B8%8Bnode%E7%89%88%E6%9C%AC%E7%AE%A1%E7%90%86-nvm-windows%2F</url>
<content type="text"><![CDATA[最近工作上接手了两个项目,可它们依赖的node版本不同,于是想到了之前用的nvm(Node Version Manager)。https://github.com/creationix/nvm之前安装nvm的方式是通过npm install nvm,而新版本可以通过脚本或者手动安装。目前,nvm没有提供windows的支持,但是在其文档中提到了nvm-windows这个工具。https://github.com/coreybutler/nvm-windows安装在安装nvm-windows前,需要做以下步骤:卸载系统中已有的node.js删除node.js安装目录(例如C:\Program Files\nodejs)删除npm包的目录(例如C:\Users<user>\AppData\Roaming\npm)打开release页面,下载最新版本的安装包。更新更新也很简单,直接下载最新版的nvm-windows安装即可。它将安全的覆盖文件。使用方法nvm install <version> [arch]:version可以是指定的node.js版本或者latest(最新版),arch可以是32、64或者allnvm list [available]:列出当前已经安装的node.js版本,available参数列出可安装版本nvm use <version> [arch]:切换node.js版本这里就介绍几个常用命令,更多命令请自行看文档。]]></content>
<categories>
<category>Node.js</category>
</categories>
<tags>
<tag>nvm</tag>
<tag>node.js</tag>
<tag>nvm-windows</tag>
</tags>
</entry>
<entry>
<title><![CDATA[2016年终总结]]></title>
<url>%2F2016%2F12%2F25%2F2016%E5%B9%B4%E7%BB%88%E6%80%BB%E7%BB%93%2F</url>
<content type="text"><![CDATA[又到了一年一度的圣诞节,每当到了这天就意味着这一年就要结束了,也意味着又长了一岁。其实我不是一个特别喜欢写年终总结的人,但对于我来说,2016是“不安分”的一年。当然这里的不安分加了引号,因为我今年经历了挺多事,也成长了很多,我用下面几个词来总结我的2016:读书运动房奴跳槽恋爱和分手Be myself2015年末,我还没有买房的打算,在家人的催促下,我开始看了几个楼盘,但是也没有太过认真对待这件事。再后来,我妈和我姐来南京也一起看了几个小区,最终在2016年的第一天定下了房子,也算在南京扎根了!当然,非常感谢我姐的果断,也感谢亲戚的帮助。说实话,就在那天我脑子里转的是,“嗯,有了它就意味着我肩上的责任更大了,我也将迈入房奴的生活…”。这些想法让当时的我有点懵,但是人总要成长,总要面对这些问题,而不能总想着逃避。还记得二月份,笛风研发部分组,“有幸”(当然这些事大家都心照不宣)加入蕾爷的笛风CRM开发组,负责CRM的前端开发工作,和蕾爷合作的很开心!并且也和几个小伙伴开始了马拉松的准备,大家互相鼓励和监督,一直坚持跑步。自从跑步后,同事说我气色很好(不管是不是真的,内心都是激动的),当然,我自己也会感觉到比以前更自信、更有精神。感谢在笛风认识的这些小伙伴!圈子决定人生,接近什么样的人,就会走什么样的路,所谓物以类聚,人以群分。牌友只会催你打牌;酒友只会催你干杯;而靠谱的人却会感染你如何 取得进步!可能上半年太过浮躁,所以今年也没读几本书。当然,什么时候读书都不晚,只怕你缺乏重新开始的勇气。所以,在这里给大家推荐几本不错的书——《解忧杂货店》、《沟通的艺术》、《当我谈跑步时我谈些什么》、《人类简史》。最近刚看完《人类简史》这本书,作者对人类的历史进程有着深刻的个人理解。同时,也让我对历史越来越感兴趣。常言道,读史可以明志。唯读书和跑步不可辜负。日复一日的工作,我这颗浮躁的心终于按捺不住了,于是九月份鼓起很大的勇气提了离职,也是人生中第一份正式工作的结束。当然,非常感谢笛风假期的同事,大家在一起共事很愉快!第二份工作,我挑了一个很有意义的入职日期(10月10号,1010,程序猿都懂),正式开始了我工作生涯新的篇章。不知不觉,入职HanSight瀚思快三个月了,在这里与新同事工作的非常愉快。虽然每天都得挤地铁,不过,我也能静下心来好好利用这些碎片时间,看看书,思考思考人生(哈哈)。顺便帮公司做个宣传,HanSight南京研发中心招人,有意向的欢迎投简历到我的工作邮箱[email protected]。在入职新公司的同时,我也开始了人生中第一段感情,10月15日与她正式确认关系,一个月不到又分手。当然,过去的已成过去,你我都要继续往前走,也感谢这段经历带给我的成长。不知不觉,夜已深,可能真的只有夜深人静的时候,才能好好整理自己的思绪。新的一年,也为自己定下目标:做自己(Be myself)。继续坚持跑步、坚持读书、坚持早睡早起的习惯、在技术上对自己要求更严格、静下心来深入前端领域(主要是React技术栈)。最后,送给自己一句话:但愿我从不缺乏重新开始的勇气。]]></content>
<categories>
<category>随笔</category>
</categories>
<tags>
<tag>2016</tag>
<tag>年终总结</tag>
</tags>
</entry>
<entry>
<title><![CDATA[npm3.0新的依赖解决方案]]></title>
<url>%2F2016%2F12%2F20%2Fnpm3-0%E6%96%B0%E7%9A%84%E4%BE%9D%E8%B5%96%E8%A7%A3%E5%86%B3%E6%96%B9%E6%A1%88%2F</url>
<content type="text"><![CDATA[最近给我的装备(Thinkpad S3-s431)升了一下级,将原本用来加速缓存的24G固态硬盘换成了128G。所以得重装系统,然后一堆软件也得重装。包括Node.js。安装了最新的Node.js(v6.9.2),npm(v3.10.9)。由于node_modules里的文件夹结构太深,无法移动,只能去项目中使用npm install重新安装依赖,然后发现node_modules文件夹结构是这样的:一个模块被分在了不同文件夹下,满足下好奇心,去看了npm的官方文档。果然NPM开发团队还是解决了这个包冗余和包结构太深的问题,下面我们来看看他们是如何做的。npm2以一种嵌套的方式安装所有的依赖,而npm3将所有依赖都安装在主目录的node_modules下。如下图所示:APP依赖模块A,而模块A又依赖模块B,npm2的方案是将模块B安装在模块A的node_modules。npm3将模块A和模块B都安装在APP的node_modules中。假如APP依赖另一个模块C,而模块C又依赖另一个版本的模块B,我们看它们又有哪些区别:npm2中,会在模块A和C的node_modules下安装不同版本的模块B,而npm3先安装模块A的同时,将其依赖的模块B的1.0版本安装在APP模块下,为了防止模块冲突,同npm2的做法类似,将模块C依赖的模块B2.0安装在模块C下。通过npm ls命令,我们可以看到模块依赖关系的树形结构图。如果想看项目主目录的依赖,可以使用如下命令:1npm ls --depth=0]]></content>
<categories>
<category>Node.js</category>
</categories>
<tags>
<tag>npm</tag>
<tag>v3</tag>
</tags>
</entry>
<entry>
<title><![CDATA[GitBook平台发布教程]]></title>
<url>%2F2016%2F12%2F05%2FGitBook%E5%B9%B3%E5%8F%B0%E5%8F%91%E5%B8%83%E6%95%99%E7%A8%8B%2F</url>
<content type="text">< * [第一节](chapter1_section1.md) * [第二节](chapter1_section2.md)* [第二章](chapter2.md)下面你就可以使用markdown语法编写电子书啦!如果你想预览电子书,可以使用下面命令:1gitbook serve第一次使用这个命令会有点慢,它会安装gitbook的一些额外工具。等待一会儿,会出现提示(Serving book on http://localhost:4000),打开链接便可预览你创建的电子书啦!运行该命令会在电子书的文件夹下生成一个_book文件夹,里面的内容就是生成的HTML文件。高级配置gitbook也提供了一些高级配置信息,可以通过在电子书文件夹下添加一个book.json文件为其添加一些高级配置,主要的配置信息如下:title:电子书的名称author:作者description:本书的简单描述language:gitbook使用的语言links:左侧导航栏添加的链接styles:自定义页面样式plugins:配置使用的插件pluginsConfig:各个插件对应的配置gitbook:指定使用gitbook版本123456789101112131415161718192021222324252627282930313233{ "title": "SVG教程(中文翻译版)", "author": "brucewar <[email protected]>", "description": "SVG教程中文翻译版,也是本人第一次翻译英文教程。", "links": { "sidebar": { "博客": "http://brucewar.me", "打赏": "http://brucewar.me/donate/", "View on GitHub": "https://github.com/brucewar/svg-tutorial" } }, "plugins": [ "duoshuo", // 多说评论 "github", // 展示github图标 "ga", // google analytics "ba" // 百度统计 ], "pluginsConfig": { "duoshuo": { "short_name": "brucewar", "theme": "default" }, "github": { "url": "https://github.com/brucewar" }, "ga": { "token": "UA-87259783-2" }, "ba": { "token": "80f89a3fe34a9f8e22c53f85908e2d6" } }}上面是我使用的一些配置。如果在你的配置中添加了插件,执行gitbook serve前,需要通过以下命令安装这些插件。1gitbook installgitbook提供了一个插件平台,你可以去搜索你想要的插件,当然,你也可以为其贡献插件。托管GitHub首先,你需要有一个GitHub账号。然后创建一个公有仓库。然后,回到电子书目录下,使用git工具初始化一个仓库。并将本地仓库和远程GitHub仓库关联。1234567git initgit remote add origin 你的远程仓库地址(比如https://github.com/brucewar/svg-tutorial)// 提交电子书git add .git commit -m "publish"git push origin master发布到GitBook如果没有GitBook账号,可以先注册一个。然后在新建一本book。然后需要做的就是将GitBook和GitHub进行关联。但是,这还不够,因为GitBook不知道何时构建你的电子书,所以这里我们就要用到GitHub的webhook功能。通俗点讲,就是在GitHub提交电子书的同时,让GitHub告诉GitBook,有更新了,你重新构建下。具体操作如下:关联完GitHub仓库后,会出现如下界面,复制webhook url:打开GitHub仓库的设置页面,添加一个webhook:绑定自定义域名此时,你可以通过GitBook提供的域名访问你刚才创建的电子书啦!一般域名格式是http://{author}.gitbooks.io/{book}/content,但是你也可以使用自定义的域名(首先你得买个域名)。]]></content>
<categories>
<category>GitBook</category>
</categories>
<tags>
<tag>gitbook</tag>
<tag>电子书</tag>
<tag>教程</tag>
</tags>
</entry>
<entry>
<title><![CDATA[跑步带给了我什么]]></title>
<url>%2F2016%2F11%2F29%2F%E8%B7%91%E6%AD%A5%E5%B8%A6%E7%BB%99%E4%BA%86%E6%88%91%E4%BB%80%E4%B9%88%2F</url>
<content type="text"><![CDATA[从年初2月份到现在,我已经坚持跑步大半年了,虽然频率不高(平均一周跑三次,每次5公里),姑且算是坚持下来了。还记得刚开始是因为前同事(蕾爷和阿汤)的怂恿,让我一起报名参加上海松江的首届半程马拉松比赛,不过因为大家都是第一次跑,所以没敢报半程,最终报的是12km健康跑。也是当时脑子一热,让我将跑步坚持了这么久,并且参加了今年南京的马拉松,不过跑的是5公里。题外话:没错,你看到就是蒋劲夫,他也是一个坚持运动的人,最近看的一档节目《真正的男子汉》也对他有所关注。知乎里常常看到一些关于跑步的话题,如“坚持跑步到底有什么改变?”。所以我有时也会想问问自己,坚持跑步给我带来了什么改变,也就有了这篇文章。先说说生理上变化吧!我觉得最大的改变就是整个人的精神面貌不一样了,作为程序员的一员,每天坐着的时间比站着的时间多得多,而且整天面对着电脑,一天下来都是油光满面,提不起精神。自从跑步后,周围的同事都说我“容光焕发”。当然,还有一些附带的变化就是肚子上的赘肉也少了(额,还是有点的),睡眠质量提高,也很少生病了。但是,这些生理上的变化微乎其微,坚持跑步贵在坚持,它真正带给我的是生活和工作上的变化。因为跑步,我认识了许多同样把跑步真正坚持下来的人,有时大家也会约着一起在咕咚APP上画圈。跑步也让我的生活更加规律,早睡早起已然成为我的习惯。每当早上赖床时,我会想到,“跑步时也有过真的坚持不下去的时候,我会告诉自己,你还可以再跑1公里”,我还会想到,坚持跑步这么难的习惯,我都坚持下来了,还有什么习惯不能坚持。坚持跑步给我带来了自制力,也给了我认真对待生活的态度。我很享受跑步过程,一步一个脚印,不断给自己设立新的目标。违背了自己定下的原则,哪怕只有一次,以后就将违背更多的原则。 —— 村上春树 《当我谈跑步时我谈些什么》此外,跑步也让我变的更自信了。面对工作和生活中遇到的一些问题,我能从容地面对并解决它们。也正是因为跑步,睡眠质量提高了,所以白天工作的精神状态很好,能更专注的工作和学习。现在,我又有了两件坚持做的事,我觉得这也是跑步带给我,一是坚持看书,第二件也就是坚持写博客。跑步给我带来的是身体健康的延续,而看书写博客则给我带来的是精神上积累和沉淀。我相信,我会像坚持跑步一样,把这两件事坚持下去,同样也会继续将跑步坚持下去。一生做好一件事,一天看一个小时书,这就够了。]]></content>
<categories>
<category>生活</category>
</categories>
<tags>
<tag>跑步</tag>
<tag>坚持</tag>
<tag>生活态度</tag>
</tags>
</entry>
<entry>
<title><![CDATA[D3.js(Draggable and Scalable Tree)]]></title>
<url>%2F2016%2F10%2F27%2FD3.js-Draggable%20and%20Scalable%20Tree%2F</url>
<content type="text"><![CDATA[因为最近手上有个小的需求,设计一个可缩放和可拖拽的树形结构,我便去研读了D3官网给的一个树形的例子。布局(Layout)原本我以为理解了基本的选择器、元素操作、Enter、Exit就能去看实例的代码了,后来发现我错了,所以这里需要理解一下D3中布局(Layout)的概念。布局是D3中一个十分重要的概念,从布局衍生出很多图表。例如:饼状图(pie)、力导向图(force),树状图(tree)等等,基本实现了很多开源的可视化工具提供的图表。但是它又和很多可视化工具(如Echarts)有很大的不同。相对于其它工具来说,D3较底层一点,所以初学者可能会觉得有点困难,但是一旦理解了D3布局的思想,使用起来,会比其它工具更加得心应手。首先,我阐释下D3和大部分可视化工具数据到图表的流程:大部分可视化工具:数据 => 封装好的绘图函数 => 图表D3:数据 => Layout => 绘图所需的数据 => 绘制图形 => 图表可以看出,D3需要自己去绘制图形,但是可以通过布局函数获得绘图所需要的数据,坏处是对初学者是一个很大的考验,好处是它能帮助我们制作出更加精密的图形。树状图回归正题,如何设计一个树形结构,我将从D3官网提供的示例代码分析。页面代码如下:123456789101112131415161718192021222324252627282930313233343536373839404142<!DOCTYPE html><html lang="en"><head> <meta charset="utf-8"> <title>tree</title> <style> body{ margin: 0; } svg{ background-color: #eee; } .node circle { cursor: pointer; fill: #fff; stroke: steelblue; stroke-width: 1.5px; } .node text { font-size: 11px; } path.link { fill: none; stroke: #ccc; stroke-width: 1.5px; } g.detail rect{ fill: #000; fill-opacity: .6; rx: 5; ry: 5; } g.detail text{ fill: #fff; } </style></head><body> <div id="treeContainer"></div> <script src="./dist/tree.bundle.js"></script></body></html>因为D3示例代码是同步的形式读出整个树形数据结构,我对其进行了改造,模拟异步数据(async_city.json)。12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061{ "root": { "name": "中国" }, "中国": { "name": "中国", "children": [ {"name": "浙江"}, {"name": "广西"}, {"name": "黑龙江"}, {"name": "新疆"} ] }, "浙江": { "name": "浙江", "children": [ {"name": "杭州"}, {"name": "宁波"}, {"name":"温州" }, {"name":"绍兴" } ] }, "广西": { "name": "广西", "children": [ {"name": "桂林"}, {"name": "南宁"}, {"name": "柳州"}, {"name": "防城港"} ] }, "桂林": { "name": "桂林", "children": [ {"name":"秀峰区"}, {"name":"叠彩区"}, {"name":"象山区"}, {"name":"七星区"} ] }, "黑龙江": { "name":"黑龙江", "children": [ {"name":"哈尔滨"}, {"name":"齐齐哈尔"}, {"name":"牡丹江"}, {"name":"大庆"} ] }, "新疆" : { "name":"新疆" , "children": [ {"name":"乌鲁木齐"}, {"name":"克拉玛依"}, {"name":"吐鲁番"}, {"name":"哈密"} ] }}画布123456789101112131415161718192021222324var margin = { top: 20, left: 50, right: 50, bottom: 20};var width = $(document).width(), height = $(document).height(), i = 0, limit = 2, root; // draggable and scalable var zoomListener = d3.behavior.zoom().scaleExtent([0.1, 3]).on('zoom', zoom); function zoom(){ d3.select('svg').select('g').attr('transform', 'translate(' + d3.event.translate + ')scale(' + d3.event.scale + ')'); } var svg = d3.select("#treeContainer").append("svg") .attr("width", width - margin.left - margin.right) .attr("height", height - margin.top - margin.bottom) .call(zoomListener) .append("g") .attr("transform", "translate(" + margin.left + "," + margin.top + ")");获取异步数据1234567// 异步获取数据function getData(sd, cb){ d3.json('data/async_city.json', function(err, json){ // 通过callback返回部分数据 cb && cb(json[sd.name]); });}构造树123456789// 获取树的rootgetData({name: 'root'}, function(json){ root = json; root.x0 = height / 2; root.y0 = width / 2; // 初始化树根 update(root);});从上面的代码可以看出构造树的核心代码就是这个update函数,下面以注释的形式深入理解树形的构造。123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185// 新建一个树的布局var tree = d3.layout.tree() .size([height - margin.top - margin.bottom, width - margin.left - margin.right]);// 因为默认的树布局是自上而下的,这里构建一个自左向右的树,故需要一个转换x和y坐标的函数var diagonal = d3.svg.diagonal() .projection(function(d) { return [d.y, d.x]; });function update(source) { var duration = d3.event && d3.event.altKey ? 5000 : 500; /** * 这里实际上是通过tree的nodes函数获得树形结构的每个节点的数据,包括位置信息和深度 * 返回的数据结构如下: * [{depth: 0, name: "中国", children: [], x: 380, y: 0}] */ var nodes = tree.nodes(root).reverse(); // 为了让当前节点居中,故更具当前节点的depth来计算各节点的y坐标(即横向位置) var srcDepth = source.depth; nodes.forEach(function(d){ d.y = height / 2 + 180 * (d.depth - srcDepth); }); // Update the nodes… var node = svg.selectAll("g.node") .data(nodes, function(d) { return d.id || (d.id = ++i); }); // Enter any new nodes at the parent's previous position. var nodeEnter = node.enter().append("g") .attr("class", "node") .attr("transform", function(d) { return "translate(" + source.y0 + "," + source.x0 + ")"; }) .on("click", click) .on('mouseover', function(d){ if(d.name == 'more') return; // 鼠标hover某个节点时,显示一个详细信息的弹层 var detail = d3.select(this).append('g') .attr('class', 'detail') .attr('dx', d3.event.x) .attr('dy', d3.event.y + 10); detail.append('rect') .attr('width', 100) .attr('height', 100); detail.append('text') .attr('dx', '.35em') .attr('dy', '2em') .attr('text-anchor', 'start') .text(function(d){ return 'name: ' + d.name; }); }) .on('mousemove', function(d){ var detail = d3.select(this).select('.detail'); detail.attr('x', d3.event.x) .attr('y', d3.event.y); }) .on('mouseout', function(d){ if(d.name == 'more') return; d3.select(this).select('.detail').remove(); }); nodeEnter.append("circle") .attr("r", 1e-6) .style("fill", function(d){ return !d.isExpand ? "lightsteelblue" : "#fff"; }); nodeEnter.append("text") .attr("x", -10) .attr("dy", ".35em") .attr("text-anchor", "end") .text(function(d) { return d.name; }) .style("fill-opacity", 1e-6); // Transition nodes to their new position. var nodeUpdate = node.transition() .duration(duration) .attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; }); nodeUpdate.select("circle") .attr("r", 10) .style("fill", function(d){ return !d.isExpand ? "lightsteelblue" : "#fff"; }); nodeUpdate.select("text") .style("fill-opacity", 1); // Transition exiting nodes to the parent's new position. var nodeExit = node.exit().transition() .duration(duration) .attr("transform", function(d) { if(d.name == 'more') this.remove(); return "translate(" + source.y + "," + source.x + ")"; }) .remove(); nodeExit.select("circle") .attr("r", 1e-6); nodeExit.select("text") .style("fill-opacity", 1e-6); /** Update the links... * tree.links方法获取连线节点之间的映射,返回的数据结构如下: * [{source: {}, target: {}}] */ var link = svg.selectAll("path.link") .data(tree.links(nodes), function(d) { return d.target.id; }); // Enter any new links at the parent's previous position. link.enter().insert("path", "g") .attr("class", "link") .attr("d", function(d) { var o = {x: source.x0, y: source.y0}; return diagonal({source: o, target: o}); }) .transition() .duration(duration) .attr("d", diagonal); // Transition links to their new position. link.transition() .duration(duration) .attr("d", diagonal); // Transition exiting nodes to the parent's new position. link.exit().transition() .duration(duration) .attr("d", function(d) { if(d.target.name == 'more') this.remove(); var o = {x: source.x, y: source.y}; return diagonal({source: o, target: o}); }) .remove(); // Stash the old positions for transition. // 记录当前节点所在的位置,为node update提供位移动画 nodes.forEach(function(d) { d.x0 = d.x; d.y0 = d.y; });}function collapse(d){ delete d._children; delete d.isExpand; delete d.children;}function expand(d){ getData({name: d.name}, function(json){ if(json && json.children){ // 获取到此节点有子节点 d._children = json.children; d.children = d._children.slice(0, limit); if(d._children.length > d.children.length){ d.children.push({'name': 'more'}); } } d.isExpand = true; update(d); });}// 异步获取数据function getData(sd, cb){ d3.json('data/async_city.json', function(err, json){ cb && cb(json[sd.name]); });}function click(d){ if(d.name == 'more'){ // 点击更多 d.parent.children = d.parent._children.slice(0, (d.parent.children.length - 1) + limit); if(d.parent._children.length > d.parent.children.length){ d.parent.children.push({'name': 'more'}); } update(d.parent); }else if(d.isExpand && d.children){ // 点击展开的节点 collapse(d); update(d); }else{ // 点击未展开的点 expand(d); }}可以从https://github.com/brucewar/practice-in-D3获取示例代码]]></content>
<categories>
<category>D3.js</category>
</categories>
<tags>
<tag>D3</tag>
<tag>树状图</tag>
</tags>
</entry>
<entry>
<title><![CDATA[D3.js(完整的柱状图)]]></title>
<url>%2F2016%2F10%2F25%2FD3.js-%E5%AE%8C%E6%95%B4%E7%9A%84%E6%9F%B1%E7%8A%B6%E5%9B%BE%2F</url>
<content type="text"><![CDATA[首先,我们先看一下最终的柱状图效果展示。如下图所示,一个完整的柱状图需要包含三个部分:矩形、文字、坐标轴。添加SVG画布12345678910111213141516// 画布大小var width = 400,height = 400;// 画布四个方向上的留白var margin = { top: 20, right: 30, bottom: 20, left: 30};var chart = d3.select('.chart').attr('width', width).attr('height', height).append('g');定义比例尺12345// define x,y point rulevar x = d3.scale.ordinal().rangeRoundBands([0, width - margin.left - margin.right]);var y = d3.scale.linear().range([height - margin.top - margin.bottom, 0]);添加矩形及文字123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354// 矩形之间的空白var barPadding = 4;/*** 这里用到了d3异步读取数据的接口csv* chart.csv的文件内容如下:* name,value* Locke,4* Reyes,8* Ford,15* Jarrah,16* Shephard,23* Kwon,42*/d3.csv('data/chart.csv', type, function(err, data){ x.domain(data.map(function(d){ return d.name; })); y.domain([0, d3.max(data, function(d){ return d.value; })]); // append rect chart.selectAll('.bar') .data(data) .enter().append('rect') .attr('class', 'bar') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .attr('x', function(d){ return x(d.name) + barPadding / 2; }) .attr('y', function(d){ return y(d.value); }) .attr('height', function(d){ return height - margin.top - margin.bottom - y(d.value); }) .attr('width', x.rangeBand() - barPadding); // append text chart.selectAll('.text') .data(data) .enter() .append('text') .attr('class', 'text') .attr('transform', 'translate(' + margin.left + ',' + margin.top + ')') .attr('x', function(d){ return x(d.name) + barPadding / 2; }).attr('y', function(d){ return y(d.value); }).attr('dx', function(){ return (x.rangeBand() - barPadding) / 2; }).attr('dy', 20) .text(function(d){ return d.value; });});function type(d){ d.value = +d.value; return d;}定义并添加坐标轴1234567891011121314151617181920212223242526// define axisvar xAxis = d3.svg.axis().scale(x).orient('bottom');var yAxis = d3.svg.axis().scale(y).orient('left').ticks(10); //显示的刻度值个数// append x axischart.append('g').attr('class', 'x axis').attr('transform', 'translate(' + margin.left + ', ' + (height - margin.bottom) + ')').call(xAxis);// append y axischart.append('g').attr('class', 'y axis').attr('transform', 'translate(' + margin.left + ',' + margin.top + ')').call(yAxis).append('text').attr('transform', 'rotate(-90)').attr('y', 6).attr('dy', '.71em').style('text-anchor', 'end').text('value');可以从https://github.com/brucewar/practice-in-D3获取示例代码]]></content>
<categories>
<category>D3.js</category>
</categories>
<tags>
<tag>D3</tag>
<tag>柱状图</tag>
</tags>
</entry>
<entry>
<title><![CDATA[D3.js(入门篇)]]></title>
<url>%2F2016%2F10%2F24%2FD3.js-%E5%85%A5%E9%97%A8%E7%AF%87%2F</url>
<content type="text"><![CDATA[简介D3.js(后面简称D3,官方首页https://d3js.org/)是基于JavaScript开发的一套用于数据可视化工具,项目开源在GitHub,项目排名靠前。它提供了很多API,如DOM(Document Object Model)的一些操作,绘制图形等。截止本文发表时间,D3已更新到v4,v4在v3的基础上,有了很大的改变(因为v3只支持通过svg和dom构图,所以v4在v3的基础上增加了对Canvas的支持以及API的修改),本文使用的版本为v3的API。Hello World12345678910111213141516171819<!DOCTYPE html><html> <head> <meta charset="utf-8"> <title>D3.js Hello World</title> <script src="https://d3js.org/d3.v3.min.js"></script> </head> <body> <p>first text</p> <p>second text</p> <p>third text</p> <script> var p = d3.select('body').selectAll('p') .attr('color', 'red') .attr('font-size', '72px') .text('brucewar'); </script> </body></html>上面代码展示了基本的DOM操作,有过jQuery开发经历的人会发现其语法和jQuery很相似,主要是以下两点:支持元素选择器链式语法结构插入元素1234// 末尾插入p元素d3.select('body').append('p').text('append new prograph');// 在第一个p元素之前插入pd3.select('p').insert('p').text('insert new prograph');删除元素1d3.select('p').remove();数据绑定在D3中,选择器通常和数据绑定一起使用,这里的数据绑定的意思是将DOM元素与数据进行绑定,当需要依靠数据操作元素会非常方便。12345var arr = ['a', 'b', 'c'];p.data(arr).text(function(d, i){ // d: 数据 i: 元素索引 return d;});绘制简单的横向柱状图添加svg画布1234567var width = 300;var height = 300;var svg = d3.select('body').append('svg').attr('width'. width).attr('height', height);绘制矩形12345678910111213141516var data = [250, 210, 170, 130, 90]; // 表示矩形的宽度var rectHeight = 25;svg.selectAll('rect').data(data).enter() // 当元素个数不足数据个数时,自动补全.append('rect').attr('x', 20).attr('y', function(d, i){ return i * rectHeight;}).attr('width', function(d){ return d;}).attr('height', rectHeight - 2).attr('fill', 'steelblue');从上面的代码可以看出当data中的数据大于画布的宽度时,绘制的矩形会超出画布,所以需要引入比例尺(Scale)的概念。比例尺的概念类似数学中的一元二次函数,有x和y两个未知数,当x的值确定时,y的值也就确定了,x范围被称为定义域,y的范围为值域,对应于D3比例尺中的domain和range。D3为我们提供了很多比例尺,这里主要讲这里柱状图所要使用的线性比例尺。123456var min = d3.min(data);var max = d3.max(data);var linear = d3.scale.linear().domain([min, max]).range([0, 300]);绘制坐标轴1234567891011//数据var dataset = [ 2.5 , 2.1 , 1.7 , 1.3 , 0.9 ];//定义比例尺var linear = d3.scale.linear() .domain([0, d3.max(dataset)]) .range([0, 250]);var axisX = d3.svg.axis() .scale(linear) //指定比例尺 .orient("bottom") //指定刻度的方向 .ticks(7); //指定刻度的数量定义了坐标轴之后,只要在svg中添加一个分组元素,再将坐标轴的其他元素添加到分组即可。1234svg.append('g').attr('class', 'axis').attr('transform', 'translate(20, 130)').call(axisX);1234567891011/** 修改坐标轴样式 */.axis path,.axis line{ fill: none; stroke: black; shape-rendering: crispEdges;}.axis text{ font-family: sans-serif; font-size: 11px;}]]></content>
<categories>
<category>D3.js</category>
</categories>
<tags>
<tag>D3</tag>
<tag>JavaScript</tag>
</tags>
</entry>
<entry>
<title><![CDATA[答利器上的六个问题]]></title>
<url>%2F2016%2F09%2F29%2F%E7%AD%94%E5%88%A9%E5%99%A8%E4%B8%8A%E7%9A%84%E5%85%AD%E4%B8%AA%E9%97%AE%E9%A2%98%2F</url>
<content type="text"><![CDATA[介绍一下你自己和所做的工作我叫王金龙,是一名初级(按照工作年限来说的话)Web前端开发,但是以开发经历来说,我至少应该算一名合格的前端攻城狮。目前在途牛旅游网工作(已经寻找到新的追求)。读书的时候,接触的东西比较少,心里所想唯有学习,所以也就没培养任何一样兴趣,偶尔会和同学一起踢踢球;工作的时候,才逐渐培养了一些兴趣爱好,如跳舞、唱歌等,跳舞是今年才培养起来的一个兴趣,但是因为工作的关系,基本没什么时间练舞,所以跳的非常非常一般。15年研究生毕业后,工作也有一年半了,而且公司是业务驱动开发,所以代码也写了不少,可是自身能力的提升却没看出来。你都在使用哪些硬件这款机械键盘是工作没多久之后买的。话说,当时看到同组的一位老程序员(简称斌哥,后来他们组从我们部门分出去啦)用的机械键盘,黑色键帽配上彩虹键帽,觉得很好看,所以一时冲动想买一把机械键盘。做什么事都要过一下脑子上面这句话,印象很深刻,因为我对机械键盘的知识一片空白,所以必须通过各方面的了解,首先从百度百科词条机械键盘学习了基本的知识,然后去问答网站(知乎)关注了话题机械键盘,发现此话题下,有很多问题是“程序员男朋友生日,想送机械键盘,有推荐的吗?”。额!!!被各种虐,心想为啥没有妹子送我。不过,还是有很多人推荐了不错的机械键盘。个人感觉很喜欢Filco圣手二代,但是迫于经济能力有限,最终还是买了一把国产的红轴,用起来真的很舒服(PS:好多同事试玩后都觉得不错)。舞蹈配上音乐之后,才有了灵魂如果一个舞者在没有背景乐的环境下舞动自己的身体,非专业观者可能会觉得看不懂,专业的人也只能从专业技巧上加以评价。有了音乐后,舞者会随着音乐的轻重缓急表达自己的情绪,这才有了舞魂。程序员写代码时的手指就如同这舞者,在键盘这个舞台上表演,配合音乐的节奏,程序便有了它的灵魂(PS:我写程序时听的歌单dance)。AKG的这个经典款的耳机是昊哥推荐的,性价比非常高(非广告)。显示器没什么可以说的,做为Web前端开发,两个显示器是必备的,一个放IDE,一个放浏览器,对于开发效率有非常大的提升。软件呢作为Linux的拥护者,必须向大家推荐Vim编辑器,我觉得它能让你脱离不必要的鼠标操作,大大提高工作效率,很惭愧,Vim新手阶段,用的不是很熟,只了解一些简单的编辑命令。顺带安利一款Chrome插件Vimium(用Vim命令操作浏览器环境)。你最理想的工作环境是什么大桌子(圆弧型),两台显示器,一台笔记本,网络一定要好(自带翻墙,Youtube 1080P不卡顿),绿植围绕,周围坐着谦虚耐心的牛人,大家一起干着一件有意义(改变世界,哈哈,这个太大)的事。你平时获得工作灵感的方式有哪些逛各种网站,知乎,简书等;玩各种流行的APP,微信,Instagram等,任何一个好的产品,自有它的独特的一面,我觉得作为前端开发,也需要有一些设计的想法,我开发过的项目中都曾借鉴过这些应用。推荐一件生活中的利器给大家VPN,强烈推荐我用的这款,但是这里不方便贴链接,有需要的私信我。]]></content>
<categories>
<category>随笔</category>
</categories>
<tags>
<tag>利器</tag>
</tags>
</entry>
</search>