diff --git a/29 - Countdown Timer/README.md b/29 - Countdown Timer/README.md
new file mode 100644
index 0000000..901dc8e
--- /dev/null
+++ b/29 - Countdown Timer/README.md
@@ -0,0 +1,89 @@
+# 29 Countdown Timer 中文指南
+
+> 本篇作者:©[大史快跑Dashrun](https://github.com/dashrun)——Chinasoft Frontend Developer
+
+> 简介:[JavaScript30](https://javascript30.com) 是 [Wes Bos](https://github.com/wesbos) 推出的一个 30 天挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。目的是帮助人们用纯 JavaScript 来写东西,不借助框架和库,也不使用编译器和引用。现在你看到的是这系列指南的第 29 篇。完整指南在 [GitHub](https://github.com/soyaine/JavaScript30),喜欢请 Star 哦♪(^∇^*)
+
+> 创建时间:2017-11-6
+最后更新:2017-11-12
+
+## 挑战任务
+初始文档`index-start.html`中提供了一个倒计时控制器,从`html`文档的结构可以看出,顶部的按钮可以用来增加倒计时时间,常用的时间间隔已将参数绑定在`data-time`属性上;`display`类用来显示计时的结果。
+本次编程挑战的任务是通过javascript代码基于当前时间生成一个倒计时,将`结束时间`和`剩余时间`分别显示在`diaplay__time-left`类标签和`display__end-time`类标签上。
+
+## 实现效果
+
+
+## 编程思路
+监听按点击事件`click`来为倒计时增加时间,使用`setInterval`函数每秒执行判断函数,若倒计时事件到,则清除当前计时器,若时间未到,则计算并刷新页面上应该显示的时间。
+
+## 过程指南
+1.定义变量及获取需要操作的DOM元素的引用。
+```js
+const endTime = document.querySelector(".display__end-time");
+const leftTime = document.querySelector(".display__time-left");
+const buttons = document.querySelectorAll("button");
+const date = new Date();
+var left = 0;//剩余时间
+var end = 0;//结束时间
+var timer;//interval计时器
+leftTime.innerHTML = left;//未操作时,剩余时间显示0
+```
+2.为button绑定点击事件,当按钮点击时执行对应的回调函数。
+```js
+const arr = Array.from(buttons);
+arr.map(function(item){
+ item.addEventListener('click',clickAction);
+});
+```
+3.监听表单的提交事件,注意表单的调用方式。
+```js
+document.customForm.addEventListener('submit',function(e){
+ e.preventDefault();
+ updateTime(this.minutes.value*60);
+ updateTimer();
+});
+```
+4.点击后的回调函数中取得点击按钮传递的秒数,调用`updateTime()`函数更新页面显示结果,并调用`updateTimer()`来更新计时器相关动作.
+```js
+function clickAction(e){
+ let deltaTime;
+ deltaTime = this.dataset.time;//取得data-time属性的值
+ updateTime(deltaTime);
+
+ //点击后更新计时器
+ updateTimer();
+}
+```
+5.`updateTime()`函数用来更新和页面相关的显示信息。
+```js
+ function updateTime(delta){
+ left = left + parseInt(delta,0);
+ end = date.getTime() + left*1000;
+ leftTime.innerHTML = left;
+ endTime.innerHTML =new Date(end).toLocaleTimeString();
+}
+```
+6.`updateTimer()`函数用来执行和设定每秒检查计时器是否需要继续工作的逻辑判断。
+```js
+function updateTimer(){
+ //清除以前的timer,如果不清除,新生成的定时器会和以前的定时器叠加在一起,均会生效。
+ if(timer){
+ clearInterval(timer);
+ }
+
+ // 设置新的Timer
+ timer = setInterval(function(){
+ if(left == 0){
+ endTime.innerHTML = 'End';
+ clearInterval(timer);
+ }else{
+ left -= 1;
+ leftTime.innerHTML = left;
+ }
+},1000);
+}
+```
+
+## 延伸思考
+本次代码中前后会定义定时器和清除定时器,另一种做法是定时器一直工作不清除,对应的按钮和表单只修改时间,不用调整定时器,当值发生变化后,下一秒定时器检测时就会开始倒计时,这样代码逻辑上会有所简化,感兴趣的朋友可以自行练习。
\ No newline at end of file
diff --git a/29 - Countdown Timer/effect.png b/29 - Countdown Timer/effect.png
new file mode 100644
index 0000000..6334ccb
Binary files /dev/null and b/29 - Countdown Timer/effect.png differ
diff --git a/29 - Countdown Timer/index-start.html b/29 - Countdown Timer/index-start.html
new file mode 100644
index 0000000..d54f447
--- /dev/null
+++ b/29 - Countdown Timer/index-start.html
@@ -0,0 +1,29 @@
+
+
+
+
+ Countdown Timer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/29 - Countdown Timer/scripts-START.js b/29 - Countdown Timer/scripts-START.js
new file mode 100644
index 0000000..2d3747c
--- /dev/null
+++ b/29 - Countdown Timer/scripts-START.js
@@ -0,0 +1,61 @@
+const endTime = document.querySelector(".display__end-time");
+const leftTime = document.querySelector(".display__time-left");
+const buttons = document.querySelectorAll("button");
+const date = new Date();
+var left = 0;//剩余时间
+var end = 0;//结束时间
+var timer;//interval计时器
+leftTime.innerHTML = left;//未操作时,剩余时间显示0
+
+//为button绑定点击事件
+const arr = Array.from(buttons);
+arr.map(function(item){
+ item.addEventListener('click',clickAction);
+});
+
+//监听自定义输入
+document.customForm.addEventListener('submit',function(e){
+ e.preventDefault();
+ updateTime(this.minutes.value*60);
+ updateTimer();
+});
+
+//定义点击后的回调
+function clickAction(e){
+ let deltaTime;
+ deltaTime = this.dataset.time;
+ updateTime(deltaTime);
+
+ //点击后更新计时器
+ updateTimer();
+}
+
+
+
+//updateTime
+function updateTime(delta){
+ left = left + parseInt(delta,0);
+ end = date.getTime() + left*1000;
+ leftTime.innerHTML = left;
+ endTime.innerHTML =new Date(end).toLocaleTimeString();
+}
+
+//每秒刷新时间
+function updateTimer(){
+ //清除以前的timer
+ if(timer){
+ clearInterval(timer);
+ }
+
+ // 设置新的Timer
+ timer = setInterval(function(){
+ if(left == 0){
+ endTime.innerHTML = 'End';
+ clearInterval(timer);
+ }else{
+ left -= 1;
+ leftTime.innerHTML = left;
+ }
+},1000);
+}
+
diff --git a/29 - Countdown Timer/style.css b/29 - Countdown Timer/style.css
new file mode 100644
index 0000000..f240799
--- /dev/null
+++ b/29 - Countdown Timer/style.css
@@ -0,0 +1,82 @@
+html {
+ box-sizing: border-box;
+ font-size: 10px;
+ background: #8E24AA;
+ background: linear-gradient(45deg, #42a5f5 0%,#478ed1 50%,#0d47a1 100%);
+}
+
+*, *:before, *:after {
+ box-sizing: inherit;
+}
+
+body {
+ margin:0;
+ text-align: center;
+ font-family: 'Inconsolata', monospace;
+}
+
+.display__time-left {
+ font-weight: 100;
+ font-size: 20rem;
+ margin: 0;
+ color:white;
+ text-shadow:4px 4px 0 rgba(0,0,0,0.05);
+}
+
+.timer {
+ display:flex;
+ min-height: 100vh;
+ flex-direction:column;
+}
+
+.timer__controls {
+ display: flex;
+}
+
+.timer__controls > * {
+ flex:1;
+}
+
+.timer__controls form {
+ flex:1;
+ display:flex;
+}
+
+.timer__controls input {
+ flex:1;
+ border:0;
+ padding:2rem;
+}
+
+.timer__button {
+ background:none;
+ border:0;
+ cursor: pointer;
+ color:white;
+ font-size: 2rem;
+ text-transform: uppercase;
+ background:rgba(0,0,0,0.1);
+ border-bottom:3px solid rgba(0,0,0,0.2);
+ border-right:1px solid rgba(0,0,0,0.2);
+ padding:1rem;
+ font-family: 'Inconsolata', monospace;
+}
+
+.timer__button:hover,
+.timer__button:focus {
+ background:rgba(0,0,0,0.2);
+ outline:0;
+}
+
+.display {
+ flex:1;
+ display:flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+}
+
+.display__end-time {
+ font-size: 4rem;
+ color:white;
+}
diff --git a/README.md b/README.md
index 3b1775f..872ae50 100644
--- a/README.md
+++ b/README.md
@@ -67,7 +67,7 @@ No | Guide | Demo
25 | [Event Related指南](https://github.com/soyaine/JavaScript30/blob/master/25%20-%20Event%20Related/README.md) | [Event Related效果](https://github.com/soyaine/JavaScript30/blob/master/25%20-%20Event%20Related/index-finished-Dashrun.html)
26 | [Stripe Follow Along Nav指南](https://github.com/soyaine/JavaScript30/blob/master/26%20-%20Stripe%20Follow%20Along%20Nav/README.md) | [Strip Follow Along Nav效果](https://github.com/soyaine/JavaScript30/blob/master/26%20-%20Stripe%20Follow%20Along%20Nav/index-finished-Dashrun.html)
27 | [Click and Drag指南](https://github.com/soyaine/JavaScript30/blob/master/27%20-%20Click%20and%20Drag/README.md) | [Click and Drag效果](https://github.com/soyaine/JavaScript30/blob/master/27%20-%20Click%20and%20Drag/index-finished-Dashrun.html)
-28 | Video Speed Controller | -
+28 | [Video Speed Controller指南](https://github.com/soyaine/JavaScript30/blob/master/28%20-%20Video%20Speed%20Controller/README.md) | [Video Speed Controller效果](https://github.com/soyaine/JavaScript30/blob/master/28%20-%20Video%20Speed%20Controller/index-finished-Dashrun.html)
29 | Countdown Timer | -
30 | Whack A Mole | -
@@ -80,7 +80,7 @@ Name | Contribution
[@DrakeXiang](https://github.com/DrakeXiang) | No.[11](https://github.com/soyaine/JavaScript30/tree/master/11%20-%20Custom%20Video%20Player)
[@zzh466](http://github.com/zzh466) | Review
[@Xing Liu](https://github.com/S1ngS1ng) | Review
-[@大史快跑Dashrun](https://github.com/dashrun) | No.[16](https://github.com/soyaine/JavaScript30/tree/master/16%20-%20Mouse%20Move%20Shadow).[17](https://github.com/soyaine/JavaScript30/tree/master/17%20-%20Sort%20Without%20Articles).[18](https://github.com/soyaine/JavaScript30/tree/master/18%20-%20AddingUpTimesWithReduce).[19](https://github.com/soyaine/JavaScript30/blob/master/19%20-%20Webcam%20Fun).[20](https://github.com/soyaine/JavaScript30/tree/master/20%20-%20Speech%20Detection).[21](https://github.com/soyaine/JavaScript30/tree/master/21%20-%20Geolocation).[22](https://github.com/soyaine/JavaScript30/tree/master/22%20-%20Follow%20Along%20Link%20Highlighter).[23](https://github.com/soyaine/JavaScript30/tree/master/23%20-%20Speech%20Synthesis).[24](https://github.com/soyaine/JavaScript30/tree/master/24%20-%20Sticky%20Nav).[25](https://github.com/soyaine/JavaScript30/tree/master/25%20-%20Event%20Related).[26](https://github.com/soyaine/JavaScript30/tree/master/26%20-%20Strip%20Follow%20Along%20Nav).[27](https://github.com/soyaine/JavaScript30/tree/master/27%20-%20Click%20and%20Drag)
+[@大史快跑Dashrun](https://github.com/dashrun) | No.[16](https://github.com/soyaine/JavaScript30/tree/master/16%20-%20Mouse%20Move%20Shadow).[17](https://github.com/soyaine/JavaScript30/tree/master/17%20-%20Sort%20Without%20Articles).[18](https://github.com/soyaine/JavaScript30/tree/master/18%20-%20AddingUpTimesWithReduce).[19](https://github.com/soyaine/JavaScript30/blob/master/19%20-%20Webcam%20Fun).[20](https://github.com/soyaine/JavaScript30/tree/master/20%20-%20Speech%20Detection).[21](https://github.com/soyaine/JavaScript30/tree/master/21%20-%20Geolocation).[22](https://github.com/soyaine/JavaScript30/tree/master/22%20-%20Follow%20Along%20Link%20Highlighter).[23](https://github.com/soyaine/JavaScript30/tree/master/23%20-%20Speech%20Synthesis).[24](https://github.com/soyaine/JavaScript30/tree/master/24%20-%20Sticky%20Nav).[25](https://github.com/soyaine/JavaScript30/tree/master/25%20-%20Event%20Related).[26](https://github.com/soyaine/JavaScript30/tree/master/26%20-%20Strip%20Follow%20Along%20Nav).[27](https://github.com/soyaine/JavaScript30/tree/master/27%20-%20Click%20and%20Drag).[28](https://github.com/soyaine/JavaScript30/tree/master/28%20-%20Video%20Speed%20Controller)
[@缉熙Soyaine](https://github.com/soyaine) | No.[1](https://github.com/soyaine/JavaScript30/tree/master/01%20-%20JavaScript%20Drum%20Kit).[2](https://github.com/soyaine/JavaScript30/tree/master/02%20-%20JS%20%2B%20CSS%20Clock).[3](https://github.com/soyaine/JavaScript30/tree/master/03%20-%20CSS%20%Variables).[4](https://github.com/soyaine/JavaScript30/tree/master/04%20-%20Array%20Cardio%20Day%201).[5](https://github.com/soyaine/JavaScript30/blob/master/05%20-%20Flex%20Panel%20Gallery).[6](https://github.com/soyaine/JavaScript30/blob/master/06%20-%20Type%20Ahead).[7](https://github.com/soyaine/JavaScript30/tree/master/07%20-%20Array%20Cardio%20Day%202).[8](https://github.com/soyaine/JavaScript30/tree/master/08%20-%20Fun%20with%20HTML5%20Canvas).[9](https://github.com/soyaine/JavaScript30/blob/master/09%20-%20Dev%20Tools%20Domination).[10](https://github.com/soyaine/JavaScript30/blob/master/10%20-%20Hold%20Shift%20and%20Check%20Checkboxes/README.md).[12](https://github.com/soyaine/JavaScript30/tree/master/12%20-%20Key%20Sequence%20Detection/README.md).[13](https://github.com/soyaine/JavaScript30/blob/master/13%20-%20Slide%20in%20on%20Scroll/README.md).[14](https://github.com/soyaine/JavaScript30/tree/master/14%20-%20JavaScript%20References%20VS%20Copying)
## JOIN US