vivian blog


  • 首页

  • 归档

  • 标签

  • 简历

  • 搜索
close
vivian blog

面试经历

发表于 2017-03-31

前言

经过一个星期的写简历,一个星期的等待时间,一个星期的面试时间,我终于都拿到了offer,感觉实在是不容易啊,现在特别记录一下面试时候的感觉。

软通动力面试

刚开始进去感觉还有点小紧张,然后进去了之后,就让做一份试题,是关于前端的试题,里面有很多试题是我没有见过的,但是那里感觉有很多题目都是直接css写在html上面的,顿时就感觉这家外包公司在编码规范上就做得不太好,但是呢,有些css的属性我也确实忘记了,想起先前我一直都是使用的搜索式的学习,所以有很多属性都记得不太清楚。后来呢,那个hr就说对java开发感不感兴趣啊,可以来我们这里进行培训,然后每个月有1000块钱的补贴,说实话,那时候确实有一点点小动心。但是转念一想觉得世界上哪里有那么多便宜的事情啊,人家去培训你,肯定是要和你签订合约的,到时候合同上有什么猫腻的话,你还不就死定了,哎,又没有技术面试,想想就觉得不开心

方遒教育面试

这次面试差点就迟到了,哎,下次出门应该再早点才行的。面试过程先进行了自我介绍,然后呢,说一下自己的项目经验什么之类的,后来他问到了关于jquery的想法,面完之后,我才发现我对ajax技术其实并不太了解,想想还是得多点回去啃书,补回基础。后来,我就问他,我现在的状态或者说我所掌握的知识程度怎么样。然后他说我现在的状态其实还挺不错的,但是我学的不够深入,基础也不太扎实,如果有人带着的话,带我会上手的相对快一些,啊觉得回去多看基础还是挺重要的。

佳都科技面试

这次又是路痴的问题,哎不说了。刚开始面试官不按常理的出牌,顿时就将我的思路弄乱,以致于差点把面试搞砸了。然后面试官看了我的作品,于是我就把我的树形组件做给她看,但是一打开遍历的时候,发现这个遍历有bug,顿时就觉得相当的尴尬,而恰好我又忘记了自己原来已经带了鼠标,然后就很不好意思地问她,有没有网络/有没有鼠标啊之类的问题,这样的自己让我感觉很心慌。然后我就给她看了我自己做的静态页面,她觉得我这几个月做了这么多东西,已经相当不错了,顿时心中的紧张缓解了不少。然后她就让我去做机试,写了两个页面,其中一个实现tab变换,需要用到事件委托,然后当我真正去做的时候,发现有很多属性我都忘记了(这不正是所谓的搜索式的学习嘛,确实很不好),然后心里就有点急,做了大概两个小时,终于将这个页面做好了(js代码写了20行),然后当她看到页面的时候,就说页面实现的还挺不错的,可是我的js并没有考虑扩展性!这是相当重要的。比如说要是需求有所变动的话,那你岂不是都要改嘛。接着她仅用了4行代码就实现了那个功能,这真的把我给秒杀了。

1
2
3
4
var $this = $(this),//不用重复请求
id = $this.attr(attrid);//获取自定义属性
$this.addClass('active').sibling().removeClass('active');//这里使用了链式调用,是相当节省性能的
$(id).css('display','block').sibling('main').css('display','none');

顿时心中油然而生一股崇拜之情!然后她就说

我发现你们这些应届生喜欢用插件去追求一些很酷炫的动效,但是却忽略了基础本身,要多看jquery,重拾回基础,其实很多大公司招应届生,首先考你的都是你的基础是否扎实,当然各种的网络协议啊,计算机基础都是要考到的。

后来她就跟我说,其实她真的挺喜欢我的,但是她们只招大四,申请一下,看看能不能让我进去做前端开发吧

后序

三家的面试终于完满落幕,哈哈哈,姐姐我拿到了方遒教育的offer了,感觉还是挺开心的,希望能拿到更多的offer,这样选择权就能在我手中啦!

面试总结

  1. 要有积极的心理暗示,觉得既然人家邀请你去面试,说明你的实习经历或者说教育经历是符合人家的需求,所以要自信,并且本着能让HR获得指标的心态,开开心心地去面试
  2. 要多看书,自己的基础不行,要知道大公司找实习都是要先问你计算机基础的,因为他面前端程序员之前,他是先面一个程序员的,所以基础是很重要的!
  3. 要多虚心向别人学习,好好学习,方能够天天向上哦!加油
vivian blog

事件模型

发表于 2017-03-23

EventTarget接口

DOM事件操作(监听和触发),都定义在EventTarget接口

vivian blog

事件滚动侦听

发表于 2017-03-19

前言

简历中,需要做到这样的效果,当某个元素出现在视口时,动画开始启动,现在来介绍如何获得某个元素在网页上的确切位置,下面教程总结了javascript在网页定位方面的相关知识

网页大小和浏览器窗口大小

  • 两个概念
    • 网页大小指一张网页的全部面积,通常情况下,网页的大小由内容和css样式表决定。
    • 浏览器窗口大小,则是值在浏览器窗口中看到的那部分网页面积,又叫viewport(视口)。

很显然,如果网页的内容能够在浏览器窗口中全部显示(也就是不出现滚动条),那么网页的大小和浏览器窗口的大小是相等的。如果不能全部显示,则滚动浏览器窗口,可以显示出网页的各个部分。

获取网页大小

  • clientHeight和clientWidth属性

clientHeight和clientWidth属性
(图一 clientHeight和clientWidth属性)
因此,document元素的clientHeight和clientWidht属性代表网页大小

1
2
3
4
5
6
7
8
9
10
11
12
13
  function getViewport(){
    if (document.compatMode == "BackCompat"){
      return {
        width: document.body.clientWidth,
        height: document.body.clientHeight
      }
    } else {
      return {
        width: document.documentElement.clientWidth,
        height: document.documentElement.clientHeight
      }
    }
  }

getViewport函数就可以返回浏览器高和宽,需注意

  1. 这个函数必须在页面加载完成后才能运行,否则document对象还没生成,浏览器会报错。
  2. 大多数情况下,都是document.documentElement.clientWidth返回正确值。但是,在IE6的quirks模式中,document.body.clientWidth返回正确的值,因此函数中加入了对文档模式的判断。
  3. clientWidth和clientHeight都是只读属性,不能对它们赋值。

获取网页大小的另一种方法

  • scrollHeight和scrollWidth属性,指包含滚动条在内的视觉面积。
  • document对象的scrollHeight和scrollWidth属性就是网页的大小,意思就是滚动条滚过的所有长度和宽度。
1
2
3
4
5
6
7
8
9
10
11
12
13
function getPagearea(){
    if (document.compatMode == "BackCompat"){
      return {
        width: document.body.scrollWidth,
        height: document.body.scrollHeight
      }
    } else {
      return {
        width: document.documentElement.scrollWidth,
        height: document.documentElement.scrollHeight
      }
    }
  }

但是,这个函数有一个问题。如果网页内容能够在浏览器窗口中全部显示,不出现滚动条,那么网页的clientWidth和scrollWidth应该相等。但是实际上,不同浏览器有不同的处理,这两个值未必相等。所以,我们需要取它们之中较大的那个值,因此要对getPagearea()函数进行改写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
function getPagearea(){
    if (document.compatMode == "BackCompat"){
      return {
        width: Math.max(document.body.scrollWidth,
                document.body.clientWidth),
        height: Math.max(document.body.scrollHeight,
                document.body.clientHeight)
      }
    } else {
      return {
        width: Math.max(document.documentElement.scrollWidth,
                document.documentElement.clientWidth),
        height: Math.max(document.documentElement.scrollHeight,
                document.documentElement.clientHeight)
      }
    }
  }

获取网页元素的绝对位置

  • 网页元素的绝对位置,指该元素的左上角相当于整张网页左上角的坐标,这个绝对位置需通过计算方能得到。
  • offsetTop和offsetLeft属性,表示该元素左上角与父容器(offsetParent对象)左上角的距离。所以,只需要将这两个值进行累加,就可以得到该元素的绝对坐标
    offsetTop和offsetLft属性

以下两个函数可以用来获取绝对位置的横坐标和纵坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
  function getElementLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null){
      actualLeft += current.offsetLeft;
      current = current.offsetParent;
    }
    return actualLeft;
  }
  function getElementTop(element){
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null){
      actualTop += current.offsetTop;
      current = current.offsetParent;
    }
    return actualTop;
  }

由于在表格和iframe中,offsetParent对象未必等于父容器,所以上面的函数对于表格和iframe中的元素不适用。

获取网页元素的相对位置

  • 网页元素的相对位置,指该元素左上角相当于浏览器窗口左上角的坐标,有了绝对位置后,获取相对位置就容易了,只要将绝对坐标减去页面的滚动条滚动的距离即可。

  • document的scrollTop属性指的是滚动条滚动的垂直距离。同理scrollLeft是滚动条滚动的水平距离。

获取元素的相对位置

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
 function getElementViewLeft(element){
    var actualLeft = element.offsetLeft;
    var current = element.offsetParent;
    while (current !== null){
      actualLeft += current.offsetLeft;
      current = current.offsetParent;
    }
    if (document.compatMode == "BackCompat"){
      var elementScrollLeft=document.body.scrollLeft;
    } else {
      var elementScrollLeft=document.documentElement.scrollLeft;
    }
    return actualLeft-elementScrollLeft;
  }
  function getElementViewTop(element){
    var actualTop = element.offsetTop;
    var current = element.offsetParent;
    while (current !== null){
      actualTop += current. offsetTop;
      current = current.offsetParent;
    }
     if (document.compatMode == "BackCompat"){
      var elementScrollTop=document.body.scrollTop;
    } else {
      var elementScrollTop=document.documentElement.scrollTop;
    }
    return actualTop-elementScrollTop;
  }

scrollLeft和scrollTop属性是可以赋值的,并且会立即自动滚动网页的相应位置,可以利用他们改变网页的相对位置。element.scrollIntoView()方法也有类似的这样,可使网页元素出现在浏览器窗口的左上角。

获取元素位置的快速方法

  • getBoundingClientRect(),返回一个对象,包含left,right,top,bottom四个属性,分别对应该元素的左上角和右下角相当于浏览器窗口(viewport)左上角的距离
    所以,网页元素的相对位置为
1
2
3
  var X= this.getBoundingClientRect().left;
  var Y =this.getBoundingClientRect().top;

再加上滚动距离,就可以得到绝对位置

1
2
3
  var X= this.getBoundingClientRect().left+document.documentElement.scrollLeft;
  var Y =this.getBoundingClientRect().top+document.documentElement.scrollTop;

转载自利用js获取元素位置

滚动事件侦听

  • window.scroll()是用于侦听页面滚动的元素
    参考链接

以上是储备知识,现在来说一下我在编码中所遇到的困境


困境

在调试的时候,window.scroll()无法运行,通过stackflow查询到

webkit use body for keeping track of scrolling,document.documentElement.scrollTop always returns 0 on chrome

而恰好为了让每个section都占据这个viewport,我将body的height设成了100%,所以当我remove这个css之后,window.scroll()正常运行,现附上代码

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
window.onscroll = function() {
var top = document.getElementById('Skill').getBoundingClientRect().top;
if (!flag) {//只有一次机会
var barList = $('.progress-bar');
if (top <= 0) { //outerHeight()[jquery] === offsetHeight[js]
for (var i = 0; i < barList.length; i++) {
flag = 1;
//barList[i]是一个DOM,故要使用$(barList[i])返回该jquery对象
var Width = $(barList[i]).attr('aria-valuenow') + '%';
$(barList[i]).animate({
width: Width
}, 750);
}
}
}
var Top = document.getElementById('Contact').getBoundingClientRect().top;
if (Top <= 0 && handle) {
handle = false;
$('.flip-container').toggleClass('flip');
} else if (Top > 0) {
handle = true;
}
};
vivian blog

画面翻转

发表于 2017-03-18

前言

网上有很多关于画面翻转的教程,现在转载一篇个人认为比较好的做法

HTML代码

实现正反面效果的HTML代码,估计你想到该是这样的

1
2
3
4
5
6
7
8
9
10
<div class="flip-container" ontouchstart="this.classList.toggle('hover');">
<div class="flipper">
<div class="front">
<!-- 前面内容 -->
</div>
<div class="back">
<!-- 背面内容 -->
</div>
</div>
</div>

正如你所想,有两个容器,分别存放卡片‘前面’和‘后面’,通过css,我们会指定这两个容器元素自己的这样。注意ontouchstart这段js,能让你使用触屏来触发翻转动作,显然应该将这段代码单独提取,让javaScript代码放到一起

css代码

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
/* 翻转动画 */
.flip-container{
-webkit-perspective: 1000;
perspective: 1000;/*透视属性*/
}
.flip-container:hover .flipper, .flip-container.hover .flipper{
transform: rotateY(180deg);
}
.flip-container, .front, .back{
width: 95px;
height: 95px;
}
/* flip speed goes here */
.flipper {
transition: 0.6s;
transform-style: preserve-3d;
position: relative;
}
/* hide back of pane during swap */
.front,.back{
backface-visibility: hidden;/* 背面可视性 */
position: absolute;
top: 0;
left: 0;
border-radius: 50%;
background: #eee;
display: flex;
justify-content: center;
align-items: center;
}
/* placed above back */
.front {
z-index: 2;
box-shadow: 8px 10px 10px -9px rgba(0,0,0,0.4);
}
/* back, initially hidden pane */
.back {
transform: rotateY(180deg);
box-shadow: -8px 10px 10px -9px rgba(0,0,0,0.4);
}

原理

  • 在flip-container容器元素上设置整个动画区域的透视perspective属性
  • 当flip-container元素遇到鼠标悬停事件时,flipper旋转180deg,这里是控制旋转速度的地方,若旋转值设为-180deg,逆方向旋转
  • 表示卡片正反面的元素都要绝对定位,这样他们才能在相同的位置相互遮挡,他们的背面可视性backface-visibility设置为隐藏,这样每个卡片的背面在翻转时都看不见的
  • 将卡片的正面设置为比一个背面要高的z-index的值,保证卡片的正面在最上面
  • 将背面卡片旋转180deg,这样能让她扮演背面的角色

Ana Tudor’s quote

对卡片元素的某些属性设置一些特定的值(例如)(like overflow: hidden)会导致其子元素丧失3D变换功能。我认可他的观点,因为正是在本文的例子中我正好遇到了overflow: hidden相关的麻烦,它导致了3D变换子元素全都出现在了同一个平面上,有几个是被旋转了180度。

触发css翻转

若你喜欢用javaScript来触发翻转动作,下面这个简单的css样式能帮到你

1
2
3
.flip-container:hover .flipper, .flip-container.hover .flipper, .flip-container.flip .flipper {
transform: rotateY(180deg);
}

使用javascript给容器元素添加这个css flip类就能触发卡片翻转,不需要用户系统鼠标在上面

1
document.querySelector("#myCard").classList.toggle("flip");

资料:3d翻转效果

vivian blog

3dtagCloud

发表于 2017-03-18

前言

最近准备要找实习了,然后就想着要做个漂漂亮亮的简历。想起ife项目中的标签云,于是便准备做一个3d的标签云效果


总体思路

  • 为所有标签要随机生成坐标,平均分布在球面上,然后再根据旋转公式,获取旋转后的坐标,然后再进行动画。以下是具体做法

重要概念

  • sinθ与cosθ的值域为[-1,1](很重要)
  • 球坐标公式,以坐标原点为中心,半径为R的球面的参数方程为
1
2
3
x = r * sinθ * cosΦ;
y = r * sinθ * sinΦ;
z = r * cosθ;

原理
资料:球坐标公式推导

  • 旋转公式
1
2
c = cosβ * x - sinβ * y;//(x,y,z)为旋转前的坐标,(c,d,z1)为旋转后的坐标
d = cosβ * y - sinβ * x;

原理
球绕某一轴旋转可以抽象成圆绕圆心旋转!
资料:坐标旋转公式推导

设置坐标

设置坐标是相对较难的一步,因为我们要将标签平均分布在球面上,藉此,引用一下大神的式子

1
2
θ = arccos(((2 * i) - 1) / len - 1);
Φ = θ * sqrt(len * π);

如图

第一个式子arccos中的(2 * i) - 1) / len - 1实际上是一个在[-1,1]区间中关于0对称的等差数列(因为sinθ,cosθ的值域是[-1,1]),第二个式子中的sqrt(len * π)却不是很懂其中的原理

详细代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
for (let i = 0; i < tagLen; i++) {
// 设置随机坐标,平均分布
let a = Math.acos((2 * (i + 1) - 1) / tagLen - 1), // θ = arccos(((2*(i+1))-1)/len - 1),基于[-1,1]的关于0对称的等差数列
b = a * Math.sqrt(tagLen * Math.PI), // Φ = θ*sqrt(all * π),不懂原理
x = R * Math.sin(a) * Math.cos(b), // x轴坐标: x=r*sinθ*cosΦ,详情请参考https://zh.wikipedia.org/wiki/%E7%90%83%E5%BA%A7%E6%A8%99%E7%B3%BB
y = R * Math.sin(a) * Math.sin(b), // y轴坐标: x=r*sinθ*cosΦ
z = R * Math.cos(a), // z轴坐标: z=r*cosθ
t = new tag(tagEle[i], x, y, z);
tagEle[i].style.color = '#' + Math.floor(Math.random() * 0xffffff).toString(16); // 设置随机颜色
tags.push(t);
t.move(); // 初始化位置
}

旋转

先前已经说明了思路,现在就直接post上代码吧

1
2
3
4
5
6
7
8
9
10
function rotateX() {
let cos = Math.cos(angleX),
sin = Math.sin(angleX);
for(var i = 0;i<tags.length;i++){
let y = tags[i].y * cos - tags[i].z * sin,
z = tags[i].z * cos + tags[i].y * sin;
tags[i].y = y;
tags[i].z = z;
}
};

其中angleX和angleY是旋转角速度的,可按需求设置,注意 在单位时间内v=w*r,且旋转360度跟没旋转的效果一样。

移动

1
2
3
4
5
6
7
8
9
10
11
12
13
tag.prototype = {
move: function() {
let scale = length / (length - this.z),
alpha = (this.z + R) / (2 * R),
ele = this.ele;
ele.style.fontSize = 15 * scale + "px";
ele.style.opacity = alpha + 0.5;
ele.style.zIndex = parseInt(scale * 100);
// 原点是 (cloud.offsetWidth/2, cloud.offsetHeight/2)
ele.style.left = this.x + board.offsetWidth / 2 - ele.offsetWidth / 2 + "px";
ele.style.top = this.y + board.offsetHeight / 2 - ele.offsetHeight / 2 + "px";
}
};

scale,alpha都是取关于z坐标递增的函数,用于设置font-size,opacity与z-index为了能够更好地形成视觉差(3d效果),可按需设置!

预览

参考链接 3dtagCloud

vivian blog

Trip Of D3.js

发表于 2017-03-11

前言

从freecodecamp中得知了d3.js,在官网上看到了那么多高大上的例子,不禁想要多加了解它

d3.js介绍

  • D3.js是一套javaScript函数库,包含一整组操纵画图很好的辅助工具,还有很方便element操作模型

  • 需掌握javascript,html和css与svg


废话不多说,先上例子(用d3的3.5.3版本)


柱形图

  • Visualize Data with a Bar Chart
  • 作品

定义变量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var data = chart.data;
d3.select('#note').text(chart.description);
barWidth = Math.ceil(width / data.length);
minDate = new Date(chart.from_date);
maxDate = new Date(chart.to_date);
var margin = {
top: 5,
right: 10,
bottom: 30,
left: 75
},
width = 1000 - margin.left - margin.right, //所有小矩形所占用的宽度
height = 500 - margin.top - margin.bottom,//小矩形最大的高度
barWidth = Math.ceil(width / data.length);

设置svg画布

  • 为了改变轴相对于基址图的位置,可以指定g元素上的 transform 变换属性,此处改变的是svg图
1
var chart = d3.select('#chart').attr("width", width + margin.left + margin.right).attr("height", height + margin.top + margin.bottom).append('g').attr('transform', "translate(" + margin.left + "," + margin.top + ")");

坐标轴渲染

  • 先定义x,y轴比例尺,本例使用d3.time.scale()[日期比例尺]与d3.scale.linear()[线性比例尺],然后再设置范围和定义域,注意:svg的x,y轴的方向是由左上角向右与下延伸
1
2
3
4
5
var x = d3.time.scale().domain([minDate, maxDate]).range([0, width]);//d3.time必须传入Date对象
var y = d3.scale.linear().range([height, 0]).domain([0, d3.max(data, function(d) {
return d[1];//注意range([height,0])
})]);
  • x,y轴的渲染设置,d3.svg.axis.scale()是将比例尺应用在坐标轴上,orient()指刻度的方向,ticks()刻度出现的频率,而ticks(d3.time.year,5)指的是每5年一个刻度,同样的代表时间的还有d3.time.minute/d3.time.seconds
1
2
3
var xAxis = d3.svg.axis().scale(x).orient('bottom').ticks(d3.time.year, 5),
yAxis = d3.svg.axis().scale(y).orient('left').ticks(10, "")//ticks(10)表示将y值刻度分成10份
  • 渲染x,y轴,attr(y,)表示相对于svg坐标y轴的位置,而attr(dy,)是偏离于y的值其中attr('tranform','translate(left,top)')是偏离(x,y)当前位置,然后call()相应的坐标轴渲染设置
1
2
3
4
5
//x轴
chart.append("g").attr("class", "x axis").attr("transform", "translate(0," + height + ")").call(xAxis);
//y轴
chart.append("g").attr("class", "y axis").call(yAxis).append('text').attr('transform', "rotate(-90)").attr('y', 6).attr('dy', '0.8em').style('text-anchor', 'end').text("Gross Domestic Product, USA");//style('text-anchor','end')表示在尾部对齐,添加单位可直接append('text')并做出相对应的位置偏移即可

画图

  • 设置选择器,此处通过append(‘rect’)来画矩形图
1
chart.selectAll(".bar").data(data).enter().append("rect").attr("class", "bar");
  • 设置矩形具体信息
1
2
3
4
5
6
7
d3.selectAll('.bar').attr('x', function(d) { //x坐标
return x(new Date(d[0]));
}).attr('y', function(d) { //y坐标
return y(new Date(d[1]));
}).attr('height', function(d) {
return height - y(d[1]); //svg的y坐标是在左边且往下
}).attr('width', barWidth);
  • 为其绑定事件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
var div = d3.select('.container').append('div').attr('class', 'title').style('opacity', 0);//小矩形信息
d3.selectAll('.bar').on("mouseover", function(d) {
var rect = d3.select(this);
rect.attr("class", "mousehover");
var currentDate = new Date(d[0]),
year = currentDate.getFullYear(),
month = currentDate.getMonth(),
dollars = d[1];
div.transition().duration(200).style("opacity", 0.9);//动画,duration表示变化时间
div.html("<span class = 'tip'>" + formatCurrency(dollars) + "&nbsp;Billion </span><br><span class = 'year'>" + year + ' - ' + Months[month] + '</span>')
.style('left', (d3.event.pageX + 5) + 'px')//d3.event.pageX是svg(position X)
.style('top', (d3.event.pageY - 50) + 'px');
}).on('mouseout', function() {
var rect = d3.select(this);
rect.attr('class', 'mouseoff');
div.transition().duration(500).style('opacity', 0);
});

散布图

  • Visualize Data with a Scatterplot Graph
  • 作品

此处便只post上与柱形图不同的核心代码

绘制比例尺

  • 由于x轴单位为Minutes Behind Fastest Time,而date对象中并没有直接地对分钟的提取,故需要使用tickFormat()函数来设置刻度格式,事实上,在此时,ticks指定的参数也要传递给scale.tickFormat方法
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
var convertTime = function(s) {//按min:sec显示
var sec = s%60,min = (s-sec)/60;
sec = (sec<10)? '0' + sec : sec;
return min + ':' + sec;
}
var xAxis = d3.svg.axis().scale(x).orient('bottom').tickFormat(convertTime), //x轴
yAxis = d3.svg.axis().scale(y).orient('left');
//x轴绘制
svg.append('g')
.attr('class', 'x axis')
.attr('transform', "translate(0," + height + ")")
.call(xAxis)
.append('text')
.attr('class', 'label')
.attr('x', width)
.attr('y', -6)
.style('text-anchor', 'end')
.text('Minutes Behind Fastest Time');
//y轴绘制
svg.append('g')
.attr('class', 'y axis')
.call(yAxis)
.append('text')//添加y轴单位
.attr('class', 'label')
.attr('transform', 'rotate(-90)')
.attr('y', 6)
.attr('dy', '.8em')
.style('text-anchor', 'end')
.text('Ranking');

画图

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
svg.selectAll('.dot')//此处若无该选择器,则返回一个空选择器
.data(data)
.enter().append('circle')
.attr('class', 'dot')
.attr('cx', function(d) {
return x(d.Seconds - minTime);
})
.attr('cy', function(d) {
return y(d.Place);
})
.attr('r', 3.5)
.style('fill', function(d) {
if (d.Doping === '') {
return '#035';
}
return '#EB3349';
}).on('mouseover',function(d) {
var circle = d3.select(this);
circle.attr('class','mouseover');
div.html('<span class = "des" >'+'name: '+d.Name+' Nationality: '+d.Nationality+'</span><br><span class = "doping">'+'Doping:'+d.Doping+'</span>'+'<br>'+'<span class = "URL">'+'URL'+d.URL+'</span>')
.style('left',width /2 +'px').style('top', height*0.8+'px').style('opacity',1);
}).on('mouseout',function(){
var circle = d3.select(this);
circle.attr('class','mouseoff');
div.style('opacity',0);
});

热图

  • Visualize Data with a Heat Map
  • 作品

分析:本例使用的d3.json()来获取数据,凡是画图表,重中之重仍是比例尺的绘制。现在让我们来分析一下如何绘制该比例尺吧。
首先是x轴的绘制,此处与我们上述的代码都相似

1
2
var x = d3.time.scale().domain([minYear, maxYear]).range([0, width]),//必须使用date对象传入
xAxis = d3.svg.axis().scale(x).orient('bottom').ticks(d3.time.years, 10);

然后便是monthLabel的设置,它的方位是在y轴位置,但此处无需按照我们先前设置比例尺后,再绘制坐标轴,因为无需显示刻度线。

1
2
3
4
5
6
7
8
9
10
11
var monLabels = svg.selectAll('.monthLabel').data(Months).enter().append('text')
.attr('class','monthLabel')
.text(function(d) {
return d;
})
.attr('x', 0)
.attr('y', function(d, i) {
return i * gridHeight+gridHeight/2;
})
.style('text-anchor', 'end')
.attr('transform', 'translate(-6,'+gridWidth/1.5+')');

自定义颜色比例尺,该比例尺乃是热图的特点,通过自定义颜色来确定温度的范围,让数据瞬间一目了然。
此处需要用到d3.scale.quantile()量化比例尺,定义域是用于可视化的数据维度,值域则是输出的可视化维度,用在此处便恰当好处。

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
var colorScale = d3.scale.quantile()
.domain([minVariance + baseTem, maxVariance + baseTem]).range(colors);
var dataPicker = svg.selectAll('.dataset-button')
.data(colorScale.quantiles());
//rect
dataPicker.enter().append('g').attr('class', 'dataPicker')
.append('rect')
.attr('x', function(d, i) {
return legendElementWidth * i + (width - legendElementWidth * buckets)+8;
})
.attr('y', height + 50)
.attr('width', legendElementWidth)
.attr('height', gridHeight / 2)
.style('fill', function(d, i) {
return colors[i];
});
//text
dataPicker.append('text')
.attr('class', 'scales')
.text(function(d) {
return Math.floor(d*10)/10;
})
.attr('x', function(d,i) {
return legendElementWidth * i + (width - legendElementWidth * buckets);
})
.attr('y', height + 50 + gridHeight)
.style('text-anchor','center');
});

然后便开始画图了,注意d3.event.pageX表示当前交互物件相对于svg的坐标

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
var temps = svg.selectAll('.years').data(dataTem)
.enter().append('rect').attr('class','years')
.attr('x', function(d) {//此处也可使用x(new Date(d.year,0))来获得x属性值
return ((d.year - lowYear)*gridWidth);
})
.attr('y', function(d) {
return ((d.month - 1) * gridHeight);
})
.attr('rx', 0)
.attr('ry', 0)
.attr('width', gridWidth)
.attr('height', gridHeight)
.style("fill", function(d) {
return colorScale(d.variance + baseTem);
}).on('mouseover', function(d) {
div.transition().duration(100).style('opacity', 0.8);
div.html('<span class = "year">' + d.year + '</span>' + ' - ' + '<span class = "month">' + Months[d.month - 1] + '</span><br><span class = "temp">' + formatTem(d.variance + baseTem) + '&#8451' + '</span><br><span class = "variance">' + d.variance + '&#8451' + '</span>')
.style('left', d3.event.pageX - $('.tooltip').width() / 2 + 'px')
.style('top', d3.event.pageY - 80 + 'px');
}).on('mouseout', function(d) {
div.transition().duration(200).style('opacity', 0);
});

力导学图

这时候我需要介绍一种关于d3的力导学图布局给大家,d3.layout.force(),力导学图可以反映事物之间的关系,下面以国家间的关系为例。

Show National Contiguity with a Force Directed Graph

作品
现在让我来说一下思路

  • 布局(数据转换)
1
2
3
4
5
6
var force = d3.layout.force()
.nodes(nodes) //指定节点数组
.links(edges) //指定连线数组
.size([width,height]) //指定作用域范围
.linkDistance(150) //指定连线长度
.charge([-400]); //相互之间的作用力
  • 使力学作用作用生效
1
force.start();//开始作用
  • 绘制
    • line 线段
    • circle 节点
1
2
3
4
5
6
7
8
9
10
11
12
13
//绘制line
var svg_edges = svg.selectAll('.line').data(edges).enter().append('line').attr('class','line');
//绘制节点
var svg_nodes = d3.select('#chart').select('#flagbox').selectAll('.node').data(nodes).enter().append('img')
.attr('class',function(d) {
return 'flag flag-'+d.code;
}).on('mouseover',function(d) {
toolTip.style('opacity',1);
toolTip.html(d.country).style('left',d3.event.pageX+'px').style('top',(d3.event.pageY-28)+'px');
}).on('mouseout',function(d) {
toolTip.style('opacity',0);
}).call(force.drag);//使得节点能够拖动
  • 更新节点和连线位置(因为力导向图是时刻变化的,重中之重)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
force.on('tick',function() {
//更新连线坐标
svg_edges.attr('x1',function(d) {
return d.source.x;
}).attr('y1',function(d) {
return d.source.y;
}).attr('x2',function(d) {
return d.target.x;
}).attr('y2',function(d) {
return d.target.y;
});
//更新节点坐标,需稍微改变各节点坐标,动态显示
svg_nodes.style('left',function(d){
if(d.x-8>0)
return (d.x-8)+'px';
}).style('top',function(d){
return (d.y-5)+'px';
});
});

效果示例

  • 柱形图

See the Pen Visualize Data with a Bar Chart by vivian (@leoCecilia) on CodePen.

  • 散布图

See the Pen Visualize Data with a Scatterplot Graph by vivian (@leoCecilia) on CodePen.

  • 热点图

See the Pen Visualize Data with a Heat Map by vivian (@leoCecilia) on CodePen.

  • 力导向图

See the Pen Force Directed Graph by vivian (@leoCecilia) on CodePen.

vivian blog

currently thinking

发表于 2017-03-06

场景1

我们宿舍开了一个小会,目的是为了让我们宿舍能够继续和睦相处。小A说我生活习惯有些随便,导致她东西的生命周期缩短

自述

我确实以为有时候集体生活,有些资源是可以共用的,因此我便没有询问对方的意见,不问自取,此确实是我的不对。对方的处理让我觉得很是尴尬,并不是说人家冤枉了我什么的,也不是说我有错不认,而是她并没有事先跟我说清你我的矛盾,却先跟其他舍友说此事,然后说还开个小会。其他人都在不好意思地笑着。。。噢,对啊,原来她们竟是不好意思地笑着。是啊,小B和小C想必也早就不满我不问自取之恶习,而小D也最是反感这种恶习的。哪怕是最微末的事情,长期以来,也是很消磨人的意志的吧,只是碍于舍友情谊,不方便明说罢了。如此说来小A倒是成为了代表者来开会说明她们对我的不满啊,而我竟误以为她。。。原来根源是出于己身啊!

场景2

我男友召集他的初中同学来华农游玩,并且也让他们看一下他们的大嫂(然而我心理是不承认的,明明只是女朋友嘛!!!总觉得叫我大嫂会将我说的很老的感觉)。他让我做他们的导游,可是在那一刻我竟不知道该说啥,内心也有一些局促不安,后来他还是承接了自己的话题。。。 到后来,大多数情况下也是他跟他们聊天,游玩结束之后,他问我为什么那么不自信呢,是因为不熟悉嘛

自述

我竟没感觉我那是不自信,可是一时间让我做导游,我也确实是脑袋空白了,不知该说什么啊。思前想后,忽然想起我大一的时候却也不是如此,大一是会很热衷于结交新朋友,且令人感觉易聊随和,可是自从我有了男朋友之后,我却似乎又慢慢变回我初时的模样,腼腆,不太爱说话。想来是因为我已经依赖于他了。嗯,感觉很多时候都是这个样子哎,很多时候,在他面前,我都像一个诸事不晓的傻白甜。哦,这是一个不太美好的征兆。回想起我以前和晖皓一起相处的时候(朋友)亦是如此!渐渐地我便不会展露自己,不会表达自己。如果不及时意识到,并且充实自己的话,那我可能便永远跳不出自己的圈子了。没错,当我恋爱时,我应仍是单身,不过分依赖对方,而更应该培养独立意识,否则当他离开之时,我便可能只剩下躯壳,恋爱应该是使人变得更加美好的,在此过程中,双方共同成长!而若是没有了恋爱,我也应自给自足,能够养活自己!此乃吾现时之目标。对了,最近才发现我大腿又粗了些许,是该减肥了,做一个更美好的自己!

随想

深夜失眠,便开始思考最近的所闻所见,忽然发现自己有很多不美好的地方,比如说,之前我男朋友因为我的原因丢了手机,可是我没有和他一起解决这个问题啊,于是他问他兄弟借钱,然后自己揽下大堆兼职,感觉很累的样子。可是我却没跟他一起分担啊,然后就挺恼自己的。后来去长隆时候,他恐高嘛,然后我觉得我好没有义气,就这么扔下他自己去玩了,可是那时候我也好不开心,因为我不知道该怎么给予他勇气,反而还让他更胆怯了,可是每当我无助之时,他却总会出现。于是昨晚我便感觉更加难过了,然后我就下床去写随笔,因为心里堵的慌,也睡不着!后来梳理自己的思绪的时候啊,原来很多时候我都依赖他,然后不仅导致我疏远了我舍友和我们班的同学,更是减退了我先前热衷结交小伙伴的热情啊,还有其他的一些东西。 我之前看了一篇文章,它的标题是我恋爱了,但我仍然单身。后来嘛,我跟他说了昨晚的事情,问他你要看我的随笔嘛。

他说随笔怎么可以随便给别人看呢,那是最私人的东西,只能你自己拥有啊,你得留一片独属于你自己的空间在你心中啊。

然后我心中一颤。是啊,不管任何时候都要做一个有独立思想的人,这样,才不会受控于他人啊。又或许说,即便他将来真的离开了我,我也依旧能够向着自己的目标前行!

vivian blog

市井讲价之心理拉锯战

发表于 2017-03-04

前言

之所以会发出这篇文章,是由于昨天与男友买衣服所收到的启发


场景1

我们进入了一家装饰风格略显 拘束的小店中,略微询问了下价格,价格倒是便宜,摸上去材质却也是一般,想来其货色是较下层等次,拿了好几件衣服试穿,或许是因为我们本身比较旺场,店里客人接踵而来,于是我也不好意思继续占着更衣室,便退了出来,当我想要再试另外一件衣服之前,老板娘却黑着脸说,不用再试了,比比就知道好不好看啦。然后我们便甚为不爽,本想多买几件,现在居然还赶起人来了。最终不还是只有我们买下来了。

场景1分析

其实由店中的装饰风格,便可看出,店家的度量与为人处事。当我们觉察到我们占用了店中的资源时,我们便很知趣的退了下来,然而当我们想要继续去试衣服时,这是无可厚非,因为这是公众场合,每个人都有机会去试衣间试穿衣服,然而她却只以为我们占着试衣间,就得失我们,却没看到我们也是潜在的客源,即便我们这次不买,若是试穿的高兴的话,也许下一次还会叫身边的人去买衣服呢。

试衣感受

很不美好。当天我刚好来M,且试穿衣服亦是一件累人的活计,于是心情也便有些许不佳,再加上新买的鞋子穿进穿出很累人,到后来试穿裹胸的时候,我就觉得勒的很紧,所以就更郁闷啦,我就向我男友抱怨说我不喜欢这裹胸,他就笑着说,不要就不要嘛,怎么好像很委屈的样子。可是他还一直要找别的裹胸给我。


场景2

我们进入了一家装饰风格较为敞亮的店中,售货员是男的,练手之心便来了。如同上次一般,不久店内便有很多人到来,各式美女都在试穿着衣裳,忽觉做售货员也是一种美好的享受,能够看着各式美女展现出她们的苗条身材,视觉上的享受得以满足。一位美女在踌躇着挑选衣服,这时我男朋友便选了一件衣服给她,并说:”你看哈,如果把里面的衬衣稍微露出来一丢丢,让牛仔衣自然垂下,便会很好看的啦”,那美女便说:”好像是有那么点道理哦!这件多少钱啊。”我男友便笑了:”我不是售货员。” “你看我都帮你推销了,是不是要给我们打折啊。”男友说。这时美女也一起附和说:”得打八折。”然后我们便让那位美女先付账,然后我们再跟他展开拉锯大战。最后我们还是将价格讲到了自己目标的价位

场景2分析

在此处,我掌握了一些讲价的策略。当一开始问价时,应先问其价,此时对方不会减太多,然后要表示出一脸嫌弃的模样,嫌贵,然后当对方问你想要多少钱时,不能立马将自己内心的价位告诉对方,而应先往下报价,此时对方肯定表示不行,铺租都不够了,然后我们再可以继续提价,但又不可提太多,得加一丢丢,显示自己很小气的样子,对方肯定仍不肯,这时便可出门走开,但要脚步放慢,表示出有回旋的余地。此时,对方肯定说,你得加,那么这个时候,我们就可以说,那就再加… 对方不肯,继续往前走,”好啦好啦,75,75就给你啦”。”我刚刚听到好像说60”;”不是65,65”,这样70收工。


继续分析,首先砍一半价格是合理的,因为通常情况下,其开价后所赚到的利润是200%,接着逐渐展开拉锯之战,当对方以为砍下了25%价格,便以为交易能够达成,但是,你坚定自己立场,明确表态价格便该是这样子的。这个时候,他便会相对应的继续减价,因为生意总是要做的,当到他只能赚10多块钱的时候,要他出手,仍是有可能的,因为卖出总比放着,一直放着可能便会亏损,倒是卖出, 不仅赚到薄利,更能赚到的是客源,其实很多人都看不到这一点 。我以薄利来赚取你的好感,从而你便会带你相熟之人继续去帮衬,从而源源不断。人与人之间的关系亦是如此。这个男售货员就不会得失顾客,首先就是给你一种很和善的感觉,而且还会适时给予你推荐搭配的建议。

场景2试衣感受

感觉好好玩哦,学到挺多心理策略,顾客就是上帝,前提还是要你略微估摸一下它的价格,不能随便砍价。


场景3

我们去到了一家很讲究整洁的小店,店家很认真的擦拭自己店门的玻璃,但是当我们问推荐衣服的时候,他却随便指了一件衣服给我们看,也没有说出怎么推荐搭配穿着的建议,结果觉得试衣扫兴,便就离开了。

场景3分析

其实本来店家讲究简洁是一件好事情,但是呢,他却不会说话,没有去满足顾客的需求,并适时给予顾客建议。


总结

– 售货员角度:当你拿到中下等次的货色时,你不能得失顾客,得让顾客买的舒心,当你拿到较好等次的货色时,你得适当给予顾客一些穿衣打扮的建议,尤其是在其踌躇郁闷之时,给予他一声定锤,但是同时你不能将自己的意志凌驾于顾客至上,永远谨记顾客便是你的上帝。其实跟男友一起出去逛街,感受生活,这是一件十分有趣的事情,顾客与售货员之间的心理拉锯大战每日都在重演着,多点去感受生活,去真实的感受一下面对面的拉锯心理战,这便是人与人之间的关系缩影啊。

– 顾客角度:不要把自己的价位一开始便暴露给售货员,因为无论如何都要往上抬价,

vivian blog

React学习之旅

发表于 2017-03-02

React介绍

认识误区

  • React不是完整的MVC框架,最多只是MVC中的V(view),甚至React本身都不认可MVC开发模式

  • React并不是在任何情况下,都是性能最佳的框架,在有大量DOM改动的情况下,React的性能较差

  • React不是一个新的模板语言,JSX只是一个表象,没有JSX的React也能工作

React原理

  • React 会完整的复制一份DOM Tree,然后通过标记dirty nodes 来找到真正重绘的Nodes。

– eg.某个DOM被标记说要更新,那么它以下所有的subTree都要被调用shouldComponentUpdate。关键是从setState被触发后,其自身节点到其所有子节点都要被呼叫到shouldComponentUpdate,遍历所有节点的时间成本是很高的。

React知识点

  • getDefaultProps只在组件创建时调用一次并缓存对象(即在React.createClass之后就会调用)此方法不能以来this获得这个组件实例。使用ES6语法,直接定义defaultProps这个类属性来替代,Temp.defaultProps = {initialCount: 0};

  • getInitialState,初始化this.state的值,只在组件装载之前调用一次,若使用ES6语法,可在构造函数中初始状态

1
2
3
4
5
6
7
8
9
class Temp extends React.Component{
constructor(props){
super(props);
this.state = {count: 0};
}
render() {
}
}
  • render必须用于生成此组件的HTML结构,可返回null或者false,这时候ReactDOM.findDOMNode(tjos)会返回null

生命周期函数

装载组件触发

componentWillMount

  • 只在装载之前调用一次,在render之前调用,可在这个方法调用this.state改变状态,此处不会额外调用render

更新组件触发

  • componentWillReceiveProps
  • shouldComponentUpdate
  • shouldComponentUpdate(nextProps,nextState)
    – 调用shouldComponentUpdate可以让React知道一个组件的输出是否会被当前状态或参数的改变而影响,默认的行为是在每一个状态改变后便重新渲染页面,而且在大多情况下需要依赖这个默认的行为。
    当新的参数和状态被接收到时shouldComponentUpdate就会被调用。此方法不会在首次render或是forceUpdate在执行时被调用

  • componentWillUpdate

  • componentDidUpdate

卸载组件触发

  • componentWillUnmount

React的核心

  • 虚拟DOM,传统的diff(比较新DOM与虚拟DOM的差异,然后再加以渲染),但facebook工程师采用了更近大胆的策略,当然这必定有所牺牲

  • DOM节点跨层级少

  • 组件,不同组件基本不同DOM,比较是否同一类型组件,若否,删除原有节点,新增整个组件下的子节点,若是,比较ELEMENT
  • 同一层级的子节点,可用ID区分,然后根据节点ID,进行移位操作,得到如何修改,删除,新增
vivian blog

build the game of life

发表于 2017-03-02

build the game of life(204)

相关资料

game of life(英文版)
康威生命游戏(中文版)


规则

  • 当前细胞为存活状态时,当周围低于2个(不包含2个)存活细胞时, 该细胞变成死亡状态。(模拟生命数量稀少)
  • 当前细胞为存活状态时,当周围有2个或3个存活细胞时, 该细胞保持原样。
    当前细胞为存活状态时,当周围有3个以上的存活细胞时,该细胞变成死亡状态。(模拟生命数量过多)
  • 当前细胞为死亡状态时,当周围有3个存活细胞时,该细胞变成存活状态。 (模拟繁殖)

分析

核心算法部分

  • 创建一个2d数组来储存细胞
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
createGrid() {
var grid = [];
for (var i = 0; i < 25; i++) {
var row = [];
for (var j = 0; j < 40; j++) {
var random = Math.random()/1; //简单的random算法,可百度
if (random < 0.3) {
row.push(1);
} else {
row.push(0);
}
}
grid.push(row);
}
return grid;
}
  • 由规则可知,其主要难点是找出每个细胞的邻居总数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
calculateNeighbour(x,y) {
//注意25乃是arr.length;
//40乃是arr[i].length;
let topRow = x-1<0?(25-1):x-1;
let bottomRow = (x+1===25)?0:x+1;
let leftColumn = y-1<0?(40-1):y-1;
let rightColumn = (y+1===40)?0:y+1;
let total = 0;
total += (this.state.Grid[topRow][leftColumn]>0?1:0);
total += (this.state.Grid[topRow][y]>0?1:0);
total += (this.state.Grid[topRow][rightColumn]>0?1:0);
total += (this.state.Grid[x][leftColumn]>0?1:0);
total += (this.state.Grid[x][rightColumn]>0?1:0);
total += (this.state.Grid[bottomRow][leftColumn]>0?1:0);
total += (this.state.Grid[bottomRow][y]>0?1:0);
total += (this.state.Grid[bottomRow][rightColumn]>0?1:0);
return total;
}
  • 对grid中所有元素都应用生命游戏规则
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
update() {
var copyGrid = [];
var len = this.state.Grid.length;
var Len = this.state.Grid[0].length;
for (var i = 0; i < len ; i++) {
let row = [];
for (var j = 0; j < this.state.Grid[i].length; j++) {
var neighbours = this.calculateNeighbour(i,j);
if(this.state.Grid[i][j]){
if(neighbours<2||neighbours>3){
row.push(0);
}else {
row.push(1);
}
}else{
if(neighbours===3){
row.push(2);
}else{
row.push(0);
}
}
}
copyGrid.push(row);
}
return copyGrid;
}

至此核心算法算是完成。

render 部分

使用React框架,想要了解更多React框架知识,请移步此处

  • GameFeedBack部分
    – 主要是显示当前alive的总数和generation的总数
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class GameFeedback extends React.Component {
constructor(props) {
super(props);
}
render() {
return (
<div className="feedback col-md-12 col-sm-12 col-lg-12">
<div className="population col-md-6">
<h5>Population: {this.props.population}</h5>
</div>
<div className="generations col-md-6">
<h5>Generations: {this.props.generations}</h5>
</div>
</div>
);
}
}
  • ButtonTool部分
    – 注意此处onclick事件所调用的方法均在App组件中定义,是为了避免this的混淆,用this.props获取方法。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class ButtonTool extends React.Component {
constructor(props) {
super(props);
}
render() {
var text = 'Start';
if (this.props.running) {
text = 'Parse';
}
return (
<div className="btn-group" role="group" aria-label="...">
<button onClick = {this.props.parseGame} id="playOrParse" type="button" className="btn btn-primary">{text}</button>
<button onClick = {this.props.clear} id="clear" type="button" className="btn btn-primary">clear</button>
<button onClick = {this.props.restart} id="restart" type="button" className="btn btn-primary">restart</button>
</div>
);
}
}
  • Board部分
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
class Board extends React.Component {
//无论何时何地,只要状态改变,即会调用render函数
constructor(props) {
super(props);
}
render() {
return (
<div className = 'tableContainer'>
<table>
<tbody>
{this.props.Grid.map((row,i) =>
<tr key={i}>{row.map((cell,j) =>
<Cell
X = {i}
Y = {j}
key = {j}
handleClick={this.props.onClick.bind(this)}
status={cell}
/>)}
</tr>)}
</tbody>
</table>
</div>
);
}
}
  • Cell部分
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Cell extends React.Component {
constructor(props) {
super(props);
}
render() {
let fill = '';
if(this.props.status===1){
fill = 'alive';
}else if(this.props.status===2){
fill = 'newBorn';
}else{
fill='';
}
return (<td className ={fill} onClick={this.props.handleClick.bind(this,this.props.X,this.props.Y)}></td>);
}
}
  • App部分
    – 此部分主要是将先前的所有组件连接在一起,并且定义交互时需要调用的方法,以及game的过程方法
123
vivian

vivian

23 日志
25 标签
Github Weibo Codepen Freecodecamp
© 2016 - 2017 vivian
由 Hexo 强力驱动
主题 - NexT.Mist