[
  {
    "path": ".gitattributes",
    "content": "*.css linguist-language=javascript\n*.html linguist-language=javascript  \n*.java linguist-language=javascript\n"
  },
  {
    "path": "01 - JavaScript Drum Kit/README.md",
    "content": "# Day1 JavaScript Drum Kit 中文指南\n## 简介\n第一天的练习是用JS制作一个爵士鼓的页面，通过敲击键盘上不同的字母，会发出不同的声音，并且页面上会伴随着敲击的动画。\n效果如下：\n![](http://om1c35wrq.bkt.clouddn.com/01%20-%20JavaScript%20Drum%20Kit%202.gif)\n\n想要实现以上效果，大致思路和解决方案如下：\n\n- 检测到键盘上什么键被按下--监听`keydown`事件\n- 在按键被按下的时候，播放音效--`audio.play()`\n- 在按键被按下的同时，播放动画--`Element.classList.add('className')`\n- 在动画结束后，移除动画，不然之后再点击不会有任何效果--`Element.classList.remove('className')`\n\n\n## 基础语法\n\n### 一些 ES6 语法\n\n1.  ``const`` ：声明一个只读的常量，标识符的值只能赋值一次。\n\n2.  \\`字符串 \\${ 变量、属性名 } \\`：模板字面量（Template literals）中用于表示模板字符串的标识。特点是字符串首尾用反引号（\\`），内部的模板部分用 ${ } 括起来表示，具体请看[MDN文档]( https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings)。简单例子如下：\n\n````javascript\nvar a = 1;\nvar b = 2;\n//不用模板的写法\nconsole.log(\"三是\" + (a + b) + \"不是\" + (2 * a + b)); //\"三是3不是4\"\n//使用模板字符串的写法\nconsole.log(`三是${a + b}不是${2 * a + b}`); //\"三是3不是4\"\n````\n\n\n### ``forEach`` 与箭头函数\n\n使用 ``document.querySelector`` 获取一组符合 CSS 选择符的元素快照，类型为 NodeList（此对象是对于文档的实时运行的动态查询），对其进行遍历时可采用 ``forEach`` 方法。\n\n```javascript\n// Code from http://es6-features.org/#StatementBodies\n\n// ES6\nnums.forEach(v => {\n\tif (v % 5 === 0)\n\t\tfives.push(v);\n})\n\n// ES5\nnums.forEach(function (v) {\n\tif (v % 5 === 0)\n\t\tfive.push(v);\n})\n```\n\n\n## 页面基础布局\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>JS Drum Kit</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n\n  <div class=\"keys\">\n    <div data-key=\"65\" class=\"key\">\n      <kbd>A</kbd>\n      <span class=\"sound\">clap</span>\n    </div>\n    <div data-key=\"83\" class=\"key\">\n      <kbd>S</kbd>\n      <span class=\"sound\">hihat</span>\n    </div>\n    <div data-key=\"68\" class=\"key\">\n      <kbd>D</kbd>\n      <span class=\"sound\">kick</span>\n    </div>\n    <div data-key=\"70\" class=\"key\">\n      <kbd>F</kbd>\n      <span class=\"sound\">openhat</span>\n    </div>\n    <div data-key=\"71\" class=\"key\">\n      <kbd>G</kbd>\n      <span class=\"sound\">boom</span>\n    </div>\n    <div data-key=\"72\" class=\"key\">\n      <kbd>H</kbd>\n      <span class=\"sound\">ride</span>\n    </div>\n    <div data-key=\"74\" class=\"key\">\n      <kbd>J</kbd>\n      <span class=\"sound\">snare</span>\n    </div>\n    <div data-key=\"75\" class=\"key\">\n      <kbd>K</kbd>\n      <span class=\"sound\">tom</span>\n    </div>\n    <div data-key=\"76\" class=\"key\">\n      <kbd>L</kbd>\n      <span class=\"sound\">tink</span>\n    </div>\n  </div>\n\n  <audio data-key=\"65\" src=\"sounds/clap.wav\"></audio>\n  <audio data-key=\"83\" src=\"sounds/hihat.wav\"></audio>\n  <audio data-key=\"68\" src=\"sounds/kick.wav\"></audio>\n  <audio data-key=\"70\" src=\"sounds/openhat.wav\"></audio>\n  <audio data-key=\"71\" src=\"sounds/boom.wav\"></audio>\n  <audio data-key=\"72\" src=\"sounds/ride.wav\"></audio>\n  <audio data-key=\"74\" src=\"sounds/snare.wav\"></audio>\n  <audio data-key=\"75\" src=\"sounds/tom.wav\"></audio>\n  <audio data-key=\"76\" src=\"sounds/tink.wav\"></audio>\n\n  <script>\n  </script>\n\n\n</body>\n\n</html>\n```\n\n- <kbd> 标签定义键盘文本\n\n说到技术概念上的特殊样式时，就要提到 <kbd> 标签。正如你已经猜到的，它用来表示文本是从键盘上键入的。\n浏览器通常用等宽字体来显示该标签中包含的文本。\n<kbd> 标签经常用在于计算机相关的文档和手册中。例如：\n\n```text\n键入 <kbd>quit</kbd> 来退出程序，或者键入 <kbd>menu</kbd> 来返回主菜单。\n```\n\n- 使用 data-* 属性来嵌入自定义数据\n\n页面里通过data-key将页面展示的内容和audio关联起来。使用方法如下介绍：\n\n```html\n<ul>\n<li data-animal-type=\"bird\">Owl</li>\n<li data-animal-type=\"fish\">Salmon</li> \n<li data-animal-type=\"spider\">Tarantula</li> \n</ul>\n```\n\n① data-* 属性用于存储页面或应用程序的私有自定义数据。\n② data-* 属性赋予我们在所有 HTML 元素上嵌入自定义 data 属性的能力。\n③ 属性名不应该包含任何大写字母，并且在前缀 \"data-\" 之后必须有至少一个字符\n④ 属性值可以是任意字符串\n\n**语法:**\n\n```text\n<element data-*=\"somevalue\">\n```\n\n**属性值:**\n\n|值|描述|\n|:---------:|:---------:|\n|somevalue|规定属性的值（以字符串）。|\n\n- 按键键码对应表https://www.cnblogs.com/yiven/p/7118056.html\n\n## 主要CSS代码\n\n```css\nhtml {\n  font-size: 10px;\n  background: url(http://i.imgur.com/b9r5sEL.jpg) bottom center;\n  background-size: cover;\n}\nbody,\nhtml {\n  margin: 0;\n  padding: 0;\n  font-family: sans-serif;\n}\n\n.keys {\n  display: flex;\n  flex: 1;\n  min-height: 100vh; \n  align-items: center;  \n  justify-content: center; \n}\n\n.key {\n  border: .4rem solid black;\n  border-radius: .5rem;\n  margin: 1rem;\n  font-size: 1.5rem;\n  padding: 1rem .5rem;\n  transition: all .07s ease;\n  width: 10rem;\n  text-align: center;\n  color: white; \n  background: rgba(0, 0, 0, 0.4);\n  text-shadow: 0 0 .5rem black;\n}\n\n.playing {\n  transform: scale(1.1);\n  border-color: #ffc600;\n  box-shadow: 0 0 1rem #ffc600;\n}\n\nkbd {\n  display: block; \n  font-size: 4rem;\n}\n\n.sound {\n  font-size: 1.2rem;\n  text-transform: uppercase;\n  letter-spacing: .1rem;\n  color: #ffc600;\n}\n```\n\n\n\n主要属性有以下几个：\n- `background-size：cover;`属性规定背景图像的尺寸。把背景图像扩展至足够大，以使背景图像完全覆盖背景区域。背景图像的某些部分也许无法显示在背景定位区域中。  \n- `html`中有一个样式为`font-size: 10px;`，在本案例中，`1rem`就是10px，rem是以html中的`font-size`为参照物，`1.2rem`就是`12px`。\n- `transform: scale(1.1);`--该属性在键盘被点击时将该元素缩放至原来的1.1倍。\n- `.key{border: .4rem solid black;} .playing{border-color: #ffc600;}`--这两条属性在按键点击的时候改变边框颜色。\n- `.key{text-shadow: 0 0 .5rem black;} .playing{box-shadow: 0 0 1rem #ffc600;}`--这两条属性在按键点击的时候改变阴影的效果\n- `transition: all .07s ease;`--定义以上动画在0.07秒内完成。\n我们注意到我们定义了`.palying`类，在按键按下的时侯为该元素添加`playing`类，在结束后移除`playing`类。\n\n## JS代码\n\n### 按键监听&音效播放&添加动画\n\n```js\nfunction playSound(e) {\n    const audio = document.querySelector(`audio[data-key=\"${e.keyCode}\"]`);\n    const key = document.querySelector(`div[data-key=\"${e.keyCode}\"]`);\n    if (!audio) return;\n    \n    key.classList.add('playing');\n    audio.currentTime = 0;\n    audio.play();\n}\n/**\n* 监听页面的keydown事件，触发playAudio函数。\n*/\nwindow.addEventListener('keydown', playSound);\n```\n\n* 监听页面的keydown事件，触发playAudio函数。\n* 通过KeyCode检测我们按下的键盘按钮是哪个按钮。\n    * A -> 65\n    * B -> 66\n    * C -> 67\n    * D -> 68\n    * E -> 69\n    * F -> 70\n    * G -> 71\n    * H -> 72\n    * I -> 73\n    * J -> 74\n    * K -> 75\n    * L -> 76\n    * M -> 77\n    * N -> 78\n    * O -> 79\n    * P -> 80\n    * Q -> 81\n    * R -> 82\n    * S -> 83\n    * T -> 84\n    * U -> 85\n    * V -> 86\n    * W -> 87\n    * X -> 88\n    * Y -> 89\n    * Z -> 90\n* 在这里我们用到了ES6的模板字符串，`${e.keyCode}`,可以动态的将按键的`Keycode`传过去，以使`audio`动态的获取每一个按键绑定的`audio`。需要注意的是模板字符串一定要使用\"`\"(Esc下面那个键)包裹，而不是双引号。\n* 我们注意到`audio.play();`前面一行是`audio.currentTime = 0;`，这是因为，如果没有在播放音效前将该音乐重置，会发生以下情况，当我连续点击某一按键的时候，只有第一次点击会响，第二次第三次连续的点击可能没声音。所以在每一次点击之前重置音效是很有必要的。\n* `key.classList.add('playing');`可以在按键点击的同时为该元素添加playing类，展示小动画。\n* `if(!audio) return; if(!key) return;`因为并不是每一个按键都有音效，当用户点击了非绑定音效按键，及时退出函数是很好的习惯。\n\n\n### 动画结束后移除动画\n\n```js\nfunction removeTransition(e) {\n    if (e.propertyName !== 'transform') return;\n    e.target.classList.remove('playing');\n}\n  \nconst keys = Array.from(document.querySelectorAll('.key'));\nkeys.forEach(key => key.addEventListener('transitionend',stopTransition));  \n```\n\n- 监听每一个按键元素的`transitionend`事件，当按键元素的动画结束后会触发`removeTransition`函数。\n\n- 首先在`removeTransition`函数中可以输出事件e的内容，会输出该动画每一步具体的变化，发现其中会有`propertyName`属性，可以通过判断`propertyName`等于其中的一个值（例如'transform'），等于该值就移除`playing`类，也即移除动画。\n\n- 在定位元素的时候，可以使用`this`也可以使用`e.target`,可以简单这么理解，`this`值的是谁出发了这次事件，也就是`key`，就等同于事件的目标（e.target）.\n\n## 解决难点\n\n### 如何将键盘按键与页面按钮对应起来？\n\n连接的帮手是 ``keydown`` 事件中的 `keyCode` 属性，`keyCode` 属性的值和 ASCII 编码值相同（对应小写字母）。在[这个网站]( http://keycode.info/ )可以用按键盘来查看对应的键码。\n\n我们能获取到的初始页面中，按钮 `div` 和音频 `audio` 标签中都添加了一个属性 `data-key` 用于存储对应的键码，这样做的目的是，添加键盘事件监听后，触发键盘事件时即可获取事件的 `keyCode` 属性值，以此为线索，操作对应的按钮及音频。\n\n````javascript\nconst audio = document.querySelector(`audio[data-key=\"${e.keyCode}\"]`);\nconst key = document.querySelector(`div[data-key=\"${e.keyCode}\"]`);\n````\n\n### 如何保证按键被按住不放时，可以马上响起连续鼓点声？\n\n每次播放音频之前，设置播放时间戳为 0：\n\n````javascript\nvar audio = document.getElementById(\"video\"); \naudio.currentTime = 0;\naudio.play();\n````\n\n### 如何使页面按钮恢复原状？\n\n利用一个叫 [`transitionened`](https://developer.mozilla.org/zh-CN/docs/Web/Events/transitionend) 的事件，它在 CSS transition 结束后会被触发。我们就可以利用这个事件，在每次打鼓的效果（尺寸变大、颜色变化）完成之后，去除相应样式。\n\n在这个页面中，发生 `transition` 的样式属性不止一个（`box-shadow`, `transform`, `border-color`），所以需要添加一个判断语句，使每发生一次按键事件时，只去除一次样式。\n\n````javascript\nfunciton remove(event) {\n  if (event.propertyName !== 'border-left-color') return;\n  this.classList.remove('playing');\n  // event.target.classList.remove('playing');\n}\n````\n"
  },
  {
    "path": "01 - JavaScript Drum Kit/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>JS Drum Kit</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <div class=\"keys\">\n    <div data-key=\"65\" class=\"key\">\n      <kbd>A</kbd>\n      <span class=\"sound\">clap</span>\n    </div>\n    <div data-key=\"83\" class=\"key\">\n      <kbd>S</kbd>\n      <span class=\"sound\">hihat</span>\n    </div>\n    <div data-key=\"68\" class=\"key\">\n      <kbd>D</kbd>\n      <span class=\"sound\">kick</span>\n    </div>\n    <div data-key=\"70\" class=\"key\">\n      <kbd>F</kbd>\n      <span class=\"sound\">openhat</span>\n    </div>\n    <div data-key=\"71\" class=\"key\">\n      <kbd>G</kbd>\n      <span class=\"sound\">boom</span>\n    </div>\n    <div data-key=\"72\" class=\"key\">\n      <kbd>H</kbd>\n      <span class=\"sound\">ride</span>\n    </div>\n    <div data-key=\"74\" class=\"key\">\n      <kbd>J</kbd>\n      <span class=\"sound\">snare</span>\n    </div>\n    <div data-key=\"75\" class=\"key\">\n      <kbd>K</kbd>\n      <span class=\"sound\">tom</span>\n    </div>\n    <div data-key=\"76\" class=\"key\">\n      <kbd>L</kbd>\n      <span class=\"sound\">tink</span>\n    </div>\n  </div>\n\n  <audio data-key=\"65\" src=\"sounds/clap.wav\"></audio>\n  <audio data-key=\"83\" src=\"sounds/hihat.wav\"></audio>\n  <audio data-key=\"68\" src=\"sounds/kick.wav\"></audio>\n  <audio data-key=\"70\" src=\"sounds/openhat.wav\"></audio>\n  <audio data-key=\"71\" src=\"sounds/boom.wav\"></audio>\n  <audio data-key=\"72\" src=\"sounds/ride.wav\"></audio>\n  <audio data-key=\"74\" src=\"sounds/snare.wav\"></audio>\n  <audio data-key=\"75\" src=\"sounds/tom.wav\"></audio>\n  <audio data-key=\"76\" src=\"sounds/tink.wav\"></audio>\n\n  <script>\n    function removeTransition(e) {\n      if (e.propertyName !== 'transform') return;\n      e.target.classList.remove('playing');\n    }\n\n    function playSound(e) {\n      const audio = document.querySelector(`audio[data-key=\"${e.keyCode}\"]`);\n      const key = document.querySelector(`div[data-key=\"${e.keyCode}\"]`);     //获取到的具有指定data-key属性的div元素\n      if (!audio) return;\n\n      key.classList.add('playing');//将获取到的具有指定data-key属性的div元素上添加playing类\n      audio.currentTime = 0;       //currentTime 属性设置或返回音频/视频播放的当前位置（以秒计）。当设置该属性时，播放会跳跃到指定的位置.\n      audio.play();               //play() 方法开始播放当前的音频或视频。\n    }\n\n    const keys = Array.from(document.querySelectorAll('.key'));//将返回的nodelist类数组转换为真正的数组对象。\n    keys.forEach(key => key.addEventListener('transitionend', removeTransition));//给所有key元素都加上了过渡完成监听事件。transitionend 事件在 CSS 完成过渡后触发。\n\n    /**\n     * 监听页面的keydown事件，触发playAudio函数。\n    */\n    window.addEventListener('keydown', playSound);\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "01 - JavaScript Drum Kit/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>JS Drum Kit</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n\n  <div class=\"keys\">\n    <div data-key=\"65\" class=\"key\">\n      <kbd>A</kbd>\n      <span class=\"sound\">clap</span>\n    </div>\n    <div data-key=\"83\" class=\"key\">\n      <kbd>S</kbd>\n      <span class=\"sound\">hihat</span>\n    </div>\n    <div data-key=\"68\" class=\"key\">\n      <kbd>D</kbd>\n      <span class=\"sound\">kick</span>\n    </div>\n    <div data-key=\"70\" class=\"key\">\n      <kbd>F</kbd>\n      <span class=\"sound\">openhat</span>\n    </div>\n    <div data-key=\"71\" class=\"key\">\n      <kbd>G</kbd>\n      <span class=\"sound\">boom</span>\n    </div>\n    <div data-key=\"72\" class=\"key\">\n      <kbd>H</kbd>\n      <span class=\"sound\">ride</span>\n    </div>\n    <div data-key=\"74\" class=\"key\">\n      <kbd>J</kbd>\n      <span class=\"sound\">snare</span>\n    </div>\n    <div data-key=\"75\" class=\"key\">\n      <kbd>K</kbd>\n      <span class=\"sound\">tom</span>\n    </div>\n    <div data-key=\"76\" class=\"key\">\n      <kbd>L</kbd>\n      <span class=\"sound\">tink</span>\n    </div>\n  </div>\n\n  <audio data-key=\"65\" src=\"sounds/clap.wav\"></audio>\n  <audio data-key=\"83\" src=\"sounds/hihat.wav\"></audio>\n  <audio data-key=\"68\" src=\"sounds/kick.wav\"></audio>\n  <audio data-key=\"70\" src=\"sounds/openhat.wav\"></audio>\n  <audio data-key=\"71\" src=\"sounds/boom.wav\"></audio>\n  <audio data-key=\"72\" src=\"sounds/ride.wav\"></audio>\n  <audio data-key=\"74\" src=\"sounds/snare.wav\"></audio>\n  <audio data-key=\"75\" src=\"sounds/tom.wav\"></audio>\n  <audio data-key=\"76\" src=\"sounds/tink.wav\"></audio>\n\n  <script>\n  </script>\n\n\n</body>\n\n</html>"
  },
  {
    "path": "01 - JavaScript Drum Kit/style.css",
    "content": "\nhtml {\n  font-size: 10px;\n  background-size: cover;\n  background-image: url(http://i.imgur.com/b9r5sEL.jpg);\n}\nbody,\nhtml {\n  margin: 0;\n  padding: 0;\n  font-family: sans-serif;//非称线--字体的开始和结束之处没有特殊修饰\n}\n\n.keys {\n  display: flex;\n  flex: 1;\n  min-height: 100vh;\n  align-items: center;\n  justify-content: center;\n}\n\n.key {\n  border: .4rem solid black;\n  border-radius: .5rem;\n  margin: 1rem;\n  font-size: 1.5rem;\n  padding: 1rem .5rem;\n  transition: all .07s ease;\n  width: 10rem;\n  text-align: center;\n  color: white;\n  background: rgba(0, 0, 0, 0.4);\n  text-shadow: 0 0 .5rem black;\n}\n\n.playing {\n  transform: scale(1.1);\n  border-color: #ffc600;\n  box-shadow: 0 0 1rem #ffc600;\n}\n\nkbd {\n  display: block;\n  font-size: 4rem;\n}\n\n.sound {\n  font-size: 1.2rem;\n  text-transform: uppercase;\n  letter-spacing: .1rem;\n  color: #ffc600;\n}\n\n"
  },
  {
    "path": "02 - JS and CSS Clock/README.md",
    "content": "# Day02 - JavaScript + CSS Clock\n\n## 简介\n第二天的练习是用JS+CSS模拟时钟效果。\n\n效果如下：\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/02%20-%20JS%20and%20CSS%20Clock/image/GIF.gif)  \n\n实现以上模拟时钟的效果，大致思路和解决方案如下：\n* 分别获取到当前时间的时、分、秒。\n* 通过时分秒对一圈360度，进行映射，确定每一个指针所需旋转的角度。\n* 通过CSS的`transform：rotate(deg)`，来实时的调整指针在键盘中的位置。  \n### 文件说明：\n(1)image:用来存放背景图片  \n(2)click.js:最终版JS逻辑  \n(3)click.css:最终版样式表  \n## 页面布局\n\n```html\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>RealTimeClock</title>\n    <link rel=\"stylesheet\" href=\"click.css\">\n</head>\n<body>\n    <div class=\"clock\">\n        <div class=\"clock-face\">\n            <div class=\"hand hour-hand\"></div>\n            <div class=\"hand min-hand\"></div>\n            <div class=\"hand second-hand\"></div>\n        </div>\n    </div>\n    <div class=\"dateblock\">\n      <div class=\"date\"></div>\n      <div class=\"week\"></div>\n      <div class=\"time\"></div>\n    </div>\n    <script src=\"clock.js\"></script>\n</body>\n</html>\n```\n\n## CSS样式\n\n```css\n /*时分秒指针初始化是垂直的，指针移动没有设置过渡效果和过渡时间，\n就是根据角度来定位置*/\nhtml{\n    /*font-size:625%,默认字体大小都是16px,16*62.5=100px,1rem=100px*/\n    font-size:625%;\n    background: #018DED url(./image/picture4.jpg) bottom center ;\n    background-size: cover;\n}\nhtml,body{\n    margin:0px;\n    padding:0px;\n    display: flex;\n    min-height: 100vh;\n    justify-content:center;\n    align-items:center;\n}\n/*采用的是标准盒模型，即是纯宽高*/\n.clock{\n    position:relative;\n    width:3rem;\n    height:3rem;\n    border:0.2rem solid white;\n    margin:0.5rem auto;\n    padding:0.2rem;\n    background: rgba(0,0,0,0.4);\n    border-radius:50%;\n    box-shadow:0 0 2px 4px rgba(0,0,0,0.1),\n               0 0 10px 3px rgba(0,0,0,0.2),\n               0 0 1px 2px #EFEFEF inset,\n               0 0 30px black inset;\n}\n.clock-face{\n    position:relative;\n    width:100%;  /*这里的100%是300px,是clock的宽*/\n    height:100%;\n}\n/*时钟表表盘中心圆点*/\n.clock-face::after{\n    content:'';\n    display: block;\n    width:.1rem;\n    height:.1rem;\n    background-color: #a8c5d1;\n    position: absolute;\n    left:50%;\n    top:50%;\n    transform:translate(-50%,-50%);\n    border-radius:50%;\n}\n/*指针通用样式，在sass中可以封装成一个mixin*/\n.hand{\n    background: #fff;\n    position:absolute;\n    bottom:50%;\n    left:50%;\n    /*transform:translateX(-50%); 虽说这样可以使指针居中线，但是translate的平移是相对于自身center位置的，那么这样居中处理后，下面的旋转仍旧按的是平移之前的right位置为原点，虽说三个指针通过translateY看似处于一条中线上，实际旋转时仍然是按照各自的right位置进行旋转*/ \n    transform:rotate(0deg);\n    transform-origin:50% 100%;\n    box-shadow: \n      0 0 0 1px rgba(0, 0, 0, 0.1),\n      0 0 8px rgba(0, 0, 0, 0.4),\n      2px 5px 1px rgba(0, 0, 0, 0.5);\n}\n\n/*时针样式*/\n.hour-hand{\n    height:40%;\n    width:0.1rem;\n    margin-left:-0.05rem;  /*使时针向左移动自身的一半来居中*/\n    border-bottom-left-radius: .05rem;\n    border-top-left-radius:.05rem;\n}\n.min-hand {\n    height: 45%;\n    width: .05rem;\n    margin-left:-0.025rem;\n}\n.second-hand {\n    height: 50%;\n    width: .02rem;\n    margin-left:-0.01rem;\n    border-bottom-left-radius: .02rem;\n    border-top-left-radius: .02rem;\n    background-color: red;\n}\n/*日期，时间，星期几的样式*/\n.dateblock{\n    width: 5rem;\n    position: relative;\n    font-size:.7rem;\n    font-family:serif;\n    font-weight:bold;\n    text-align: center;\n    color:white;\n}\n```\n\n**涉及到的特性：**\n\n- `transform-oragin`\n\n调整指针的初始位置以及旋转的轴点:[transform-oragin](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin)\n    \n```css\n    transform:rotate(0deg);\n    transform-origin:50% 100%; //这里旋转点是bottom\n```\n\n- `transform: rotate()`\n\n## JS代码\n\n```js\n //时分表每次移动没有过渡效果，仅仅根据角度来定旋转的位置，\n//不用考虑354->0度的一个回旋BUG，若过渡时间过短会出现闪动。  \n//左边时钟表盘部分\nfunction left(){\n    const secondHand = document.querySelector(\".second-hand\");\n    const minHand    = document.querySelector(\".min-hand\");\n    const hourHand   = document.querySelector(\".hour-hand\");\n    function updata(){\n        const now = new Date();\n        //秒针的旋转计算\n        const seconds               =  now.getSeconds();\n        const secondsDegrees        =  seconds*6;\n        secondHand.style.transform  =  `rotate(${secondsDegrees}deg)`;\n        //分针旋转的计算\n        const mins                =  now.getMinutes();\n        const minsDegrees         =  (mins*6)+(seconds/60)*6;\n        minHand.style.transform   =  `rotate(${minsDegrees}deg)`;\n        //时针旋转的计算\n        const hours               =  now.getHours();\n        const hoursDegrees        =  (hours-12)*30+(mins/60)*30;\n        hourHand.style.transform  =  `rotate(${hoursDegrees}deg)`;\n\n    }\n    setInterval(updata,1000);\n    updata();\n}\n//右边电子日历部分\n function right(){\n    const DATE = document.querySelector(\".date\");\n    const WEEK = document.querySelector(\".week\");\n    const TIME = document.querySelector(\".time\");\n    function Adate(){\n      const now = new Date();\n      const weekList = [\"星期一\",\"星期二\",\"星期三\",\"星期四\",\"星期五\",\"星期六\",\"星期日\"];\n      const str = now.getFullYear()+\"-\"+now.getMonth()+\"-\"+now.getDay();\n      DATE.innerHTML = str;\n      WEEK.innerHTML = weekList[now.getDay()];\n    }\n    Adate();\n    setInterval(Adate,24*3600);\n\n    // 分钟，秒，不足两位时，用0进行凑位。\n    function zero(arg){\n      if(arg>=10){\n        return arg;\n      }else{\n        return \"0\"+arg;\n      }\n    }\n    // 显示当前时间的函数\n    function Atime(){\n      const now = new Date();\n      const str = now.getHours()+\":\"+zero(now.getMinutes())+\":\"+zero(now.getSeconds());\n      TIME.innerHTML=str;\n    }\n    Atime();\n    setInterval(Atime,1000);\n}\n  left();\n  right();\n\n```\n\n- 获取秒针、分钟、小时节点\n\n```js\n    const secondHand = document.querySelector(\".second-hand\");\n    const minHand    = document.querySelector(\".min-hand\");\n    const hourHand   = document.querySelector(\".hour-hand\");\n```\n\n- 获取当前时间秒、分、小时\n\n```js\nconst now = new Date();\nconst seconds = now.getSeconds();\nconst mins = now.getMinutes();\nconst hours = now.getHours();\n```\n\n- 计算秒、分、小时角度\n\n```js\nconst secondsDegrees        =  seconds*6;\nconst minsDegrees         =  (mins*6)+(seconds/60)*6;\nconst hoursDegrees        =  (hours-12)*30+(mins/60)*30;\n```\n\n- 根据角度设置样式\n\n```js\nsecondHand.style.transform  =  `rotate(${secondsDegrees}deg)`;\nminHand.style.transform   =  `rotate(${minsDegrees}deg)`;\nhourHand.style.transform  =  `rotate(${hoursDegrees}deg)`;\n```\n\n- 设置定时器，每秒调用一次`setDate`函数\n\n```js\n   setInterval(updata,1000);\n```\n"
  },
  {
    "path": "02 - JS and CSS Clock/click.css",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-11-27 20:14:35\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-11-30 11:41:15\n*/\n/*时分秒指针初始化是垂直的，指针移动没有设置过渡效果和过渡时间，\n就是根据角度来定位置*/\nhtml{\n    /*font-size:625%,默认字体大小都是16px,16*62.5=100px,1rem=100px*/\n    font-size:625%;\n    background: #018DED url(./image/picture4.jpg) bottom center ;\n    background-size: cover;\n}\nhtml,body{\n    margin:0px;\n    padding:0px;\n    display: flex;\n    min-height: 100vh;\n    justify-content:center;\n    align-items:center;\n}\n/*采用的是标准盒模型，即是纯宽高*/\n.clock{\n    position:relative;\n    width:3rem;\n    height:3rem;\n    border:0.2rem solid white;\n    margin:0.5rem auto;\n    padding:0.2rem;\n    background: rgba(0,0,0,0.4);\n    border-radius:50%;\n    box-shadow:0 0 2px 4px rgba(0,0,0,0.1),\n               0 0 10px 3px rgba(0,0,0,0.2),\n               0 0 1px 2px #EFEFEF inset,\n               0 0 30px black inset;\n}\n.clock-face{\n    position:relative;\n    width:100%;  /*这里的100%是300px,是clock的宽*/\n    height:100%;\n}\n/*时钟表表盘中心圆点*/\n.clock-face::after{\n    content:'';\n    display: block;\n    width:.1rem;\n    height:.1rem;\n    background-color: #a8c5d1;\n    position: absolute;\n    left:50%;\n    top:50%;\n    transform:translate(-50%,-50%);\n    border-radius:50%;\n}\n/*指针通用样式，在sass中可以封装成一个mixin*/\n.hand{\n    background: #fff;\n    position:absolute;\n    bottom:50%;\n    left:50%;\n    /*transform:translateX(-50%); 虽说这样可以使指针居中线，但是translate的平移是相对于自身center位置的，那么这样居中处理后，下面的旋转仍旧按的是平移之前的right位置为原点，虽说三个指针通过translateY看似处于一条中线上，实际旋转时仍然是按照各自的right位置进行旋转*/ \n    transform:rotate(0deg);\n    transform-origin:50% 100%;\n    box-shadow: \n      0 0 0 1px rgba(0, 0, 0, 0.1),\n      0 0 8px rgba(0, 0, 0, 0.4),\n      2px 5px 1px rgba(0, 0, 0, 0.5);\n}\n\n/*时针样式*/\n.hour-hand{\n    height:40%;\n    width:0.1rem;\n    margin-left:-0.05rem;  /*使时针向左移动自身的一半来居中*/\n    border-bottom-left-radius: .05rem;\n    border-top-left-radius:.05rem;\n}\n.min-hand {\n    height: 45%;\n    width: .05rem;\n    margin-left:-0.025rem;\n}\n.second-hand {\n    height: 50%;\n    width: .02rem;\n    margin-left:-0.01rem;\n    border-bottom-left-radius: .02rem;\n    border-top-left-radius: .02rem;\n    background-color: red;\n}\n/*日期，时间，星期几的样式*/\n.dateblock{\n    width: 5rem;\n    position: relative;\n    font-size:.7rem;\n    font-family:serif;\n    font-weight:bold;\n    text-align: center;\n    color:white;\n}\n"
  },
  {
    "path": "02 - JS and CSS Clock/clock.js",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-11-27 20:15:18\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-11-30 11:08:26\n*/\n//时分表每次移动没有过渡效果，仅仅根据角度来定旋转的位置，\n//不用考虑354->0度的一个回旋BUG，若过渡时间过短会出现闪动。\n\nfunction left(){\n    const secondHand = document.querySelector(\".second-hand\");\n    const minHand    = document.querySelector(\".min-hand\");\n    const hourHand   = document.querySelector(\".hour-hand\");\n    function updata(){\n        const now = new Date();\n        //秒针的旋转计算\n        const seconds               =  now.getSeconds();\n        const secondsDegrees        =  seconds*6;\n        secondHand.style.transform  =  `rotate(${secondsDegrees}deg)`;\n        //分针旋转的计算\n        const mins                =  now.getMinutes();\n        const minsDegrees         =  (mins*6)+(seconds/60)*6;\n        minHand.style.transform   =  `rotate(${minsDegrees}deg)`;\n        //时针旋转的计算\n        const hours               =  now.getHours();\n        const hoursDegrees        =  (hours-12)*30+(mins/60)*30;\n        hourHand.style.transform  =  `rotate(${hoursDegrees}deg)`;\n\n    }\n    setInterval(updata,1000);\n    updata();\n}\n\n function right(){\n    const DATE = document.querySelector(\".date\");\n    const WEEK = document.querySelector(\".week\");\n    const TIME = document.querySelector(\".time\");\n    function Adate(){\n      const now = new Date();\n      const weekList = [\"星期一\",\"星期二\",\"星期三\",\"星期四\",\"星期五\",\"星期六\",\"星期日\"];\n      const str = now.getFullYear()+\"-\"+now.getMonth()+\"-\"+now.getDay();\n      DATE.innerHTML = str;\n      WEEK.innerHTML = weekList[now.getDay()];\n    }\n    Adate();\n    setInterval(Adate,24*3600);\n\n    // 分钟，秒，不足两位时，用0进行凑位。\n    function zero(arg){\n      if(arg>=10){\n        return arg;\n      }else{\n        return \"0\"+arg;\n      }\n    }\n    // 显示当前时间的函数\n    function Atime(){\n      const now = new Date();\n      const str = now.getHours()+\":\"+zero(now.getMinutes())+\":\"+zero(now.getSeconds());\n      TIME.innerHTML=str;\n    }\n    Atime();\n    setInterval(Atime,1000);\n}\n  left();\n  right();\n"
  },
  {
    "path": "02 - JS and CSS Clock/clock1.js",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-11-29 09:54:44\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-11-30 11:34:30\n*/\n    const secondHand = document.querySelector('.second-hand');\n    const minsHand = document.querySelector('.min-hand');\n    const hourHand = document.querySelector('.hour-hand');\n\n    let secondDeg = 0;\n    let minDeg = 0;\n    let hourDeg = 0;\n//初始化函数，用来标定当前时间下时分秒的位置，遵循规则：小单位移动角度的效果累加到大单位上。\n    function initDate() {\n      const date = new Date();\n      const second = date.getSeconds();\n      secondDeg = second*6;  //一秒转动的角度为6°\n\n      const min = date.getMinutes();\n      minDeg = (min + second / 60)*6; //秒转换为分钟，一分钟转动的角度也是6°\n\n      const hour = date.getHours();\n      hourDeg = ((hour-12)+(min/60)+(second/3600)) * 30; //转化为小时,一小时转动的角度的30°\n      secondHand.style.transform = `rotate(${ secondDeg }deg)`;\n      minsHand.style.transform = `rotate(${ minDeg }deg)`;\n      hourHand.style.transform = `rotate(${ hourDeg }deg)`;\n    }\n//跟新函数：每隔一秒计算一次，也即是每次增加1s,(1/60)分钟，(1/3600)小时\n    function updateDate() {\n      secondDeg += 1*6;\n      minDeg += (1 / 60)*6;\n      hourDeg += (1/3600)*30;\n      secondHand.style.transform = `rotate(${ secondDeg }deg)`;\n      minsHand.style.transform = `rotate(${ minDeg }deg)`;\n      hourHand.style.transform = `rotate(${ hourDeg }deg)`;\n    }\n\n    initDate();\n    setInterval(updateDate, 1000);\n\n//通过初始化时分秒的位置，然后进行角度的累加"
  },
  {
    "path": "02 - JS and CSS Clock/clock2.js",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-11-30 10:48:55\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-11-30 11:34:08\n*/\n/*\n* @Author: Administrator\n* @Date:   2018-11-27 20:15:18\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-11-29 13:53:54\n*/\n//每隔一秒，计算一次时分秒的角度进行定位。\n\nfunction left(){\n    const secondHand = document.querySelector(\".second-hand\");\n    const minHand    = document.querySelector(\".min-hand\");\n    const hourHand   = document.querySelector(\".hour-hand\");\n    function updata(){\n        const now = new Date();\n        //秒针的旋转计算，以及闪动消除 \n        const seconds               =  now.getSeconds();\n        const secondsDegrees        =  seconds*6;\n        if (secondsDegrees === 0) {\n            secondHand.style.transition = 'all 0s';\n        } else {\n            secondHand.style.transition = 'all 0.05s';\n        }\n        secondHand.style.transform  =  `rotate(${secondsDegrees}deg)`;\n        //分针旋转的计算\n        const mins                =  now.getMinutes();\n        const minsDegrees         =  (mins*6)+(seconds/60)*6;\n        if (minsDegrees === 0) {\n            minHand.style.transition = 'all 0s';\n        } else {\n            minHand.style.transition = 'all 0.05s';\n        }\n        minHand.style.transform   =  `rotate(${minsDegrees}deg)`;\n        //时针旋转的计算\n        const hours               =  now.getHours();\n        const hoursDegrees        =  (hours-12)*30+(mins/60)*30;\n        hourHand.style.transform  =  `rotate(${hoursDegrees}deg)`;\n\n    }\n    setInterval(updata,1000);\n    updata();\n}\n\n function right(){\n    const DATE = document.querySelector(\".date\");\n    const WEEK = document.querySelector(\".week\");\n    const TIME = document.querySelector(\".time\");\n    function Adate(){\n      const now = new Date();\n      const weekList = [\"星期一\",\"星期二\",\"星期三\",\"星期四\",\"星期五\",\"星期六\",\"星期日\"];\n      const str = now.getFullYear()+\"-\"+now.getMonth()+\"-\"+now.getDay();\n      DATE.innerHTML = str;\n      WEEK.innerHTML = weekList[now.getDay()];\n    }\n    Adate();\n    setInterval(Adate,24*3600);\n\n    // 分钟，秒，不足两位时，用0进行凑位。\n    function zero(arg){\n      if(arg>=10){\n        return arg;\n      }else{\n        return \"0\"+arg;\n      }\n    }\n    // 显示当前时间的函数\n    function Atime(){\n      const now = new Date();\n      const str = now.getHours()+\":\"+zero(now.getMinutes())+\":\"+zero(now.getSeconds());\n      TIME.innerHTML=str;\n    }\n    Atime();\n    setInterval(Atime,1000);\n  }\n  left();\n  right();\n"
  },
  {
    "path": "02 - JS and CSS Clock/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>RealTimeClock</title>\n    <link rel=\"stylesheet\" href=\"click.css\">\n</head>\n<body>\n    <div class=\"clock\">\n        <div class=\"clock-face\">\n            <div class=\"hand hour-hand\"></div>\n            <div class=\"hand min-hand\"></div>\n            <div class=\"hand second-hand\"></div>\n        </div>\n    </div>\n    <div class=\"dateblock\">\n      <div class=\"date\"></div>\n      <div class=\"week\"></div>\n      <div class=\"time\"></div>\n    </div>\n    <script src=\"clock.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "02 - JS and CSS Clock/style.css",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-11-27 20:14:35\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-11-29 12:41:57\n*/\nhtml{\n    /*font-size:625%,默认字体大小都是16px,16*62.5=100px,1rem=100px*/\n    font-size:625%;\n    background: #018DED url(http://unsplash.it/1500/1000?image=881&blur=50) bottom center ;\n    background-size: cover;\n}\nhtml,body{\n    margin:0px;\n    padding:0px;\n    display: flex;\n    min-height: 100vh;\n    justify-content:center;\n    align-items:center;\n}\n/*采用的是标准盒模型，即是纯宽高*/\n.clock{\n    position:relative;\n    width:3rem;\n    height:3rem;\n    border:0.2rem solid white;\n    margin:0.5rem auto;\n    padding:0.2rem;\n    background: rgba(0,0,0,0.4);\n    border-radius:50%;\n    box-shadow:0 0 2px 4px rgba(0,0,0,0.1),\n               0 0 10px 3px rgba(0,0,0,0.2),\n               0 0 1px 2px #EFEFEF inset,\n               0 0 30px black inset;\n}\n.clock-face{\n    position:relative;\n    width:100%;  /*这里的100%是300px,是clock的宽*/\n    height:100%;\n    /*transform: translateY(-30px);*/\n}\n/*时钟表表盘中心圆点*/\n.clock-face::after{\n    content:'';\n    display: block;\n    width:.1rem;\n    height:.1rem;\n    background-color: #a8c5d1;\n    position: absolute;\n    left:50%;\n    top:50%;\n    transform:translate(-50%,-50%);\n    border-radius:50%;\n}\n/*指针通用样式，在sass中可以封装成一个mixin*/\n.hand{\n    background: #fff;\n    position:absolute;\n    top:50%;\n    right:50%;\n    /*transform:translateY(-50%); 虽说这样可以使指针居中线，但是translate的平移是相对于自身center位置的，那么这样居中处理后，下面的旋转仍旧按的是平移之前的right位置为原点，虽说三个指针通过translateY看似处于一条中线上，实际旋转时仍然是按照各自的right位置进行旋转*/ \n    /*transform:rotate(90deg);*/\n    transform-origin:100% 50%;\n    box-shadow: \n      0 0 0 1px rgba(0, 0, 0, 0.1),\n      0 0 8px rgba(0, 0, 0, 0.4),\n      2px 5px 1px rgba(0, 0, 0, 0.5);\n    transition-timing-function:linear;\n}\n\n/*时针样式*/\n.hour-hand{\n    width:40%;\n    height:0.1rem;\n    margin-top:-0.05rem;\n    border-bottom-left-radius: .05rem;\n    border-top-left-radius:.05rem;\n    transition:all 3s;\n}\n.min-hand {\n    width: 45%;\n    height: .05rem;\n    margin-top:-0.025rem;\n    transition: all .1s;\n}\n.second-hand {\n    width: 50%;\n    height: .02rem;\n    margin-top:-0.01rem;\n    border-bottom-left-radius: .02rem;\n    border-top-left-radius: .02rem;\n    transition: all .05s;\n    background-color: red;\n}\n/*日期，时间，星期几的样式*/\n.dateblock{\n    width: 5rem;\n    position: relative;\n    font-size:.7rem;\n    font-family:serif;\n    font-weight:bold;\n    text-align: center;\n    color:white;\n}\n"
  },
  {
    "path": "03 - CSS Variables/README.md",
    "content": "# Day03 - CSS 变量\n\n## 实现效果\n\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/03%20-%20CSS%20Variables/GIF.gif)\n\n## HTML源码\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CSS Variables</title>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n    <h2>Update <span class=\"h1\">CSS</span> Variables with <span class=\"h1\">JS</span></h2>\n    <div class=\"controls\">\n        <div class=\"wrap\">\n            <label for=\"spacing\">Spacing:</label>\n            <input type=\"range\" id=\"spacing\" min=\"10\" max=\"200\" value=\"10\" onchange=\"spacingchange()\">\n        </div>\n        <div class=\"wrap\">\n            <label for=\"blur\">Blur:</label>\n            <input type=\"range\" id=\"blur\" min=\"0\" max=\"25\" value=\"10\" onchange=\"blurchange()\">\n        </div>\n        <div class=\"wrap\">\n            <label for=\"base\">Base Color:</label>\n            <input type=\"color\" id=\"base\" name=\"base\" value=\"#ffc600\" onchange=\"basechange\">\n        </div>\n    </div>\n    <img id=\"img\" src=\"http://f.hiphotos.baidu.com/lvpics/h=800/sign=b346032cbe389b5027ffed52b534e5f1/960a304e251f95ca545f8b84ce177f3e6709525d.jpg\" alt=\"演示图片\">\n    <script src=\"variables.js\"> </script>\n</body>\n</html>\n```\n\n## CSS源码\n\n```css\n:root{\n    --spacing:10px;\n    --blur:10px;\n    --base:#ffc600;\n    --fontsize:10px;\n}\n\nhtml,body{\n    text-align:center;\n    background: #193549;\n    margin:0;\n    padding:0;\n    min-height: 100vh;\n    font-size:calc(3*var(--fontsize));\n    font-family:'helvetica neue', sans-serif; /*Helvetica是一种被广泛使用的的西文字体(铅字体）,用于印刷行业,Helvetica是苹果电脑的默认字体，微软常用的Arial字体也来自于它*/\n    font-weight:900;/*设置字体粗细：100---900,400=normal,700=bolder*/\n    color:white;\n}\n\n.h1{\n    color:var(--base);\n}\n.controls{\n    font-weight:100;\n    margin-bottom:calc(5*var(--fontsize));\n}\n.controls .wrap{\n    display: inline-block;\n    margin:5px auto;\n}\n.controls .wrap label{\n    margin-left:20px;\n}\n.controls .wrap input{\n    position:relative;\n    top:3px;\n    width:calc(10*var(--fontsize));\n}\n.controls .wrap #base{\n    top:-3px;\n}\nimg{\n    width:calc(60*var(--fontsize));\n    height:calc(40*var(--fontsize));\n    padding:var(--spacing);\n    filter:blur(var(--blur));\n    background-color:var(--base);/*背景颜色填充内容、内边距、边框，作为打底色*/\n}\n```\n\n\n## JS源码\n\n```js\nfunction spacingchange(){\n    var spacing=document.querySelector(\"#spacing\");\n    document.body.style.setProperty('--spacing', spacing.value+'px');\n    // var img=document.querySelector(\"#img\");\n    // img.style.padding=spacing.value+'px';一个需要改变的元素可以将其取出，改变它的对应项，但有100个需要改变的元素时，改变根变量值最有效。\n}\nfunction blurchange(){\n    var blur=document.querySelector(\"#blur\");\n    document.body.style.setProperty(\"--blur\",blur.value+'px');\n}\nfunction basechange(){\n    var base=document.querySelector(\"#base\");\n    document.body.style.setProperty(\"--base\",base.value);\n}\n```\n\n## 过程指南\n\n### CSS 部分准备\n\n1. 声明全局（`:root`）的 CSS 变量  \n2. 将变量应用到页面中对应元素 `<img>`   \n3. 处理标题的 CSS 值  \n\n### JS 实时更新 CSS 值\n1. 监听input的change改变函数，然后触发各自的事件处理函数    \n2. 每个事件中，先取出该元素，然后设置CSS的原生变量值，进而下面的所有样式中凡是用到这个变量的值都跟着改变。  \n改进：利用字符串模板+遍历添加事件的方法，给每一个input元素添加事件处理函数。  \n\n## 基础知识\n\n1. NodeList 和 Array 的区别\n\n\t可以打开 __proto__ 查看它的方法，其中有 `forEach()`、`item()`、`keys()` 等。而 Array 的 prototype 中有 `map()`、`pop()` 等数组才有的方法。\n\t\n3. HTML5 中的自定义数据属性 `dataset`\n\n\tHTML5 中可以为元素添加非标准的自定义属性，只需要加上 `data-` 前缀，可以随便添加和命名。添加之后，可以通过元素的 `dataset` 属性来访问这些值，`dataset` 的值是 DOMStringMap 的一个实例化对象，其中包含之前所设定的自定义属性的“名-值”对。\n\t\n4. [CSS variable](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Using_CSS_variables)\n\n\t这是一个 CSS3 的新特性，[IE 和 Edge 目前都还不支持](http://caniuse.com/#feat=css-variables)。命名写法是 `--变量名`，在引用这个变量时写法是 `var(--变量名)`。具体实例见下一条代码。\n\t\n5. `:root` 伪类\n\n\t这个伪元素匹配的是文档的根元素，也就是 `<html>` 标签。\n\t\n\t所以常用于声明全局的 CSS 变量：\n\t\n\t```css\n\t:root {\n\t  --color: #fff;\n\t}\n\t```\n\t\n\t在使用时：\n\t\n\t```css\n\timg {\n\t  background: var(--base);\n\t}\n\t```\n\t\n5. CSS 滤镜 [filter](https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter)\n\n\tCSS 的滤镜提供了一些图形特效，比如高斯模糊（blur）、锐化、变色等。它带有一些预设的函数，在使用时加上参数调用这些函数即可。[在 Chrome、Firefox 中都支持。](http://caniuse.com/#search=filter)  \n6. `<input type=\"range\">`HTML5中type属性之range   \nrange 输入类型用于应该包含指定范围值的输入字段。  \nrange 类型显示为滑块。  \n您也可以设置可接受数字的限制：  \n`<input type=\"range\" name=\"points\" min=\"1\" max=\"10\" />` \n\n|属性|值|描述|\n|:---------:|:---------:|:---------:|\n|max|number|规定允许的最大值。| \n|min|number|规定允许的最小值。| \n|step|number|规定合法数字间隔（如果 step=\"3\"，则合法数字是 -3,0,3,6，以此类推）。| \n|value|number|规定默认值。|   \n\n7. `<input type=\"color\">`HTML5中type属性之color  \ncolor输入类型用于规定颜色。  \n该输入类型允许您从拾色器中选取颜色。  \n实例：  \n`Color: <input type=\"color\" name=\"user_color\" />`  \n8. font-weight 属性设置文本的粗细。  \n> normal\t默认值。定义标准的字符。  \nbold\t定义粗体字符。  \nbolder\t定义更粗的字符。  \nlighter\t定义更细的字符。  \n100,\n200,\n300,\n400,\n500,\n600,\n700,\n800,\n900:定义由粗到细的字符。400 等同于 normal，而 700 等同于 bold。  \ninherit\t规定应该从父元素继承字体的粗细。  \n9. background-color 属性为元素设置一种纯色。  \n这种颜色会填充元素的内容、内边距和边框区域，但不包括外边距。如果边框有透明部分（如虚线边框dashed），会透过这些透明部分显示出背景色。   \n## 解决难点\n\n1. **如何处理参数值（一个有 px 、另一个没有）**\n\n\t运用 `dataset` 储存后缀，有 px 后缀的标签中设置 `<input data-sizing: px>`：\n\t\n\t```html\n\t<input type=\"range\" name=\"blur\" min=\"0\" max=\"25\" value=\"10\" data-sizing=\"px\">\n    <input type=\"color\" name=\"base\" value=\"#8aa8af\">\n\t```\n\t\n\tJS 中通过 `dataset.sizing` 来获取后缀值：\n\n\t```javascript\n\tconst suffix = this.dataset.sizing || ''; \n\t```\n\t\n\t此时 suffix 获取到的值，针对颜色为空，而针对长度类的则为 'px'。\n\t\n2. \t**如何用 JavaScript 改变 CSS 属性值？**\n\n\t在 JavaScript 中 `document.documentElement` 即代表文档根元素。所以要改变全局的 CSS 变量，可以这样写：\n\t\n\t```js\n\tdocument.documentElement.style.setProperty('--base', '#fff');\n\t```\n\n\n\n\n\n"
  },
  {
    "path": "03 - CSS Variables/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>CSS Variables</title>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n    <h2>Update <span class=\"h1\">CSS</span> Variables with <span class=\"h1\">JS</span></h2>\n    <div class=\"controls\">\n        <div class=\"wrap\">\n            <label for=\"spacing\">Spacing:</label>\n            <input type=\"range\" id=\"spacing\" min=\"10\" max=\"200\" value=\"10\" onchange=\"spacingchange()\">\n        </div>\n        <div class=\"wrap\">\n            <label for=\"blur\">Blur:</label>\n            <input type=\"range\" id=\"blur\" min=\"0\" max=\"25\" value=\"10\" onchange=\"blurchange()\">\n        </div>\n        <div class=\"wrap\">\n            <label for=\"base\">Base Color:</label>\n            <input type=\"color\" id=\"base\" name=\"base\" value=\"#ffc600\" onchange=\"basechange\">\n        </div>\n    </div>\n    <img id=\"img\" src=\"http://f.hiphotos.baidu.com/lvpics/h=800/sign=b346032cbe389b5027ffed52b534e5f1/960a304e251f95ca545f8b84ce177f3e6709525d.jpg\" alt=\"演示图片\">\n    <script src=\"variables.js\"> </script>\n</body>\n</html>"
  },
  {
    "path": "03 - CSS Variables/style.css",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-11-30 20:09:15\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-12-03 21:57:37\n*/\n:root{\n    --spacing:10px;\n    --blur:10px;\n    --base:#ffc600;\n    --fontsize:10px;\n}\n\nhtml,body{\n    text-align:center;\n    background: #193549;\n    margin:0;\n    padding:0;\n    min-height: 100vh;\n    font-size:calc(3*var(--fontsize));\n    font-family:'helvetica neue', sans-serif; /*Helvetica是一种被广泛使用的的西文字体(铅字体）,用于印刷行业,Helvetica是苹果电脑的默认字体，微软常用的Arial字体也来自于它*/\n    font-weight:900;/*设置字体粗细：100---900,400=normal,700=bolder*/\n    color:white;\n}\n\n.h1{\n    color:var(--base);\n}\n.controls{\n    font-weight:100;\n    margin-bottom:calc(5*var(--fontsize));\n}\n.controls .wrap{\n    display: inline-block;\n    margin:5px auto;\n}\n.controls .wrap label{\n    margin-left:20px;\n}\n.controls .wrap input{\n    position:relative;\n    top:3px;\n    width:calc(10*var(--fontsize));\n}\n.controls .wrap #base{\n    top:-3px;\n}\nimg{\n    width:calc(60*var(--fontsize));\n    height:calc(40*var(--fontsize));\n    padding:var(--spacing);\n    filter:blur(var(--blur));\n    background-color:var(--base);/*背景颜色填充内容、内边距、边框，作为打底色*/\n}"
  },
  {
    "path": "03 - CSS Variables/variables.js",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-11-30 20:09:45\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-12-03 22:17:32\n*/\nfunction spacingchange(){\n    var spacing=document.querySelector(\"#spacing\");\n    document.body.style.setProperty('--spacing', spacing.value+'px');\n    // var img=document.querySelector(\"#img\");\n    // img.style.padding=spacing.value+'px';一个需要改变的元素可以将其取出，改变它的对应项，但有100个需要改变的元素时，改变根变量值最有效。\n}\nfunction blurchange(){\n    var blur=document.querySelector(\"#blur\");\n    document.body.style.setProperty(\"--blur\",blur.value+'px');\n    // document.documentElement.style.setProperty(\"--blur\",blur.value+'px');\n    // 在 JavaScript 中 document.documentElement 即代表文档根元素。所以要改变全局的 CSS 变量，可以这样写\n\n}\nfunction basechange(){\n    var base=document.querySelector(\"#base\");\n    document.body.style.setProperty(\"--base\",base.value);\n}"
  },
  {
    "path": "04 - Array Cardio Day 1/README.md",
    "content": "# Day04 - Array Cardio 指南一\n\n\n## 实现效果\n\n这一部分主要是熟悉 Array 的几个基本方法，其中有两个（filter、map）是 ES6 定义的迭代方法，这些迭代方法都有一个特点，就是对数组的每一项都运行给定函数，根据使用的迭代方法的不同，有不同的返回结果。\n\n文档给出了一个初始操作的 `inventor` 数组，基于这个数组可以练习一下`Array`的各个方法，请用`Google Chrome`浏览器打开 `HTML` 后在`Console`面板中查看输出结果。\n\n## 炫酷的调试技巧\n\n在 Console 中我们常用到的可能是 `console.log()` ，但它还有一个很炫的输出，按照表格来输出，效果如下：\n\n```js\nconsole.table(thing)\n```\n![console.table()](http://om1c35wrq.bkt.clouddn.com/day4-000.png)\n\n## 原始数据\n\n本节中我们将围绕如下数据进行相关操作以便快速掌握数组的相关方法的使用。\n\n```js\n    const inventors = [{\n        first: 'Albert',\n        last: 'Einstein',\n        year: 1879,\n        passed: 1955\n      },\n      {\n        first: 'Isaac',\n        last: 'Newton',\n        year: 1643,\n        passed: 1727\n      },\n      {\n        first: 'Galileo',\n        last: 'Galilei',\n        year: 1564,\n        passed: 1642\n      },\n      {\n        first: 'Marie',\n        last: 'Curie',\n        year: 1867,\n        passed: 1934\n      },\n      {\n        first: 'Johannes',\n        last: 'Kepler',\n        year: 1571,\n        passed: 1630\n      },\n      {\n        first: 'Nicolaus',\n        last: 'Copernicus',\n        year: 1473,\n        passed: 1543\n      },\n      {\n        first: 'Max',\n        last: 'Planck',\n        year: 1858,\n        passed: 1947\n      },\n      {\n        first: 'Katherine',\n        last: 'Blodgett',\n        year: 1898,\n        passed: 1979\n      },\n      {\n        first: 'Ada',\n        last: 'Lovelace',\n        year: 1815,\n        passed: 1852\n      },\n      {\n        first: 'Sarah E.',\n        last: 'Goode',\n        year: 1855,\n        passed: 1905\n      },\n      {\n        first: 'Lise',\n        last: 'Meitner',\n        year: 1878,\n        passed: 1968\n      },\n      {\n        first: 'Hanna',\n        last: 'Hammarström',\n        year: 1829,\n        passed: 1909\n      }\n    ];\n\n    const people = ['Beck, Glenn', 'Becker, Carl', 'Beckett, Samuel', 'Beddoes, Mick', 'Beecher, Henry',\n      'Beethoven, Ludwig', 'Begin, Menachem', 'Belloc, Hilaire', 'Bellow, Saul', 'Benchley, Robert',\n      'Benenson, Peter', 'Ben-Gurion, David', 'Benjamin, Walter', 'Benn, Tony', 'Bennington, Chester',\n      'Benson, Leana', 'Bent, Silas', 'Bentsen, Lloyd', 'Berger, Ric', 'Bergman, Ingmar', 'Berio, Luciano',\n      'Berle, Milton', 'Berlin, Irving', 'Berne, Eric', 'Bernhard, Sandra', 'Berra, Yogi', 'Berry, Halle',\n      'Berry, Wendell', 'Bethea, Erin', 'Bevan, Aneurin', 'Bevel, Ken', 'Biden, Joseph', 'Bierce, Ambrose',\n      'Biko, Steve', 'Billings, Josh', 'Biondo, Frank', 'Birrell, Augustine', 'Black Elk', 'Blair, Robert',\n      'Blair, Tony', 'Blake, William'\n    ];\n    \n    const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car',\n      'truck', 'pogostick'\n    ];\n\n```\n\n## 筛选 16 世纪出生的发明家\n\n[filter](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter)\n\n过滤操作，有点像 SQL 里面的 select 语句。筛出运行结果是 true 的组成数组返回。\n\n````js\nconst __fifteen = inventors.filter(function(inventor) {\n  if (inventor.year >= 1500 && inventor.year < 1600 ) {\n\t  return true;\n  } else {\n      return false;\n  }\n});\nconsole.table(__fifteen);\n````\t  \n\n前面几篇已经提到过箭头函数，这里可以简化一下，用箭头函数来写，而且由于 if 语句的存在并不是必要的，可以写成下面这样：\n\n````js\nconst fifteen = inventors.filter(inventor =>(inventor.year >= 1500 && inventor.year < 1600));\nconsole.table(fifteen);\n````\n\n控制台效果图：\n\n![](http://om1c35wrq.bkt.clouddn.com/day4-001.png)\n\n  \n## 展示他们的姓和名\n\n[map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map)\n\nmap 形象的理解就是，把数组中的每个元素进行处理后，返回一个新的数组。\n\n例子如下：\n\n````js \n// Array.prototype.map()\n// 2. 展示他们的姓和名\nconst fullNames = inventors.map(inventor => `${inventor.first} ${inventor.last}`);\nconsole.log(fullNames);\n````\n控制台效果图：\n\n![](http://om1c35wrq.bkt.clouddn.com/day4-002.png)\n\n\n## 把他们按照年龄从大到小进行排序\n\n[sort](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort)\n\n默认情况下，`Array.prototype.sort()` 会将数组以字符串的形式进行升序排列（10 会排在 2 之前），但 sort 也可以接受一个函数作为参数。所以需要对数字大小排序时需要自己设定一个比较函数，例子如下：\n\n````js\n// Array.prototype.sort()\n// 3. 把他们按照年龄从大到小进行排序\nconst ordered = inventors.sort((a, b) => {\n if(a.year > b.year) {\n   return 1;\n } else {\n   return -1;\n }\n});\n\nconsole.table(ordered);\n````\n\n上面的代码可以简写成：\n\n```js\nconst ordered = inventors.sort((a, b) => a.year > b.year ? 1 : -1);\nconsole.table(ordered);\n```\n\n控制台效果图：\n\n![](http://om1c35wrq.bkt.clouddn.com/day4-003.png)\n\n\n## 计算所有的发明家加起来一共活了多少岁\n\n[reduce](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)\n\n```js\n// Array.prototype.reduce()\n// 4. 计算所有的发明家加起来一共活了多少岁\n<!--0为total的初始值-->\nconst totalYears = inventors.reduce((total, inventor) => {\n return total + (inventor.passed - inventor.year);\n}, 0);\n\nconsole.log(totalYears);\n```\n\n控制台效果图：\n![](http://om1c35wrq.bkt.clouddn.com/day4-004.png)\n\n\n## 按照他们活了多久来进行排序\n\n```js\n// 5. 按照他们活了多久来进行排序\nconst oldest = inventors.sort((a, b) => {\n const lastInventor = a.passed - a.year;\n const nextInventor = b.passed - b.year;\n return lastInventor > nextInventor ? -1 : 1;\n});\nconsole.table(oldest);\n```\n\n控制台效果图：\n\n![](http://om1c35wrq.bkt.clouddn.com/day4-005.png)\n\n\n## `map、filter`结合使用筛选出网页中含有`CSS`标题的数据名称\n\n\n\n```js\nconst category = document.querySelectorAll('.subject-list h2 a');\nconst links = Array.from(category);\nconst CSS_BOOK = links\n           .map(link => link.textContent)\n           .filter(streetName => streetName.includes('CSS'));\n```\n\n由 `querySelectorAll()` 获取到的是一个 `NodeList` ，它并非是 Array 类型的数据，所以并不具有 map 和 filter 这样的方法，所以如果要进行筛选操作则需要把它转化成 Array 类型，使用下面示例之中的 `Array.from()` 来转化。\n\nGoogle Chrome浏览球操作如下：\n\n- 打开`https://book.douban.com/tag/web`网页。\n- 在控制台按如下图操作即可\n\n![](http://om1c35wrq.bkt.clouddn.com/day4-006.png)\n\n\n## 按照姓氏来对发明家进行排序\n\n```js\nconst alpha = people.sort((lastOne, nextOne) => {\n const [aLast, aFirst] = lastOne.split(', ');\n const [bLast, bFirst] = nextOne.split(', ');\n return aLast > bLast ? 1 : -1;\n});\nconsole.log(alpha);\n```\n\n控制台效果图：\n\n![](http://om1c35wrq.bkt.clouddn.com/day4-007.png)\n\n\n## 统计给出数组中各个物品的数量\n\n[reduce](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce)\n\n这是一个归并数组的方法，它接受一个函数作为参数（这个函数可以理解成累加器），它会遍历数组的所有项，然后构建一个最终的返回值，这个值就是这个累加器的第一个参数。第二个参数中的`0`是`previousValue`的初始值，例子如下：\n\n````js\n[0,1,2,3,4].reduce((previousValue, currentValue, index, array) => {\n  return previousValue + currentValue;\n},0);\n````\n\n而此处我们需要统计一个给定数组中各个项的值，恰好可以用到这个方法，在累加器之中，将统计信息存入一个新的对象，最后返回统计值。\n\n```js\nconst data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car',\n      'truck', 'pogostick'\n    ];\n\nconst transportation = data.reduce( (obj, item) => {\n if (!obj[item]) {\n   obj[item] = 0;\n }\n obj[item]++;\n return obj;\n}, {});\n\nconsole.log(transportation);\n```\n```JS\n    var data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car','truck', 'pogostick'\n    ];\ndata.reduce((acc,cur)=>{\n\t\t\tcur in acc ? acc[cur]++ : acc[cur]=1; \n\t\t\treturn acc;\n\t\t\t},{});\n```\n![](http://om1c35wrq.bkt.clouddn.com/day4-008.png)\n\n\n\n\n"
  },
  {
    "path": "04 - Array Cardio Day 1/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Array Cardio 💪</title>\n</head>\n\n<body>\n  <p><em>打开Google Chrome浏览器，查看JavaScript 控制台。</em> 💁</p>\n  <script>\n    // Get your shorts on - this is an array workout!\n    // ## Array Cardio Day 1\n\n    // 构造一些我们需要使用到的数据\n\n    const inventors = [{\n        first: 'Albert',\n        last: 'Einstein',\n        year: 1879,\n        passed: 1955\n      },\n      {\n        first: 'Isaac',\n        last: 'Newton',\n        year: 1643,\n        passed: 1727\n      },\n      {\n        first: 'Galileo',\n        last: 'Galilei',\n        year: 1564,\n        passed: 1642\n      },\n      {\n        first: 'Marie',\n        last: 'Curie',\n        year: 1867,\n        passed: 1934\n      },\n      {\n        first: 'Johannes',\n        last: 'Kepler',\n        year: 1571,\n        passed: 1630\n      },\n      {\n        first: 'Nicolaus',\n        last: 'Copernicus',\n        year: 1473,\n        passed: 1543\n      },\n      {\n        first: 'Max',\n        last: 'Planck',\n        year: 1858,\n        passed: 1947\n      },\n      {\n        first: 'Katherine',\n        last: 'Blodgett',\n        year: 1898,\n        passed: 1979\n      },\n      {\n        first: 'Ada',\n        last: 'Lovelace',\n        year: 1815,\n        passed: 1852\n      },\n      {\n        first: 'Sarah E.',\n        last: 'Goode',\n        year: 1855,\n        passed: 1905\n      },\n      {\n        first: 'Lise',\n        last: 'Meitner',\n        year: 1878,\n        passed: 1968\n      },\n      {\n        first: 'Hanna',\n        last: 'Hammarström',\n        year: 1829,\n        passed: 1909\n      }\n    ];\n\n    \n    // // Array.prototype.filter()\n    // // 1. 筛选 16 世纪出生的发明家\n    // const fifteen = inventors.filter(inventor => (inventor.year >= 1500 && inventor.year < 1600));\n\n    // console.table(fifteen);\n\n    // // Array.prototype.map()\n    // // 2. 展示他们的姓和名\n    // const fullNames = inventors.map(inventor => `${inventor.first} ${inventor.last}`);\n    // console.log(fullNames);\n\n    // Array.prototype.sort()\n    // 3. 把他们按照年龄从大到小进行排序\n    // const ordered = inventors.sort((a, b) => {\n    //   if(a.year > b.year) {\n    //     return 1;\n    //   } else {\n    //     return -1;\n    //   }\n    // });\n\n    // console.table(ordered);\n\n    // const ordered = inventors.sort((a, b) => a.year > b.year ? 1 : -1);\n    // console.table(ordered);\n\n    // // Array.prototype.reduce()\n    // // 4. 计算所有的发明家加起来一共活了多少岁\n    // const totalYears = inventors.reduce((total, inventor) => {\n    //   return total + (inventor.passed - inventor.year);\n    // }, 0);\n\n    // console.log(totalYears);\n\n    // // 5. 按照他们活了多久来进行排序\n    // const oldest = inventors.sort((a, b) => {\n    //   const lastInventor = a.passed - a.year;\n    //   const nextInventor = b.passed - b.year;\n    //   return lastInventor > nextInventor ? -1 : 1;\n    // });\n    // console.table(oldest);\n\n    // 6. 筛选出下面网页里含有某个词语的标题\n    // https://book.douban.com/tag/web\n\n    // const category = document.querySelectorAll('.subject-list h2 a');\n    // // 由 querySelectorAll() 获取到的是一个 NodeList ，它并非是 Array 类型的数据，所以并不具有 map 和 filter 这样的方法，所以如果要进行筛选操作则需要把它转化成 Array 类型，使用下面示例之中的 Array.from() 来转化。\n    // const links = Array.from(category);\n    // const CSS_BOOK = links\n    //             .map(link => link.textContent)\n    //             .filter(streetName => streetName.includes('CSS'));\n\n    // // 7. sort 练习\n    // // 按照姓氏来对发明家进行排序\n\n    // const people = ['Beck, Glenn', 'Becker, Carl', 'Beckett, Samuel', 'Beddoes, Mick', 'Beecher, Henry',\n    //   'Beethoven, Ludwig', 'Begin, Menachem', 'Belloc, Hilaire', 'Bellow, Saul', 'Benchley, Robert',\n    //   'Benenson, Peter', 'Ben-Gurion, David', 'Benjamin, Walter', 'Benn, Tony', 'Bennington, Chester',\n    //   'Benson, Leana', 'Bent, Silas', 'Bentsen, Lloyd', 'Berger, Ric', 'Bergman, Ingmar', 'Berio, Luciano',\n    //   'Berle, Milton', 'Berlin, Irving', 'Berne, Eric', 'Bernhard, Sandra', 'Berra, Yogi', 'Berry, Halle',\n    //   'Berry, Wendell', 'Bethea, Erin', 'Bevan, Aneurin', 'Bevel, Ken', 'Biden, Joseph', 'Bierce, Ambrose',\n    //   'Biko, Steve', 'Billings, Josh', 'Biondo, Frank', 'Birrell, Augustine', 'Black Elk', 'Blair, Robert',\n    //   'Blair, Tony', 'Blake, William'\n    // ];\n\n    // const alpha = people.sort((lastOne, nextOne) => {\n    //   const [aLast, aFirst] = lastOne.split(', ');\n    //   const [bLast, bFirst] = nextOne.split(', ');\n    //   return aLast > bLast ? 1 : -1;\n    // });\n    // console.log(alpha);\n\n    // 8. Reduce 练习\n    // 统计给出数组中各个物品的数量\n    const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car',\n      'truck', 'pogostick'\n    ];\n\n    const transportation = data.reduce( (obj, item) => {\n      if (!obj[item]) {\n        obj[item] = 0;\n      }\n      obj[item]++;\n      return obj;\n    }, {});\n\n    console.log(transportation);\n\n\n\n\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "04 - Array Cardio Day 1/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Array Cardio 💪</title>\n</head>\n<body>\n  <p><em>打开Google Chrome浏览器，查看JavaScript 控制台。</em> 💁</p>\n  <script>\n    // Get your shorts on - this is an array workout!\n    // ## Array Cardio Day 1\n\n    // Some data we can work with\n\n    const inventors = [\n      { first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 },\n      { first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 },\n      { first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 },\n      { first: 'Marie', last: 'Curie', year: 1867, passed: 1934 },\n      { first: 'Johannes', last: 'Kepler', year: 1571, passed: 1630 },\n      { first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 },\n      { first: 'Max', last: 'Planck', year: 1858, passed: 1947 },\n      { first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 },\n      { first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 },\n      { first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 },\n      { first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 },\n      { first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 }\n    ];\n\n    const people = ['Beck, Glenn', 'Becker, Carl', 'Beckett, Samuel', 'Beddoes, Mick', 'Beecher, Henry', 'Beethoven, Ludwig', 'Begin, Menachem', 'Belloc, Hilaire', 'Bellow, Saul', 'Benchley, Robert', 'Benenson, Peter', 'Ben-Gurion, David', 'Benjamin, Walter', 'Benn, Tony', 'Bennington, Chester', 'Benson, Leana', 'Bent, Silas', 'Bentsen, Lloyd', 'Berger, Ric', 'Bergman, Ingmar', 'Berio, Luciano', 'Berle, Milton', 'Berlin, Irving', 'Berne, Eric', 'Bernhard, Sandra', 'Berra, Yogi', 'Berry, Halle', 'Berry, Wendell', 'Bethea, Erin', 'Bevan, Aneurin', 'Bevel, Ken', 'Biden, Joseph', 'Bierce, Ambrose', 'Biko, Steve', 'Billings, Josh', 'Biondo, Frank', 'Birrell, Augustine', 'Black, Elk', 'Blair, Robert', 'Blair, Tony', 'Blake, William'];\n\n    // Array.prototype.filter()\n    // 1. Filter the list of inventors for those who were born in the 1500's\n\n    // Array.prototype.map()\n    // 2. Give us an array of the inventors' first and last names\n\n    // Array.prototype.sort()\n    // 3. Sort the inventors by birthdate, oldest to youngest\n\n    // Array.prototype.reduce()\n    // 4. How many years did all the inventors live?\n\n    // 5. Sort the inventors by years lived\n\n    // 6. create a list of Boulevards in Paris that contain 'de' anywhere in the name\n    // https://en.wikipedia.org/wiki/Category:Boulevards_in_Paris\n\n\n    // 7. sort Exercise\n    // Sort the people alphabetically by last name\n\n    // 8. Reduce Exercise\n    // Sum up the instances of each of these\n    const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car', 'truck' ];\n\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "05 - Flex Panel Gallery/README.md",
    "content": "# Day05 - Flex 实现可伸缩的图片墙 中文指南\n\n## 实现效果\n\n点击任意一张图片，图片展开，同时从图片上下两方分别移入文字。点击已经展开的图片后，图片被压缩，同时该图片上下两端的文字被挤走。\n\n![](http://om1c35wrq.bkt.clouddn.com/day5_00.gif)\n\n\n\n## HTML源码\n\n```html\n  <div class=\"panels\">\n    <div class=\"panel panel1\">\n      <p>Hey</p>\n      <p>Let's</p>\n      <p>Dance</p>\n    </div>\n    <div class=\"panel panel2\">\n      <p>Give</p>\n      <p>Take</p>\n      <p>Receive</p>\n    </div>\n    <div class=\"panel panel3\">\n      <p>Experience</p>\n      <p>It</p>\n      <p>Today</p>\n    </div>\n    <div class=\"panel panel4\">\n      <p>Give</p>\n      <p>All</p>\n      <p>You can</p>\n    </div>\n    <div class=\"panel panel5\">\n      <p>Life</p>\n      <p>In</p>\n      <p>Motion</p>\n    </div>\n  </div>\n```\n\n\n\n初始文档的 DOM 结构：以 `.panels` 为父 `div` 之下，有 5 个类名为 `.panel` 的 `div`，这 5 个各含有 3 个子 `p` 标签。而相应的 CSS 样式中，动画时间等特性已经设定好，只需要完成不同状态下的页面布局以及事件监听即可。\n\n\n## CSS 源码\n\n```css\n  <style>\n    html {\n      box-sizing: border-box;\n      background: #ffc600;\n      font-family:'helvetica neue';\n      font-size: 20px;\n      font-weight: 200;\n    }\n    body {\n      margin: 0;\n    }\n    *, *:before, *:after {\n      box-sizing: inherit;\n    }\n\n    .panels {\n      min-height:100vh;\n      overflow: hidden;\n      display: flex;\n    }\n\n    .panel {\n      background:#6B0F9C;\n      box-shadow:inset 0 0 0 5px rgba(255,255,255,0.1);\n      color:white;\n      text-align: center;\n      align-items:center;\n      /* Safari transitionend event.propertyName === flex */\n      /* Chrome + FF transitionend event.propertyName === flex-grow */\n      transition:\n        font-size 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11),\n        flex 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11),\n        background 0.2s;\n      font-size: 20px;\n      background-size:cover;\n      background-position:center;\n      flex: 1;\n      justify-content: center;\n      display: flex;\n      flex-direction: column;\n    }\n\n\n    .panel1 { background-image:url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500); }\n    .panel2 { background-image:url(https://source.unsplash.com/1CD3fd8kHnE/1500x1500); }\n    .panel3 { background-image:url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d); }\n    .panel4 { background-image:url(https://source.unsplash.com/ITjiVXcwVng/1500x1500); }\n    .panel5 { background-image:url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500); }\n\n    /* Flex Items */\n    .panel > * {\n      margin:0;\n      width: 100%;\n      transition:transform 0.5s;\n      flex: 1 0 auto;\n      display:flex;\n      justify-content: center;\n      align-items: center;\n    }\n\n    .panel > *:first-child { transform: translateY(-100%); }\n    .panel.open-active > *:first-child { transform: translateY(0); }\n    .panel > *:last-child { transform: translateY(100%); }\n    .panel.open-active > *:last-child { transform: translateY(0); }\n\n    .panel p {\n      text-transform: uppercase;\n      font-family: 'Amatic SC', cursive;\n      text-shadow:0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45);\n      font-size: 2em;\n    }\n    .panel p:nth-child(2) {\n      font-size: 4em;\n    }\n\n    .panel.open {\n      flex: 5;\n      font-size:40px;\n    }\n\n  </style>\n```\n\nCSS 在这个过程中占了重点，运用 `flex` 可以使各个元素按一定比例占据页面。在调试的时候，可以把边框显示出来方便查看效果。（`border: 1px solid #f00;`）\n\n1. 将 `.panels` 设置为 `display:flex`\n2. 设定每个子 `panel` 的 `flex` 值为 `1`\n3. 针对每个子 `panel`，设为 `display:flex`，设置其 flex 主轴方向\n4. 控制 `.panle` 的子元素 `<p>` 中的文字垂直、水平居中（单独看每个 panel，其中的文字也可以用 flex 的思路来使其三等分后居中）\n\t1. 设置为 `display:flex`\n\t2. 设置 `flex` 值\n\t2. 设置其子元素的布局方式：垂直水平居中（沿主轴、侧轴居中）\n4. 设定点击图片后文字移动的样式\n5. 设定点击图片展开后的图片的 `flex` 值\n\n\n**重要：不了解CSS和Flex的童鞋必看。**\n\n- [CSS参考手册](http://www.css88.com/book/css/properties/flex/flex.htm)\n\n- [选择器（Selectors）](https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Getting_started/Selectors)\n\n- [CSS选择器笔记](http://www.ruanyifeng.com/blog/2009/03/css_selectors.html)\n\n\n- [flex布局完全入门教程](http://bbs.kongyixueyuan.com/topic/10/flex布局完全入门教程)\n\n## JS源码\n\n```js\n  <script>\n    const panels = document.querySelectorAll('.panel');\n\n    function toggleOpen() {\n      console.log('Hello');\n      this.classList.toggle('open');\n    }\n\n    function toggleActive(e) {\n      console.log(e.propertyName);\n      if (e.propertyName.includes('flex')) {\n        this.classList.toggle('open-active');\n      }\n    }\n\n    panels.forEach(panel => panel.addEventListener('click', toggleOpen));\n    panels.forEach(panel => panel.addEventListener('transitionend', toggleActive));\n  </script>\n```\n\n1. 获取所有类名为 `panel` 的元素\n2. 为其添加 `click` 事件监听，编写触发事件调用的函数（给触发的 DOM 元素添加/去掉样式，实现拉伸/压缩的效果）\n3. 为其添加 `transitionend` 事件监听，编写调用的函数（添加/去掉样式，实现文字的飞入/飞出效果）\n\n\n\n\n\n\n"
  },
  {
    "path": "05 - Flex Panel Gallery/animation.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>flexbox gallery</title>\n    <style>\n        :root{\n            --distance:-400px;\n        }\n        html,body{\n            margin:0px;\n            padding:0px;\n        }\n        .show{\n            width:100%;\n            display: flex;\n            /*设为Flex布局以后，子元素的float、clear和vertical-align属性将失效。*/\n        }\n        .show .wrap{\n            flex:1;   /*flex-grow:1在这里便没有效果*/\n            min-height: 100vh;\n            padding:10px;\n            text-align: center;\n            font-size: 80px;\n            font-family: sans-serif;\n            font-weight: 700;\n            color: white;\n            display: flex;\n            flex-direction: column;\n            justify-content: center;\n            align-items:center;\n            /*overflow: hidden;*/\n            word-break: break-all;/*单词换行，可以任意断开换行*/\n            /*animation:extend 2s ease forwords:处于动画结束处;*/\n            background-size:cover;\n            background-repeat: no-repeat;\n\n        }\n        .show .wrap:hover{\n            box-shadow: 0 0 10px 5px rgba(0,0,0,0.4);\n            color:orangered;\n        }\n        .show .wrap p{\n            transition:transform 0.5s ease;\n        }\n        .wrap p:nth-child(1){\n            transform: translateY(-500px);\n        }\n        .wrap.open-active p:nth-child(1){\n            transform: translateY(0);\n        }\n        .wrap p:nth-child(3){\n            transform: translateY(500px);\n        }\n        .wrap.open-active p:nth-child(3){\n            transform:translate(0);\n        }\n        @keyframes extend{\n            40%{\n                flex-grow:0.8;\n            }\n            100%{\n                flex-grow:3;\n            }\n        }\n        @keyframes shink{\n            0%{\n                flex-grow:3;\n            }\n            40%{\n                flex-grow:3.2;\n            }\n            100%{\n                flex-grow:1;\n            }\n        }\n        .first  {background: url(./image/1.jpg);}\n        .second {background: url(./image/2.jpg);}\n        .third  {background: url(./image/3.jpg);}\n        .forth  {background: url(./image/4.jpg);}\n        .fifth  {background: url(./image/5.jpg);}\n\n        /*媒体查询and后面无空格不生效*/\n        /*800《=屏幕尺寸《=1250*/\n        @media screen and (min-width: 800px) and (max-width: 1250px){\n            .show .wrap{\n                font-size: 60px;\n            }\n            .wrap p:nth-child(2n+1){\n                font-family: serif;\n                font-size:50px;\n            }\n        }\n        /*屏幕尺寸《=800px*/\n        @media screen and (max-width: 800px){\n            .show .wrap{\n                font-size:30px;\n            }\n            .wrap p:nth-child(2n+1){\n                font-family: serif;\n                font-size:20px;\n            }\n        }\n\n    </style>\n</head>\n<body>\n    <div class=\"show\">\n        <div class=\"first wrap\">\n            <p>HEY</p>\n            <p>LET'S</p>\n            <p>DANCE</p>\n        </div>\n        <div class=\"second wrap\">\n            <p>GIVE</p>\n            <p>TAKE</p>\n            <p>RECEIVE</p>\n\n        </div>\n        <div class=\"third wrap\">\n            <p>WXPERE</p>\n            <p>IT</p>\n            <p>TODAY</p>\n        </div>        \n        <div class=\"forth wrap\">\n            <p>GIVE</p>\n            <p>ALL</p>\n            <p>YOUCAN</p>\n        </div>\n        <div class=\"fifth wrap\">\n            <p>UP</p>\n            <p>IN</p>\n            <p>FIGHT</p>\n        </div>\n    </div>\n    <script>\n        //取出所有的wrap元素，返回的是伪数组，转化为真数组后进行遍历给每个元素添加点击监听事件。\n        const Wrap = Array.from(document.querySelectorAll(\".wrap\"));\n        Wrap.forEach(item=>item.addEventListener(\"click\",handwith,true));\n        Wrap.forEach(item=>item.addEventListener(\"animationend\",openActive,true));\n        // 动画效果结束触发事件处理函数\n        function openActive(e){\n            console.log(\"fff\");\n            this.classList.toggle(\"open-active\");\n        }\n        function handwith(e){\n            // var _this=e.target;\n            if(this.classList.contains(\"scale\")){\n                this.classList.remove(\"scale\");\n                this.style.animation=\"shink 1s ease forwards\";\n            }else{\n                 this.classList.add(\"scale\");\n                 this.style.animation=\"extend 1s ease forwards\";\n                 // document.body.style.setProperty(\"--distance\",\"-50px\");\n            }\n\n        }\n    </script>\n</body>\n</html>\n\n"
  },
  {
    "path": "05 - Flex Panel Gallery/index-FINISHED.html",
    "content": "/*这里用了过渡的思路：transition，而我自己做的用了animation+flex-grow的变化以及@media*/\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Flex Panels</title>\n  <link href='https://fonts.googleapis.com/css?family=Amatic+SC' rel='stylesheet' type='text/css'>\n</head>\n<body>\n  ```css\n  <style>\n    html {\n      box-sizing: border-box;/*怪异盒模型：width是content+padding+border,实际开发中多采用这种，不用再计算所谓的padding和border来确定整个盒模型大小*/\n      background:#ffc600;\n      font-family:'helvetica neue';\n      font-size: 20px;\n      font-weight: 200;\n    }\n    body {\n      margin: 0;\n    }\n    *, *:before, *:after {\n      box-sizing: inherit;\n    }\n\n    .panels {\n      min-height:100vh;\n      overflow: hidden;\n      display: flex;/*对该元素下的子元素进行flex弹性布局*/\n    }\n\n    .panel {\n      background:#6B0F9C;\n      box-shadow:inset 0 0 0 5px rgba(255,255,255,0.1);\n      color:white;\n      text-align: center;\n      align-items:center;\n      /* Safari transitionend event.propertyName === flex */\n      /* Chrome + FF transitionend event.propertyName === flex-grow */\n      transition:\n        font-size 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11),\n        flex 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11),\n        background 0.2s;\n      font-size: 20px;\n      background-size:cover; \n      background-position:center;\n      flex: 1;      /*同级子元素平均布局*/\n      display: flex; /*对其子元素p, 设置布局方式*/\n      justify-content: center; /*水平居中*/\n      flex-direction: column;  /*成列排列*/\n    }\n    /*设置了5个栅格的背景照片*/\n    .panel1 { background-image:url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500); }\n    .panel2 { background-image:url(https://source.unsplash.com/1CD3fd8kHnE/1500x1500); }\n    .panel3 { background-image:url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d); }\n    .panel4 { background-image:url(https://source.unsplash.com/ITjiVXcwVng/1500x1500); }\n    .panel5 { background-image:url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500); }\n\n    /* Flex Items */\n    .panel > * {\n      margin:0;\n      width: 100%;\n      transition:transform 0.5s;\n      flex: 1 0 auto;\n      display:flex;\n      justify-content: center;\n      align-items: center;\n    }\n\n    .panel > *:first-child { transform: translateY(-100%); }\n    .panel.open-active > *:first-child { transform: translateY(0); }\n    .panel > *:last-child { transform: translateY(100%); }\n    .panel.open-active > *:last-child { transform: translateY(0); }\n\n    .panel p {\n      text-transform: uppercase; /*文本转化为大写*/\n      font-family: 'Amatic SC', cursive;\n      text-shadow:0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45);\n      font-size: 2em;\n    }\n    .panel p:nth-child(2) {\n      font-size: 4em;\n    }\n\n    .panel.open {\n      flex: 5;\n      font-size:40px;\n    }\n\n    .panel1panel1 {\n      color: red;\n    }\n\n  </style>\n```\n```html\n  <div class=\"panels\">\n    <div class=\"panel panel1\">\n      <p>Hey</p>\n      <p>Let's</p>\n      <p>Dance</p>\n    </div>\n    <div class=\"panel panel2\">\n      <p>Give</p>\n      <p>Take</p>\n      <p>Receive</p>\n    </div>\n    <div class=\"panel panel3\">\n      <p>Experience</p>\n      <p>It</p>\n      <p>Today</p>\n    </div>\n    <div class=\"panel panel4\">\n      <p>Give</p>\n      <p>All</p>\n      <p>You can</p>\n    </div>\n    <div class=\"panel panel5\">\n      <p>Life</p>\n      <p>In</p>\n      <p>Motion</p>\n    </div>\n  </div>\n```\n```js\n  <script>\n    const panels = Array.form(document.querySelectorAll('.panel'));\n\n    function toggleOpen() {\n      console.log('Hello');\n      this.classList.toggle('open');\n    }\n\n    function toggleActive(e) {\n      console.log(e.propertyName);\n      if (e.propertyName.includes('flex')) {\n        this.classList.toggle('open-active');\n      }\n    }\n\n    panels.forEach(panel => panel.addEventListener('click', toggleOpen));  \n    panels.forEach(panel => panel.addEventListener('transitionend', toggleActive)); //过渡效果完成之后触发的时间toggleActive。\n  </script>\n```\n</body>\n</html>\n"
  },
  {
    "path": "05 - Flex Panel Gallery/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Flex Panels 💪</title>\n  <link href='https://fonts.googleapis.com/css?family=Amatic+SC' rel='stylesheet' type='text/css'>\n</head>\n\n<body>\n  <style>\n    html {\n      /*\n       *content-box:padding和border不被包含在定义的width和height之内。\n       *对象的实际宽度等于设置的width值和border、padding之和，\n       *即 ( Element width = width + border + padding )此属性表现为标准模式下的盒模型。\n       *\n       *border-box：padding和border被包含在定义的width和height之内。\n       *对象的实际宽度就等于设置的width值，即使定义有border和padding也不会改变对象的实际宽度，\n       *即 ( Element width = width )此属性表现为怪异模式下的盒模型。\n       */\n      box-sizing: border-box;\n      background: #ffc600;\n      font-family: 'helvetica neue';\n      font-size: 20px;\n      font-weight: 200;\n    }\n\n    body {\n      margin: 0;\n    }\n\n    *,\n    *:before,\n    *:after {\n      box-sizing: inherit;\n    }\n\n    .panels {\n      min-height: : 100vh;\n    }\n\n    .panel {\n      background: #6B0F9C;\n      box-shadow: inset 0 0 0 5px rgba(255, 255, 255, 0.1);\n      color: white;\n      text-align: center;\n      align-items: center;\n      transition: font-size 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),\n      flex 0.7s cubic-bezier(0.61, -0.19, 0.7, -0.11),\n      background 0.2s;\n      font-size: 20px;\n      background-size: cover;\n      background-position: center;\n    }\n\n\n    .panel1 {\n      background-image: url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500);\n    }\n\n    .panel2 {\n      background-image: url(https://source.unsplash.com/1CD3fd8kHnE/1500x1500);\n    }\n\n    .panel3 {\n      background-image: url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d);\n    }\n\n    .panel4 {\n      background-image: url(https://source.unsplash.com/ITjiVXcwVng/1500x1500);\n    }\n\n    .panel5 {\n      background-image: url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500);\n    }\n\n    .panel>* {\n      margin: 0;\n      width: 100%;\n      transition: transform 0.5s;\n    }\n\n    .panel p {\n      text-transform: uppercase;\n      font-family: 'Amatic SC', cursive;\n      text-shadow: 0 0 4px rgba(0, 0, 0, 0.72), 0 0 14px rgba(0, 0, 0, 0.45);\n      font-size: 2em;\n    }\n\n    .panel p:nth-child(2) {\n      font-size: 4em;\n    }\n\n    .panel.open {\n      font-size: 40px;\n    }\n  </style>\n\n\n  <div class=\"panels\">\n    <div class=\"panel panel1\">\n      <p>Hey</p>\n      <p>Let's</p>\n      <p>Dance</p>\n    </div>\n    <div class=\"panel panel2\">\n      <p>Give</p>\n      <p>Take</p>\n      <p>Receive</p>\n    </div>\n    <div class=\"panel panel3\">\n      <p>Experience</p>\n      <p>It</p>\n      <p>Today</p>\n    </div>\n    <div class=\"panel panel4\">\n      <p>Give</p>\n      <p>All</p>\n      <p>You can</p>\n    </div>\n    <div class=\"panel panel5\">\n      <p>Life</p>\n      <p>In</p>\n      <p>Motion</p>\n    </div>\n  </div>\n\n  <script>\n  </script>\n\n</body>\n\n</html>\n"
  },
  {
    "path": "06 - Fetch、filter、正则表达式实现快速古诗匹配/.vscode/launch.json",
    "content": "{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        \n        {\n            \"type\": \"chrome\",\n            \"request\": \"launch\",\n            \"name\": \"Launch Chrome against localhost\",\n            \"url\": \"http://localhost:8080\",\n            \"webRoot\": \"${workspaceRoot}\"\n        },\n        {\n            \"type\": \"chrome\",\n            \"request\": \"attach\",\n            \"name\": \"Attach to Chrome\",\n            \"port\": 9222,\n            \"webRoot\": \"${workspaceRoot}\"\n        }\n    ]\n}"
  },
  {
    "path": "06 - Fetch、filter、正则表达式实现快速古诗匹配/README.md",
    "content": "# Day06 - Fetch、filter、正则表达式实现快速古诗匹配\n## 效果图\n\n在输入框中搜索`字或者某个词`快速匹配含有这个字或者是词的诗句。\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/06%20-%20Fetch%E3%80%81filter%E3%80%81%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%AE%9E%E7%8E%B0%E5%BF%AB%E9%80%9F%E5%8F%A4%E8%AF%97%E5%8C%B9%E9%85%8D/DemoGIF.gif)\n\n## 涉及特性\n\n- flex布局  \n\n- `nth-child`奇偶匹配  \n\n- Fetch获取数据以及异常处理  \n\n- Array\n\t- `filter()`  \n\t- `push()`  \n\t- `...`  \n- JavaScript RegExp 对象\n\n    - 字面量语法\n    - 创建 RegExp 对象的语法\n    - 修饰符`i`、`g`\n    - `test()`\n    - `replace()`\n- 防抖处理\n- 匹配文本的高亮显示\n- \"瓦楞纸效果\"的实现：transform:perspective(xxxpx) rotateX(xdeg)\n## 实现步骤\n- UI布局\n- 通过Fetch下载数据\n- 数据处理并保存\n- 事件监听\n- 数据匹配操作\n- 新数据替换展示\n\n## 布局篇\n\n- HTML代码\n\n```html\n <form class=\"search-form\">\n        <input type=\"text\" class=\"search\" placeholder=\"诗人名字，关键字\">\n        <ul class=\"suggestions\">\n            <li>输入诗人名字</li>\n            <li>输入关键字，找一首诗</li>\n        </ul>\n    </form>\n```\n\n- CSS代码\n\n```css\n    <style>\n        html{\n            box-sizing:border-box;\n            margin:0px;\n            background-color:burlywood;\n            font-family: \"Kaiti\",\"SimHei\",\"Hiragino Sans GB\",\"Helvetica neue\";\n            font-size:625%;\n            font-weight:200;\n        }\n        *,*:before,*:after{\n            box-sizing:inherit;\n        }\n        body{\n            display: flex;\n            justify-content: center;\n            /*text-align:center;*/\n        }\n        .search-form{\n            display: flex;\n            flex-direction:column; /*规定元素项目垂直显示，此时主轴为垂直方向*/\n            align-items: center;  /*在主轴垂直时，设置align-items可以使元素水平居中*/\n        }\n        .search{\n            width:8rem;\n            border:0.1rem solid #f7f7f7;\n            padding:0.2rem;\n            border-radius: 0.05rem;\n            font-size:0.2rem;\n            text-align:center;\n            box-shadow:0 0 5px 0 rgba(0,0,0,0.12),\n                       0 0 3px 0 rgba(0,0,0,.19) inset;\n            margin-top:0.4rem;\n            outline:none;  /*去掉input框的默认样式*/\n        }\n        .suggestions{\n            list-style: none; /*去掉序列修饰符号*/\n            width:6rem;\n            margin:0px;\n            padding: 0px;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            font-size:0.2rem;\n        }\n        .suggestions li{\n            text-align: center;\n            width:100%;\n            background: white;\n            border-bottom: 0.01rem solid #D8D8D8;\n            padding:0.15rem;\n            box-shadow:0 0 10px rgba(0,0,0,0.14);\n            /*transition:transform 0.5s ease;*/\n        }\n        .suggestions li:hover{\n            box-shadow: 0 0 10px 0 rgba(0,0,0,0.4);\n        }\n        .suggestions p{\n            text-align:right;\n            margin:0;\n        }\n        /*实现奇偶栏的折叠效果*/\n        .suggestions li:nth-child(odd){\n            transform: perspective(1rem) rotateX(-3deg) ;\n        }\n        .suggestions li:nth-child(even){\n            transform: perspective(1rem) rotateX(3deg) translateY(-2px) ;\n        }\n    </style>\n```\n\n- CSS布局相关参考文档\n\n    - [CSS参考手册](http://www.css88.com/book/css/properties/flex/flex.htm)\n    \n    - [CSS选择器笔记](http://www.ruanyifeng.com/blog/2009/03/css_selectors.html)\n    \n    - [flex布局完全入门教程](http://bbs.kongyixueyuan.com/topic/10/flex布局完全入门教程)\n    \n    - [使用HTML5里的classList操作CSS类](http://www.webhek.com/post/html5-classlist-api.html)\n    \n    - [position](http://zh.learnlayout.com/position.html)\n\n## 通过Fetch下载数据解析并且保存\n\n```js\n //通过fetch来获取后台数据，并进行json化以及请求异常处理。\n        fetch(url)\n            .then(response=>{\n                if(response.ok){   \n                    return response.json();  \n                }else{\n                    return Promise.reject({\n                        status:response.status,\n                        statusText:response.statusText\n                    });\n                }\n                \n            })\n            .then(data => { poetrys.push(...data);  //concat会创建一个新数组，push会修改原来的数组，所以可以直接拿过来用。\n                            // console.log(poetrys);\n                })\n            .catch(e=>{console.log(\"status:\",e.status);\n                       console.log(\"statusText:\",e.statusText);\n                });\n\n```\n\n[Fetch详细使用文档，详见本人博客](https://blog.csdn.net/qq_39207948/article/details/85050687)  \n\n## 事件监听\n\n```js\n \tconst search = document.querySelector(\".search\");\n        const suggestions = document.querySelector(\".suggestions\");\n        search.addEventListener(\"change\",debounce(findMatches,500)); //当输入框中文本改变时会触发事件处理函数\n        search.addEventListener(\"keyup\",debounce(findMatches,500));  //当按键up时会触发事件，最好有防抖操作。\n```\n\n获取`search`和`suggestions'`节点分别对`change`、`keyup`事件进行监听，当输入框中的内容发生变化或者键盘弹起时触发`debounce`函数进行防抖处理。\n\n## 数据匹配操作\n\n- RegExp使用基础\n\n[RegExp参考文档](http://www.w3school.com.cn/jsref/jsref_obj_regexp.asp)\n\n- 项目源码分析\n\n```js\n        //关键字匹配函数，在里面又调用了内容加载函数\n        function findMatches(){\n            //有搜索内容时，进行关键字匹配，没有的话显示两行提示\n            if(this.value){\n                let regexp = new RegExp(this.value,\"gi\");\n                let matched= poetrys.filter(item=>{   //根据标题、作者名、文本中的是否有关键字，将该数组项取出\n                    return regexp.test(item.title)||regexp.test(item.detail_author)||regexp.test(item.detail_text);\n                    });\n                if(matched.length > 0){    //如果匹配到数组项，将匹配到的内容加载出来\n                    createDom(matched);\n                }else{                     //如果没有匹配项，那么显示提示信息。\n                    suggestions.innerHTML='';\n                    suggestions.innerHTML=`<li>抱歉，没有查找到匹配项！</li>`\n                }\n            }else{\n                suggestions.innerHTML=`<li>输入诗人名字</li>\n                                        <li>输入关键字，找一首诗</li>`;\n            }\n        }\n        //将匹配到的内容加载出来\n        function createDom(matched){\n            let frag = document.createDocumentFragment();  //用文本片段的形式进行一次性添加，减少回流重绘。\n            matched.forEach(item=>{\n                    let li = document.createElement(\"li\");\n                    let p= document.createElement(\"p\");\n                    let regexp = new RegExp(search.value,\"gi\");\n                    //将匹配到的关键词用带样式的形式进行替换。\n                    let detailText   = item.detail_text.replace(regexp,`<span style=\"color:green\">${search.value}</span>`);\n                    let title        = item.title.replace(regexp,`<span style=\"color:green\">${search.value}</span>`);\n                    let detailAuthor = item.detail_author[0].replace(regexp,`<span style=\"color:green\">${search.value}</span>`);\n                    li.innerHTML = detailText;\n                    p.innerHTML = title + \"-\" +detailAuthor;\n                    // p.setAttribute(\"style\",\"text-align:right;margin:0px\");\n                    li.appendChild(p);\n                    frag.appendChild(li);\n                });\n            suggestions.innerHTML='';\n            suggestions.appendChild(frag);\n         }\n```\n"
  },
  {
    "path": "06 - Fetch、filter、正则表达式实现快速古诗匹配/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Fetch,filter,Regexp实现快速古诗匹配</title>\n    <style>\n        html{\n            box-sizing:border-box;\n            margin:0px;\n            background-color:burlywood;\n            font-family: \"Kaiti\",\"SimHei\",\"Hiragino Sans GB\",\"Helvetica neue\";\n            font-size:625%;\n            font-weight:200;\n        }\n        *,*:before,*:after{\n            box-sizing:inherit;\n        }\n        body{\n            display: flex;\n            justify-content: center;\n            /*text-align:center;*/\n        }\n        .search-form{\n            display: flex;\n            flex-direction:column; /*规定元素项目垂直显示，此时主轴为垂直方向*/\n            align-items: center;  /*在主轴垂直时，设置align-items可以使元素水平居中*/\n        }\n        .search{\n            width:8rem;\n            border:0.1rem solid #f7f7f7;\n            padding:0.2rem;\n            border-radius: 0.05rem;\n            font-size:0.2rem;\n            text-align:center;\n            box-shadow:0 0 5px 0 rgba(0,0,0,0.12),\n                       0 0 3px 0 rgba(0,0,0,.19) inset;\n            margin-top:0.4rem;\n            outline:none;  /*去掉input框的默认样式*/\n        }\n        .suggestions{\n            list-style: none; /*去掉序列修饰符号*/\n            width:6rem;\n            margin:0px;\n            padding: 0px;\n            display: flex;\n            flex-direction: column;\n            align-items: center;\n            font-size:0.2rem;\n        }\n        .suggestions li{\n            text-align: center;\n            width:100%;\n            background: white;\n            border-bottom: 0.01rem solid #D8D8D8;\n            padding:0.15rem;\n            box-shadow:0 0 10px rgba(0,0,0,0.14);\n            /*transition:transform 0.5s ease;*/\n        }\n        .suggestions li:hover{\n            box-shadow: 0 0 10px 0 rgba(0,0,0,0.4);\n        }\n        .suggestions p{\n            text-align:right;\n            margin:0;\n        }\n        /*实现奇偶栏的折叠效果*/\n        .suggestions li:nth-child(odd){\n            transform: perspective(1rem) rotateX(-3deg) ;\n        }\n        .suggestions li:nth-child(even){\n            transform: perspective(1rem) rotateX(3deg) translateY(-2px) ;\n        }\n    </style>\n</head>\n<body>\n    <form class=\"search-form\">\n        <input type=\"text\" class=\"search\" placeholder=\"诗人名字，关键字\">\n        <ul class=\"suggestions\">\n            <li>输入诗人名字</li>\n            <li>输入关键字，找一首诗</li>\n        </ul>\n    </form>\n    <script>\n        const url =  'https://gist.githubusercontent.com/liyuechun/f00bb31fb8f46ee0a283a4d182f691b4/raw/3ea4b427917048cdc596b38b67b5ed664605b76d/TangPoetry.json';\n        const poetrys=[];\n        //通过fetch来获取后台数据，并进行json化以及请求异常处理。\n        fetch(url)\n            .then(response=>{\n                if(response.ok){\n                    return response.json();  \n                }else{\n                    return Promise.reject({\n                        status:response.status,\n                        statusText:response.statusText\n                    });\n                }\n                \n            })\n            .then(data => { poetrys.push(...data);  //concat会创建一个新数组，push会修改原来的数组，所以可以直接拿过来用。\n                            // console.log(poetrys);\n                })\n            .catch(e=>{console.log(\"status:\",e.status);\n                       console.log(\"statusText:\",e.statusText);\n                });\n\n        const search = document.querySelector(\".search\");\n        const suggestions = document.querySelector(\".suggestions\");\n        search.addEventListener(\"change\",debounce(findMatches,500)); //当输入框中文本改变时会触发事件处理函数\n        search.addEventListener(\"keyup\",debounce(findMatches,500));  //当按键up时会触发事件，最好有防抖操作。\n\n        //防抖函数:避免搜索内容更改过快\n        function debounce(func,wait){\n            let timeout;\n            return function(){\n                let _this=this;\n                let args=arguments;\n                clearTimeout(timeout);\n                timeout=setTimeout(func.bind(_this,args),wait);\n            }\n        }\n        //关键字匹配函数，在里面又调用了内容加载函数\n        function findMatches(){\n            //有搜索内容时，进行关键字匹配，没有的话显示两行提示\n            if(this.value){\n                let regexp = new RegExp(this.value,\"gi\");\n                let matched= poetrys.filter(item=>{   //根据标题、作者名、文本中的是否有关键字，将该数组项取出\n                    return regexp.test(item.title)||regexp.test(item.detail_author)||regexp.test(item.detail_text);\n                    });\n                if(matched.length > 0){    //如果匹配到数组项，将匹配到的内容加载出来\n                    createDom(matched);\n                }else{                     //如果没有匹配项，那么显示提示信息。\n                    suggestions.innerHTML='';\n                    suggestions.innerHTML=`<li>抱歉，没有查找到匹配项！</li>`\n                }\n            }else{\n                suggestions.innerHTML=`<li>输入诗人名字</li>\n                                        <li>输入关键字，找一首诗</li>`;\n            }\n        }\n        //将匹配到的内容加载出来\n        function createDom(matched){\n            let frag = document.createDocumentFragment();  //用文本片段的形式进行一次性添加，减少回流重绘。\n            matched.forEach(item=>{\n                    let li = document.createElement(\"li\");\n                    let p= document.createElement(\"p\");\n                    let regexp = new RegExp(search.value,\"gi\");\n                    //将匹配到的关键词用带样式的形式进行替换。\n                    let detailText   = item.detail_text.replace(regexp,`<span style=\"color:green\">${search.value}</span>`);\n                    let title        = item.title.replace(regexp,`<span style=\"color:green\">${search.value}</span>`);\n                    let detailAuthor = item.detail_author[0].replace(regexp,`<span style=\"color:green\">${search.value}</span>`);\n                    li.innerHTML = detailText;\n                    p.innerHTML = title + \"-\" +detailAuthor;\n                    // p.setAttribute(\"style\",\"text-align:right;margin:0px\");\n                    li.appendChild(p);\n                    frag.appendChild(li);\n                });\n            suggestions.innerHTML='';\n            suggestions.appendChild(frag);\n         }\n    </script>\n</body>\n</html>\n"
  },
  {
    "path": "07 - Array Cardio Day 2/README.md",
    "content": "# Day07 - Array Cardio 中文指南二\n\n第七天的练习是接着之前[Day04 - Array Cardio 中文指南一](http://bbs.kongyixueyuan.com/topic/40/day04-array-cardio-%E6%8C%87%E5%8D%97%E4%B8%80)的练习，继续熟练数组的方法，依旧没有页面显示效果，所以请打开浏览器的Console面板进行调试运行。\n\n![](http://om1c35wrq.bkt.clouddn.com/dya7%20-%20000.png)\n![](http://om1c35wrq.bkt.clouddn.com/day7%20-%20001.png)\n\n\n\n\n## 任务表\n网站给了两个数组，分别为`people`数组和`comments`数组，如下：\n\n```JavaScript\nconst people = [\n    { name: 'Wes', year: 1988 },\n    { name: 'Kait', year: 1986 },\n    { name: 'Irv', year: 1970 },\n    { name: 'Lux', year: 2015 }\n];\n\nconst comments = [\n    { text: 'Love this!', id: 523423 },\n    { text: 'Super good', id: 823423 },\n    { text: 'You are the best', id: 2039842 },\n    { text: 'Ramen is my fav food ever', id: 123523 },\n    { text: 'Nice Nice Nice!', id: 542328 }\n];\n```\n\n**在此两数组的基础上实现一下几个操作：**\n1. 是否至少有一人年满`19`周岁？\n2. 是否每一个人都年满`19`周岁？\n3. 是否存在`id=823423`的评论？\n4. 找到`id=823423`的评论的序列号(下标)。\n5. 删除`id=823423`的评论。\n\n\n## 是否至少有一人年满19周岁？\n\n### `Array.prototype.some()`\n\n> [some参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some)\n\n- CASE\n\n```js\nlet isBiggerThan10 = (element, index, array) => {\n  return element > 10;\n}\n\n[2, 5, 8, 1, 4].some(isBiggerThan10);  // false\n[12, 5, 8, 1, 4].some(isBiggerThan10); // true\n```\n\n- Syntax\n\n```js\narr.some(callback[, thisArg])\n```\n\n\n- Parameters\n\n    - element：当前在操作的对象。\n    \n    - index：当前操作对象的索引。\n    \n    - array：在操作的数组指针。\n\n- Return value\n返回`true`或者`false`，返回true，说明数组中有满足条件的数据存在，返回false，说明数组里面没有满足条件的数组存在。\n\n### 项目源码\n\n- 版本一：\n\n```js\nconst isAdult = people.some(function(person){\n  const currentYear = new Date().getFullYear();\n  if(currentYear - person.year >= 19){\n    return true;\n  }\n});\nconsole.log(isAdult);\n```\n\n- 版本二：\n\n```JavaScript\nconst isAdult = people.some((person) => {\n  const currentYear = new Date().getFullYear();\n  if(currentYear - person.year >= 19){\n    return true;\n  }\n});\nconsole.log(isAdult);\n```\n\n- 版本三：\n\n```JavaScript\nconst isAdult = people.some(person => (new Date().getFullYear() - person.year) >= 19 );\nconsole.log(isAdult);\n```\n\n## 是否每一个人都年满`19`周岁？\n\n### `Array.prototype.every()`\n\n> [every参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every)\n\n\n- CASE\n\n```js\nlet isBigEnough = (element, index, array) => { \n  return element >= 10; \n} \n\n[12, 5, 8, 130, 44].every(isBigEnough);   // false \n[12, 54, 18, 130, 44].every(isBigEnough); // true\n```\n\n- Syntax\n\n```js\narr.every(callback)\n```\n\n- Parameters\n\n- Parameters\n\n    - element：当前在操作的对象。\n    \n    - index：当前操作对象的索引。\n    \n    - array：在操作的数组指针。\n\n- Return value\n返回`true`或者`false`，返回true，代表数组中所有数据都满足条件，否则，至少有一条数据不满足条件。\n\n\n### 项目源码\n\n```JavaScript\nconst everyAdult = people.every(person => (new Date().getFullYear() - person.year) >= 19);\nconsole.log({everyAdult});\n```\n\n## 是否存在`id=823423`的评论？\n\n### `Array.prototype.find(callback)`\n\n>  [find参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find)\n\n- CASE\n\n```js\nlet  isBigEnough = (element) => {\n  return element >= 15;\n}\n\n[12, 5, 8, 130, 44].find(isBigEnough); // 130\n```\n\n- Syntax\n\n```js\narr.find(callback)\n```\n\n\n- Parameters\n\n    - element：当前在操作的对象。\n    \n    - index：当前操作对象的索引。\n    \n    - array：在操作的数组指针。\n\n- Return value\n如果有满足条件对象，返回该对象，否则返回`undefined `。\n\n### 项目源码\n\n```JavaScript\nconst findComment = comments.find(comment => comment.id === 823423);\nconsole.log(findComment);\n}\n```\n\n\n## 找到`id=823423`的评论的序列号(下标)\n\n### `Array.prototype.findIndex()`\n\n>  [findIndex参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex)\n\n\n- CASE\n\n```js\nlet isBigEnough = (element) => {\n  return element >= 15;\n}\n\n[12, 5, 8, 130, 44].findIndex(isBigEnough); \n// index of 4th element in the Array is returned,\n// so this will result in '3'\n```\n\n- Syntax\n\narr.findIndex(callback)\n\n- Parameters\n\n    - element：当前在操作的对象。\n    \n    - index：当前操作对象的索引。\n    \n    - array：在操作的数组指针。\n\n- Return value\n返回满足条件的当前对象在数组中的索引，如果找不到满足条件的对象，返回`-1`。\n\n\n\n### 项目源码\n\n```JavaScript\nconst findCommentIndex = comments.findIndex(comment => comment.id === 823423);\nconsole.log(findCommentIndex);\n```\n\n\n## 删除`id=823423`的评论\n\n> [splice参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice)\n> [slice参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice)\n\n\n###  `Array.prototype.splice()`\n\n- CASE\n\n在索引2的位置移除0个元素，并且插入\"drum\"\n\n```js\nvar myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];\nvar removed = myFish.splice(2, 0, 'drum');\n\n// myFish 是 [\"angel\", \"clown\", \"drum\", \"mandarin\", \"sturgeon\"] \n// removed is [], 没有元素被移除。\n```\n\n从索引3开始移除1个元素。\n\n```js\nvar myFish = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon'];\nvar removed = myFish.splice(3, 1);\n\n// 移除的原色是 [\"mandarin\"]\n// myFish 为 [\"angel\", \"clown\", \"drum\", \"sturgeon\"]\n```\n\n\n从索引2移除一个元素，并且插入\"trumpet\"\n\n```js\nvar myFish = ['angel', 'clown', 'drum', 'sturgeon'];\nvar removed = myFish.splice(2, 1, 'trumpet');\n\n// myFish 为 [\"angel\", \"clown\", \"trumpet\", \"sturgeon\"]\n// 移除的元素为 [\"drum\"]\n```\n\n从索引0开始移除2个元素，并且插入\"parrot\", \"anemone\" 和 \"blue\"。\n\n```js\nvar myFish = ['angel', 'clown', 'trumpet', 'sturgeon'];\nvar removed = myFish.splice(0, 2, 'parrot', 'anemone', 'blue');\n\n// myFish为 [\"parrot\", \"anemone\", \"blue\", \"trumpet\", \"sturgeon\"] \n// 移除的元素是 [\"angel\", \"clown\"]\n```\n\n\n从索引2开始移除所有元素\n\n```js\nvar myFish = ['angel', 'clown', 'mandarin', 'sturgeon'];\nvar removed = myFish.splice(2);\n\n// myFish 为 [\"angel\", \"clown\"] \n// 移除的原色为 [\"mandarin\", \"sturgeon\"]\n```\n\n\n- Syntax\n\n```js\narray.splice(start)\narray.splice(start, deleteCount)\narray.splice(start, deleteCount, item1, item2, ...)\n```\n**array.splice(start)：** 从索引`start`开始移除后面所有的元素。\n\n**array.splice(start, deleteCount)：** 从索引`start`元素删除`deleteCount`个元素。\n\n\n**array.splice(start, deleteCount, item1, item2, ...)：**从`start`索引开始，删除`deleteCount`个元素，然后插入`item1`,`item2`,...\n\n### `Array.prototype.slice()`\n\n- CASE\n\n```js\nvar a = ['zero', 'one', 'two', 'three'];\nvar sliced = a.slice(1, 3);\n\nconsole.log(a);      // ['zero', 'one', 'two', 'three']\nconsole.log(sliced); // ['one', 'two']\n```\n\n- Syntax\n\n```js\narr.slice()\narr.slice(begin)\narr.slice(begin, end)\n```\n**arr.slice()**等价于**arr.slice(0,arr.length)**\n\n**arr.slice(begin)**等价于**arr.slice(begin,arr.length)**\n\n`arr.slice(begin, end)`：创建一个新数组，将索引`begin`-`end`(不包含end)的元素放到新数组中并返回新数组，原数组不被修改。\n\n\n### 项目源码 - 删除`id=823423`的评论\n\n```\nconst comments = [\n { text: 'Love this!', id: 523423 },\n { text: 'Super good', id: 823423 },\n { text: 'You are the best', id: 2039842 },\n { text: 'Ramen is my fav food ever', id: 123523 },\n { text: 'Nice Nice Nice!', id: 542328 }\n];\n    \nconst findCommentIndex = comments.findIndex(comment => comment.id === 823423);\n\n// delete the comment with the ID of 823423\n//comments.splice(findCommentIndex,1);\n\nconst newComments = [\n ...comments.slice(0,findCommentIndex),\n ...comments.slice(findCommentIndex+1)\n]; \n```\n\n`splice`会修改原数组，`slice`不会改变原数组的值。\n\n\n"
  },
  {
    "path": "07 - Array Cardio Day 2/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Array Cardio 💪💪</title>\n</head>\n<body>\n  <p><em>Psst: have a look at the JavaScript Console</em> 💁</p>\n  <script>\n    // ## Array Cardio Day 2\n    const people = [\n      { name: 'Wes', year: 1988 },\n      { name: 'Kait', year: 1986 },\n      { name: 'Irv', year: 1970 },\n      { name: 'Lux', year: 2015 }\n    ];\n\n    const comments = [\n      { text: 'Love this!', id: 523423 },\n      { text: 'Super good', id: 823423 },\n      { text: 'You are the best', id: 2039842 },\n      { text: 'Ramen is my fav food ever', id: 123523 },\n      { text: 'Nice Nice Nice!', id: 542328 }\n    ];\n\n    // Some and Every Checks\n    // Array.prototype.some() // is at least one person 19 or older?\n\n    // const isAdult = people.some(function(person){\n    //   const currentYear = new Date().getFullYear();\n    //   if(currentYear - person.year >= 19){\n    //     return true;\n    //   }\n    // });\n\n    const isAdult = people.some(person => (new Date().getFullYear() - person.year) >= 19 );\n\n\n    // Array.prototype.every() // is everyone 19 or older?\n    const everyAdult = people.every(person => (new Date().getFullYear() - person.year) >= 19);\n\n\n    // Array.prototype.find()\n    // Find is like filter, but instead returns just the one you are looking for\n    // find the comment with the ID of 823423\n    const findComment = comments.find(comment => comment.id === 823423);\n\n\n    // Array.prototype.findIndex()\n    // Find the comment with this ID\n    const findCommentIndex = comments.findIndex(comment => comment.id === 823423);\n\n\n    // delete the comment with the ID of 823423\n    //comments.splice(findCommentIndex,1);\n    const newComments = [\n      ...comments.slice(0,findCommentIndex),\n      ...comments.slice(findCommentIndex+1)\n    ];\n\n  </script>\n</body>\n</html>\n"
  },
  {
    "path": "08 - HTML5 Canvas 实现彩虹画笔绘画板/README.md",
    "content": "# Day08 - HTML5 Canvas 实现彩虹画笔绘画板指南\n\n## 项目效果\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/08%20-%20HTML5%20Canvas%20%E5%AE%9E%E7%8E%B0%E5%BD%A9%E8%99%B9%E7%94%BB%E7%AC%94%E7%BB%98%E7%94%BB%E6%9D%BF/GIF.gif)\n\n> 用 HTML5 中的 Canvas 的路径绘制实现一个绘画板，可供鼠标画画，颜色呈彩虹色渐变，画笔大小同样呈渐变效果。这部分不涉及 CSS 内容，全部由 JS 来实现。\n\n## 涉及特性\n\nCanvas：\n\n- 模板骨架\n- 基本属性\n\t- `getContext()`\n\t- `strokeStyle`\n\t- `lineCap`\n\t- `lineJoin`\n- 路径绘制\n\t- `beginPath()`\n\t- `lineTo()`\n\t- `moveTo()`\n\t\n鼠标事件处理：\n- `mousemove`\n- `mousedown`\n- `mouseup`\n- `mouseout`\n\n## 过程指南\n\n1. 获取 HTML 中的 `<canvas>` 元素，并设定宽度和高度\n2. `.getContext('2d')` 获取上下文，下面以 ctx 表示\n3. 设定 ctx 基本属性\n\t- 描边和线条颜色  \n\t- 线条宽度  \n\t- 线条末端形状  \n4. 绘画效果\n\t1. 设定一个用于标记绘画状态的变量  \n\t2. 鼠标事件监听，不同类型的事件将标记变量设为不同值  \n\t3. 编写发生绘制时触发的函数，设定绘制路径起点、终点  \n5. 线条彩虹渐变效果（运用 hsl 的 `h` 值的变化，累加）  \n6. 线条粗细渐变效果（设定一个范围，当超出这个范围时，线条粗细进行逆向改变，利用[撞墙反弹程序](https://blog.csdn.net/qq_39207948/article/details/85252068)  \n\n## Canvas相关知识\n\n[Canvas_API](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API)\n\n### 代码部分\n```JS\n        //全局变量和初始值设置部分\n        let drawflag=false; //用于区分鼠标点击事件和鼠标移动事件。\n        let beginX=0;   //设置为全局变量，初始点要传到移动处理事件中。\n        let beginY=0;\n        let hue=0;      //hsl的色调初始值\n        let context='';\n        let lineWidth=60;\n        let direction=true; //定义变量增加方向\n\t\n        // 页面加载函数，在DOM结构解析完成后运行\n        window.onload=function(){\n\n            let canvas = document.querySelector(\"#tutorial\");\n            canvas.width = window.innerWidth;\n            canvas.height = window.innerHeight;\n            context=canvas.getContext(\"2d\");\n\n            canvas.addEventListener(\"mousedown\",beginlocation);\n            canvas.addEventListener(\"mousemove\",drawing);\n            canvas.addEventListener(\"mouseup\",()=>drawflag=false);\n            canvas.addEventListener(\"mouseout\",()=>drawflag=false);            \n        }\n\n```\n- 页面加载函数中用的两个事件处理函数\n```JS\n //设定初始点坐标，并开启绘图flag\n        function beginlocation(e){\n            beginX=e.offsetX;\n            beginY=e.offsetY;\n            drawflag=true;\n        }\n        //绘图函数：实际上一段一段的直线连接而成，鼠标每移动一点就将该时刻的坐标转换成下一次的起始坐标，而鼠标移动后的位置作为该段直线结束的坐标。\n        function drawing(e){\n            if(drawflag){\n                let moveX=e.offsetX;\n                let moveY=e.offsetY;\n\n                //色相值改变\n                if(hue<=360){  //hue要设置初始值\n                    hue++;\n                }else{\n                    hue=0;\n                }\n                context.strokeStyle=`hsl(${hue},100%,50%)`;\n\n                //“撞墙反弹程序”\n                if(lineWidth>100||lineWidth<10){\n                    direction = !direction;\n                }\n                if(direction){\n                    lineWidth++;\n                }else{\n                    lineWidth--;\n                }\n                context.lineWidth=lineWidth;\n\n                context.lineCap=\"round\";\n                context.lineJoin=\"round\";\n\n                context.beginPath();\n                context.moveTo(beginX,beginY);\n                context.lineTo(moveX,moveY);\n                context.closePath();\n                [beginX,beginY]=[moveX,moveY]; //es6解构赋值\n\n                context.stroke();\n            }else{\n                return;\n            }\n        }\n```\n\n- canvas 元素\n\n```js\n<canvas id=\"tutorial\"></canvas>\n```\n\n`canvas` 看起来和 `img` 元素很相像，唯一的不同就是它并没有 `src` 和`alt` 属性。实际上，`canvas` 标签只有两个属性——`width`和`height`。这些都是可选的，并且同样利用 `DOM properties` 来设置。当没有设置宽度和高度的时候，`canvas`会初始化宽度为`300`像素和高度为`150`像素。该元素可以使用CSS来定义大小，但在绘制时图像会伸缩以适应它的框架尺寸：如果CSS的尺寸与初始画布的比例不一致，它会出现扭曲。\n\n\n- 渲染上下文（The rendering context）\n\n```js\nvar canvas = document.getElementById('tutorial');\nvar ctx = canvas.getContext('2d');\n```\n\n`canvas`元素创造了一个固定大小的画布，它公开了一个或多个渲染上下文，其可以用来绘制和处理要展示的内容。\n\n`canvas`起初是空白的。为了展示，首先脚本需要找到渲染上下文，然后在它的上面绘制。`canvas`元素有一个叫做 `getContext()` 的方法，这个方法是用来获得渲染上下文和它的绘画功能。`getContext()`只有一个参数，上下文的格式。对于2D图像而言，基本教程，你可以使用[CanvasRenderingContext2D](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D)\n\n- 检查支持性\n\n替换内容是用于在不支持 `canvas` 标签的浏览器中展示的。通过简单的测试`getContext()`方法的存在，脚本可以检查编程支持性。\n\n```js\nvar canvas = document.getElementById('tutorial');\n\nif (canvas.getContext){\n    //支持\n  var ctx = canvas.getContext('2d');\n  // drawing code here\n} else {\n   //不支持\n  // canvas-unsupported code here\n}\n```\n\n### Canvas的简单实例\n- [canvas 倒计时特效](https://blog.csdn.net/qq_39207948/article/details/85252925)\n- [canvas 躁动的小球](https://blog.csdn.net/qq_39207948/article/details/85252947)\n- [canvas 单个小球运动实验](https://blog.csdn.net/qq_39207948/article/details/85252849)\n\n### 涉及知识点\n\n[Canvas](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API)\n\n#### canvas宽高设置\n```js\ncanvas.width = window.innerWidth;\ncanvas.height = window.innerHeight;\n```\n#### 属性\n- `lineCap`：笔触的形状，有 round | butt | square 圆、平、方三种。\n- `lineJoin`：线条相交的样式，有 round | bevel | miter 圆交、斜交、斜接三种。\n- `lineWidth`：线条的宽度\n- `strokeStyle`：线条描边的颜色\n#### 方法\n- `beginPath()`：新建一条路径\n- `stroke()`：绘制轮廓\n- `moveTo()`：（此次）绘制操作的起点\n- `lineTo()`：路径的终点\n\n### 彩虹渐变颜色——HSL  \n\n在这个挑战中，涉及到改变线条的颜色，如何实现彩虹的渐变效果？我们需要利用 HSL 色彩模式，首先可以去这个网站 [http://mothereffinghsl.com](http://mothereffinghsl.com/) 感受一下 HSL 不同色彩值对应的效果。\n- H(hue) 代表色调，取值为 0~360，专业术语叫色相\n- S 是饱和度，可以理解为掺杂进去的灰度值，取值为 0~1\n- L 则是亮度，取值也是 0~1，或者百分比。\n\t\n这之中 H 值从 0 到 360 的变化代表了色相的角度的值域变化，利用这一点就可以实现绘制时线条颜色的渐变了，只需要在它的值超过 360 时恢复到 0 重新累加即可。\n\n```js\n//色相值改变\nif(hue<=360){  //hue要设置初始值\n    hue++;\n}else{\n    hue=0;\n}\ncontext.strokeStyle=`hsl(${hue},100%,50%)`;\n```\n\n除此之外，如果想实现黑白水墨的颜色，可以将颜色设置为黑色，通过透明度的改变来实现深浅不一的颜色。\n\n### 控制笔触大小\n\n```js\n\t//“撞墙反弹程序”\n\tif(lineWidth>100||lineWidth<10){\n\t    direction = !direction;\n\t}\n\tif(direction){\n\t    lineWidth++;\n\t}else{\n\t    lineWidth--;\n\t}\n\tcontext.lineWidth=lineWidth;\n```\n\n上面的代码中，根据线条的宽度的变化来控制`direction`的值，根据`direction`的值来控制线宽是增加还是减少。\n\n\n### 控制线条路径\n\n```js\n\tcontext.beginPath();\n\tcontext.moveTo(beginX,beginY);\n\tcontext.lineTo(moveX,moveY);\n\tcontext.closePath();\n\t[beginX,beginY]=[moveX,moveY]; //es6解构赋值\n```\n\n### 事件监听代码逻辑分析\n\n```js\n    canvas.addEventListener(\"mousedown\",beginlocation);\n    canvas.addEventListener(\"mousemove\",drawing);\n    canvas.addEventListener(\"mouseup\",()=>drawflag=false);\n    canvas.addEventListener(\"mouseout\",()=>drawflag=false);      \n```\n\n#### 需要整理知识点\n- 1、鼠标事件有哪些，具体使用方法。  \n- 2、获取窗口的高度与宽度(不包含工具条与滚动条):  \n    var w=window.innerWidth;  \n    var h=window.innerHeight;浏览器中地址导航栏下面中的部分  \n    和clientWidth以及clientHeight的区别。  \n    clientX和offsetX的区别  \n      clientX检索与窗口客户区域有关的鼠标光标的X坐标，  \n      offsetX 检索与触发事件的对象相关的鼠标位置的水平坐标   \n    因为canvas的宽高均设置为了window.innerHtml和window.innerWidth,那么当点击鼠标时，实际上得到的是相对于canvas元素的位置，也即是e.offsetX/Y，这里offset中的set是小写。  \n\n- 3、lineCap 属性设置或返回线条末端线帽的样式。  \n    butt  默认。向线条的每个末端添加平直的边缘。  \n    round   向线条的每个末端添加圆形线帽。  \n    square  向线条的每个末端添加正方形线帽。  \n- 4、lineJoin 属性设置或返回所创建边角的类型，当两条线交汇时。  \n    bevel   创建斜角。  \n    round   创建圆角。  \n    miter   默认。创建尖角。  \n\n"
  },
  {
    "path": "08 - HTML5 Canvas 实现彩虹画笔绘画板/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Canvas Rainbowbrush</title>\n    <style>\n        html,body{\n            margin:0;\n            padding:0;\n            overflow:hidden;\n        }\n        #tutorial{\n            border:1px solid black;\n        }\n    </style>\n</head>\n<body>\n    <canvas id=\"tutorial\">\n        <p>抱歉！您的浏览器暂不支持Canvas标签属性!</p>\n    </canvas>\n    <script>\n        //全局变量和初始值设置部分\n        let drawflag=false; //用于区分鼠标点击事件和鼠标移动事件。\n        let beginX=0;   //设置为全局变量，初始点要传到移动处理事件中。\n        let beginY=0;\n        let hue=0;      //hsl的色调初始值\n        let context='';\n        let lineWidth=60;\n        let direction=true; //定义变量增加方向\n        \n        // 页面加载函数，在DOM结构解析完成后运行\n        window.onload=function(){\n\n            let canvas = document.querySelector(\"#tutorial\");\n            canvas.width = window.innerWidth;\n            canvas.height = window.innerHeight;\n            context=canvas.getContext(\"2d\");\n\n            canvas.addEventListener(\"mousedown\",beginlocation);\n            canvas.addEventListener(\"mousemove\",drawing);\n            canvas.addEventListener(\"mouseup\",()=>drawflag=false);\n            canvas.addEventListener(\"mouseout\",()=>drawflag=false);            \n        }\n\n        //设定初始点坐标，并开启绘图flag\n        function beginlocation(e){\n            beginX=e.offsetX;\n            beginY=e.offsetY;\n            drawflag=true;\n        }\n        //绘图函数：实际上一段一段的直线连接而成，鼠标每移动一点就将该时刻的坐标转换成下一次的起始坐标，而鼠标移动后的位置作为该段直线结束的坐标。\n        function drawing(e){\n            if(drawflag){\n                let moveX=e.offsetX;\n                let moveY=e.offsetY;\n\n                //色相值改变\n                if(hue<=360){  //hue要设置初始值\n                    hue++;\n                }else{\n                    hue=0;\n                }\n                context.strokeStyle=`hsl(${hue},100%,50%)`;\n\n                //“撞墙反弹程序”\n                if(lineWidth>100||lineWidth<10){\n                    direction = !direction;\n                }\n                if(direction){\n                    lineWidth++;\n                }else{\n                    lineWidth--;\n                }\n                context.lineWidth=lineWidth;\n\n                context.lineCap=\"round\";\n                context.lineJoin=\"round\";\n\n                context.beginPath();\n                context.moveTo(beginX,beginY);\n                context.lineTo(moveX,moveY);\n                context.closePath();\n                [beginX,beginY]=[moveX,moveY]; //es6解构赋值\n\n                context.stroke();\n            }else{\n                return;\n            }\n        }\n    </script>\n</body>\n</html>\n<!--\n 用canvas实现彩虹画笔的思路\n1、判断鼠标按下和抬起的状态\n2、鼠标按下时开始绘制，鼠标移动时需要得到鼠标的位置信息，根据位置信息添加新的绘图路径，进行render,抬起鼠标绘图结束。\n3、点击之后开始绘图，需要一个开关的flag，只有flag打开时才能在鼠标移动时绘图。\n4、可以结合input的color的拾色器来改变线条颜色。\n -->\n\n <!-- 需要整理知识点\n1、鼠标事件有哪些，具体使用方法。\n2、获取窗口的高度与宽度(不包含工具条与滚动条):\n    var w=window.innerWidth;\n    var h=window.innerHeight;浏览器中地址导航栏下面中的部分\n    和clientWidth以及clientHeight的区别。\n    clientX和offsetX的区别\n      clientX检索与窗口客户区域有关的鼠标光标的X坐标，\n      offsetX 检索与触发事件的对象相关的鼠标位置的水平坐标 \n    因为canvas的宽高均设置为了window.innerHtml和window.innerWidth,那么当点击鼠标时，实际上得到的是相对于canvas元素的位置，也即是e.offsetX/Y，这里offset中的set是小写。\n\n3、lineCap 属性设置或返回线条末端线帽的样式。\n    butt  默认。向线条的每个末端添加平直的边缘。\n    round   向线条的每个末端添加圆形线帽。\n    square  向线条的每个末端添加正方形线帽。\n4、lineJoin 属性设置或返回所创建边角的类型，当两条线交汇时。\n    bevel   创建斜角。\n    round   创建圆角。\n    miter   默认。创建尖角。\n-->\n\n"
  },
  {
    "path": "09 - Console 调试各种姿势指南/README.md",
    "content": "# Day09 - Console 调试各种姿势指南\n\n## 项目效果\n[控制台打印结果，请猛戳我！！！](https://blog.csdn.net/qq_39207948/article/details/85261675)\n## 各种调试正确姿势\n### `.log` 的更多用法\n这个是最常用的，但它还有一些更多功能：比如参数支持类似 C 语言的字符串替换模式。\n- `%s` 字符串\n- `%d` 整数\n- `%f` 浮点值\n- `%o` Object\n- `%c` 设定输出的样式，在之后的文字将按照第二个参数里的值进行显示\n\n```js\nconsole.log(\"I am a String: %s \", \"log\"); //log\nconsole.log(\"I am a int number: %d \", 1); //1\nconsole.log(\"I am a float number: %d \", 1.23); //1.23\nlet dog = {name: \"Lucky\",age: \"5\"};\nconsole.log(\"%o\",dog);\nconsole.log(\"%c3D Text\",\" text-shadow: 0 1px 0 #ccc,0 2px 0 #c9c9c9,0 3px 0 #bbb,0 4px 0 #b9b9b9,0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);font-size:5em\");\n```\n\n![](http://om1c35wrq.bkt.clouddn.com/day9-log-method.png)\n\n\n### 清空 console 面板输出内容\n\n要清空已经打印输出的内容，有两种方式，一种是 JavaScript 语句： `console.clear()`。另一个是快捷键 `Ctrl ＋ L`。\n\n![](http://om1c35wrq.bkt.clouddn.com/day9-2-2-2.gif)\n\n### 不同样式的输出\n\n除了常规的 `log` 之外，还有一些其他已设定好的样式，区别在于图标或者颜色不一样：\n\n```js\n// warning!\nconsole.warn(\"用于输出警示信息\");\n// Error :|\nconsole.error(\"用于输出错误信息\");\n// Info\nconsole.info(\"用于输出提示性信息\");\n//debug\nconsole.debug(\"用于输出调试信息\");\n```\n\n![](http://om1c35wrq.bkt.clouddn.com/day9-warn-info.png)\n\n### 打印DOM节点\n获取 DOM 元素之后，可以直接打印输出。\n\n```js\nconst p = document.querySelector('p');\nconsole.log(p);\nconsole.dir(p);\n```\n\n*  .log 输出这个 DOM 的 HTML 标签。\n\n* .dir 则会输出这个 DOM 元素的属性列表。\n\n![](http://om1c35wrq.bkt.clouddn.com/day9-dir-p.png)\n\n\n### 断点调试\n\n`console.asset(arg1,arg2)`方法接受一个表达式作为参数，如果参数返回值是`false`，则会输出第二个参数中的内容。\n\n\n```js\nconst p = document.querySelector('p');\nconsole.assert(p.classList.contains('ouch'), 'That is wrong!');\n```\n\n![](http://om1c35wrq.bkt.clouddn.com/day9-assert.png)\n\n### 打印表格\n`console.table()`方法，可以将数组、对象以表格的形式打印输出，如果只输出其中的某一列，可以加上第二个参数，如下所示：\n\n```Javascript\nconsole.table(dogs);\nconsole.table(dogs, [\"age\"]);\n```\n![](http://om1c35wrq.bkt.clouddn.com/day9-table.png)\n\n### 分组打印\n\n```Javascript\nconst dogs = [{ name: 'Snickers', age: 2 }, { name: 'hugo', age: 8 }];\ndogs.forEach(dog => {\n    console.group(`${dog.name}`);        \n//  console.groupCollapsed(`${dog.name}`);  // 列表默认叠起状态\n    console.log(`${dog.name}`);\n    console.log(`${dog.age}`);\n    console.log(`${dog.name} 有 ${dog.age} 岁了`);\n    console.groupEnd();\n});\n```\n`group()`方法中可以传入这个分组的名称。`group()/groupCollapsed() `与 `groupEnd()` 之间的内容会自动分组，区别在于是否能自动折叠。\n\n![](http://om1c35wrq.bkt.clouddn.com/day9-group-1.png)\n![](http://om1c35wrq.bkt.clouddn.com/day9-group-2.png)\n\n### console.count() 计数\n\n通过`console.count()`可以对输出的对象进行计数。但需要注意的是这里的计数对象仅限于由 `count()` 输出的内容，并非所有 `console` 中的输出。\n\n![](http://om1c35wrq.bkt.clouddn.com/day9-count11.png)\n\n### `time` 计时\n\n用 `time(\"name\")` 和 `timeEnd(\"name\")` 分别控制开始点和结束点，它们两的参数表示当前计时的名称，可以自定义但需要保持相同。所以如果想看异步获取数据花了多场时间，可以这样写：\n\n````js\nconsole.time('fetch my data');\nfetch(\"https://api.github.com/users/soyaine\")\n  .then(data => data.json())\n  .then(data => {\n  console.timeEnd('fetch my data');\n  console.log(data);\n});\n````\n![](http://om1c35wrq.bkt.clouddn.com/day9-time.png)\n如果 timeEnd 中的名称如果和上面不一样，得到的数据是系统当前时间换算后的毫秒值。\n\n"
  },
  {
    "path": "09 - Console 调试各种姿势指南/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Console Tricks!</title>\n</head>\n\n<body>\n\n  <p style=\"color:red;font-size:40px\" onClick=\"makeGreen()\">全栈部落的小伙伴，请点击我！</p>\n\n  <script>\n    const dogs = [{\n      name: 'Snickers',\n      age: 2\n    }, {\n      name: 'hugo',\n      age: 8\n    }];\n\n    function makeGreen() {\n      const p = document.querySelector('p');\n      p.style.color = '#BADA55';\n      p.style.fontSize = '50px';\n      p.innerText = \"打开控制台，查看调试效果。\";\n    }\n\n    // Regular\n    console.log('hello');\n\n    // Interpolated\n    console.log(\"I am a String: %s \", \"log\"); //log\n    console.log(\"I am a int number: %d \", 1); //1\n    console.log(\"I am a float number: %d \", 1.23); //1.23\n    let dog = {\n      name: \"Lucky\",\n      age: \"5\"\n    };\n    console.log(\"%o\", dog);\n    console.log(\"%c3D Text\",\n      \" text-shadow: 0 1px 0 #ccc,0 2px 0 #c9c9c9,0 3px 0 #bbb,0 4px 0 #b9b9b9,0 5px 0 #aaa,0 6px 1px rgba(0,0,0,.1),0 0 5px rgba(0,0,0,.1),0 1px 3px rgba(0,0,0,.3),0 3px 5px rgba(0,0,0,.2),0 5px 10px rgba(0,0,0,.25),0 10px 10px rgba(0,0,0,.2),0 20px 20px rgba(0,0,0,.15);font-size:5em\"\n    );\n\n\n    // warning!\n    console.warn('OH NOOO');\n\n    // Error :|\n    console.error('Shit!');\n\n    // Info\n    console.info('Crocodiles eat 3-4 people per year');\n\n    //debug\n    console.debug(\"用于输出调试信息\");\n\n    // Testing\n    const p = document.querySelector('p');\n\n    console.assert(p.classList.contains('ouch'), 'That is wrong!');\n\n    // clearing\n    console.clear();\n\n    // Viewing DOM Elements\n    console.log(p);\n    console.dir(p);\n\n    console.clear();\n\n    console.table(dogs);\n    console.table(dogs, [\"age\"]);\n\n    // Grouping together\n    dogs.forEach(dog => {\n      console.groupCollapsed(`${dog.name}`);\n      console.log(`This is ${dog.name}`);\n      console.log(`${dog.name} is ${dog.age} years old`);\n      console.log(`${dog.name} is ${dog.age * 7} dog years old`);\n      console.groupEnd(`${dog.name}`);\n    });\n\n    // counting\n\n    console.count('liyuechun');\n    console.count('chunge');\n    console.count('黎跃春');\n    console.count('黎跃春');\n    console.count('黎跃春');\n    console.count('黎跃春');\n    console.count('黎跃春');\n    console.count('全栈部落');\n    console.count('黎跃春');\n    console.count('黎跃春');\n    console.count('黎跃春');\n    console.count('全栈部落');\n\n    // timing\n    console.time('fetch my data');\n    fetch(\"https://gist.githubusercontent.com/liyuechun/f00bb31fb8f46ee0a283a4d182f691b4/raw/3ea4b427917048cdc596b38b67b5ed664605b76d/TangPoetry.json\")\n      .then(data => data.json())\n      .then(data => {\n        console.timeEnd('fetch my data');\n        console.log(data);\n      });\n  </script>\n</body>\n\n</html>\n"
  },
  {
    "path": "10 - JS 实现 Checkbox 中按住 Shift 的多选功能/README.md",
    "content": "# Day10 - JS 实现 Checkbox 中按住 Shift 的多选功能\n\n## 项目效果\n\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/10%20-%20JS%20%E5%AE%9E%E7%8E%B0%20Checkbox%20%E4%B8%AD%E6%8C%89%E4%BD%8F%20Shift%20%E7%9A%84%E5%A4%9A%E9%80%89%E5%8A%9F%E8%83%BD/GIF.gif)\n\n初始文档中提供了一组 checkbox 类型的 input 元素，选中某个复选框时，其 <p> 标签中的文字会显示删除线。最终效果是，提供按下 Shift 键后进行多选操作的功能。readme.md文档中的说明对应的是xunzhoaxinag.html。\n## 操作方法\n\n1. 选中 A 项\n2. 按下 Shift\n3. 再选中 B 项\n4. A-B 之间的所有项都被选中或者取消\n\n## 实现方法\n\n### 方法一\n\nWes Bos 在文档里提供了一种解决办法：用一个变量，来标记这个范围。\n\n变量初始值为 `false`，当按下 Shift 键且同时选中了某个元素的时候，遍历所有项，遍历过程中，若遇到 A 或 B，则将标记值取反。同时，将所有标记为 `true` 的项设置为选中。\n\n```js\nlet startChecked;\n\n//\t处理方法一：用变量 inBetween 对需要选中的元素进行标记\nfunction handleCheck0(e) {\n\tlet inBetween = false;\n\tif(e.shiftKey && this.checked){\n\t\tboxs.forEach(input => {\n\t\t\tconsole.log(input);\n\t\t\tif(input === startChecked || input ===this) {\n\t\t\t\tinBetween = !inBetween;\n\t\t\t}\n\t\t\tif(inBetween) {\n\t\t\t\tconsole.log(\"on\");\n\t\t\t\tinput.checked = true;\n\t\t\t}\n\t});\n\t}\n\tstartChecked = this;\n}\n```\n> 上面会出现一个问题，初次加载页面时，按住 Shift 再点击某一项，此项之后的元素都会被选中。此外，对于取消选中，无法批量操作。所以我参照了 Stack Overflow 的一个答案： How can I shift-select multiple checkboxes like GMail? 改进得到第二种解决方案。\n\n### 方法二\n方法一中的 inBetween 仅仅表示此项是否在被选中的范围中，此处会赋给它更多的意义，用它来表示此项是选中还是未选中，而范围划定则由数组来解决。  \n\n首先将获取到的 `<input>` 组转化为数组，针对每次操作，获取 A 和 B，利用 `indexOf()` 来获得 A 和 B 在数组中的索引值，由此即可确定范围，并能通过 `slice()` 来直接截取 A-B 的所有 DOM 元素，并进行状态改变的操作，而变量 onOff 表示 A-B 范围内的状态，true 表示选中，false 表示取消选中。\n```js\n    const boxs = document.querySelectorAll('.inbox input[type=\"checkbox\"]');\n    const boxArr = Array.from(boxs);\n    boxArr.forEach(box => box.addEventListener('click', handleCheck1));\n\n//  处理方法二：利用数组索引获取需要选中的范围\n\n    let lastinput;//用来保存上一次的点击元素\n    let onoff; //用来保存上一次的点击状态，已提供给截取范围内CheckBox的状态\n\n    function handleCheck1(e){\n        if(e.shiftKey){                   //若果按下shift按键再点击时进入该程序，主要用来处理索引值\n            let cur=boxArr.indexOf(this);    //用来获取当前点击元素是第几个input\n            let last=boxArr.indexOf(lastinput);  //用来获取上一次点击元素是第几个input\n            boxArr.slice(Math.min(cur,last),Math.max(cur,last)+1) //slice返回一个子数组\n                .forEach(item=>item.checked = onoff); //将截取出来的各项状态设置的和上下点击元素的状态一致\n        }\n\n        lastinput = this;  //用来存放第一次或者上一次的点击元素（将当前点击元素作为上次元素，然后在有点击时和下次又组成一个范围）\n        onoff = lastinput.checked ? true : false;  //识别第一次或者上一次点击元素的状态值\n    }\n}\n```\n> 学习用全局变量来存放（由当前状态，转换而成的）上次状态。  \n设置两个变量(全局变量)来分别保存上一次的点击元素和其状态，接下里选中范围的元素状态以此为依据。  \n在shift被按下时，需要得到上次点击元素的索引，和当前点击元素（this）的索引，  \n然后将该段内input元素的状态全部统一于上次点击后的状态。  \n然后将当前点击元素作为上次元素，进行一个接龙。  \n### 涉及知识点\n- 1.shiftKey：检测 SHIFT 键是否被按住。  \n事件属性可返回一个布尔值，指示当事件发生时，“SHIFT”键是否被按下并保持住。  \n语法：event.shiftKey=true|false|1|0，这里的event用this无效，用e.target无效。  \n- 2.document.querySelectorAll('.inbox input[type=\"checkbox\"]')  \n通过[]和属性名称来选取指定元素  \n\n"
  },
  {
    "path": "10 - JS 实现 Checkbox 中按住 Shift 的多选功能/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n</head>\n\n<body>\n  <style>\n    html {\n      font-family: sans-serif;\n      background: #ffc600;\n    }\n\n    .inbox {\n      max-width: 400px;\n      margin: 50px auto;\n      background: white;\n      border-radius: 5px;\n      box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.1);\n    }\n\n    .item {\n      display: flex;\n      align-items: center;\n      border-bottom: 1px solid #F1F1F1;\n    }\n\n    .item:last-child {\n      border-bottom: 0;\n    }\n\n\n    input:checked+p {\n      background: #F9F9F9;\n      text-decoration: line-through;\n    }\n\n    input[type=\"checkbox\"] {\n      margin: 20px;\n    }\n\n    p {\n      margin: 0;\n      padding: 20px;\n      transition: background 0.2s;\n      flex: 1;\n      font-family: 'helvetica neue';\n      font-size: 20px;\n      font-weight: 200;\n      border-left: 1px solid #D1E2FF;\n    }\n\n    .details {\n      text-align: center;\n      font-size: 15px;\n    }\n  </style>\n  <!--\n   The following is a common layout you would see in an email client.\n\n   When a user clicks a checkbox, holds Shift, and then clicks another checkbox a few rows down, all the checkboxes inbetween those two checkboxes should be checked.\n\n  -->\n  <div class=\"inbox\">\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>This is an inbox layout.</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Check one item</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Hold down your Shift key</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Check a lower item</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Everything inbetween should also be set to checked</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Try do it with out any libraries</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Just regular JavaScript</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Good Luck!</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Don't forget to tweet your result!</p>\n    </div>\n  </div>\n\n  <script>\n    const checkboxes = document.querySelectorAll('.inbox input[type=\"checkbox\"]');\n\n    let lastChecked;\n\n    function handleCheck(e) {\n      // Check if they had the shift key down\n      // AND check that they are checking it\n      let inBetween = false;\n      if (e.shiftKey && this.checked) {\n        // go ahead and do what we please\n        // loop over every single checkbox\n        checkboxes.forEach(checkbox => {\n          console.log(checkbox);\n          if (checkbox === this || checkbox === lastChecked) {\n            inBetween = !inBetween;\n            console.log('STarting to check them inbetween!');\n          }\n\n          if (inBetween) {\n            checkbox.checked = true;\n          }\n        });\n      }\n\n      lastChecked = this;\n    }\n\n    checkboxes.forEach(checkbox => checkbox.addEventListener('click', handleCheck));\n    console.log(\"ss1\");\n  </script>\n</body>\n\n</html>\n"
  },
  {
    "path": "10 - JS 实现 Checkbox 中按住 Shift 的多选功能/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n</head>\n\n<body>\n  <style>\n    html {\n      font-family: sans-serif;\n      background: #ffc600;\n    }\n\n    .inbox {\n      max-width: 400px;\n      margin: 50px auto;\n      background: white;\n      border-radius: 5px;\n      box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.1);\n    }\n\n    .item {\n      display: flex;\n      align-items: center;\n      border-bottom: 1px solid #F1F1F1;\n    }\n\n    .item:last-child {\n      border-bottom: 0;\n    }\n\n\n    input:checked+p {\n      background: #F9F9F9;\n      text-decoration: line-through;\n    }\n\n    input[type=\"checkbox\"] {\n      margin: 20px;\n    }\n\n    p {\n      margin: 0;\n      padding: 20px;\n      transition: background 0.2s;\n      flex: 1;\n      font-family: 'helvetica neue';\n      font-size: 20px;\n      font-weight: 200;\n      border-left: 1px solid #D1E2FF;\n    }\n\n    .details {\n      text-align: center;\n      font-size: 15px;\n    }\n  </style>\n  <!--\n   The following is a common layout you would see in an email client.\n\n   When a user clicks a checkbox, holds Shift, and then clicks another checkbox a few rows down, all the checkboxes inbetween those two checkboxes should be checked.\n\n  -->\n  <div class=\"inbox\">\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>This is an inbox layout.</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Check one item</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Hold down your Shift key</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Check a lower item</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Everything inbetween should also be set to checked</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Try do it with out any libraries</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Just regular JavaScript</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Good Luck!</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\">\n      <p>Don't forget to tweet your result!</p>\n    </div>\n  </div>\n\n  <script>\n  </script>\n</body>\n\n</html>\n"
  },
  {
    "path": "10 - JS 实现 Checkbox 中按住 Shift 的多选功能/sunzhaoxiang.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>checkbox的shift多选功能</title>\n    <style>\n\n    </style>\n</head>\n<body>\n  <style>\n    html {\n      font-family: sans-serif;\n      background: #123456;\n    }\n\n    .inbox {\n      max-width: 400px;\n      margin: 50px auto;\n      background: white;\n      border-radius: 5px;\n      box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.1);\n    }\n\n    .item {\n      display: flex;\n      align-items: center;\n      border-bottom: 1px solid #F1F1F1;\n    }\n\n    .item:last-child {\n      border-bottom: 0;\n    }\n\n\n    input:checked+p {\n      background: #F9F9F9;\n      text-decoration: line-through;\n    }\n\n    input[type=\"checkbox\"] {\n      margin: 20px;\n    }\n\n    p {\n      margin: 0;\n      padding: 20px;\n      transition: background 0.2s;\n      flex: 1;\n      font-family: 'Microsoft Yahei', 'helvetica neue';\n      font-size: 20px;\n      font-weight: 200;\n      border-left: 1px solid #D1E2FF;\n    }\n\n    .details {\n      text-align: center;\n      font-size: 15px;\n    }\n  </style>\n  <!--\n   The following is a common layout you would see in an email client.\n\n   When a user clicks a checkbox, holds Shift, and then clicks another checkbox a few rows down, all the checkboxes inbetween those two checkboxes should be checked.\n\n  -->\n  <div class=\"inbox\">\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N0\">\n      <p>无双看过吗？</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N1\">\n      <p>从零到壹全栈部落</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N2\">\n      <p>输出是最好的学习方式</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N3\">\n      <p>赠人玫瑰，手留余香</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N4\">\n      <p>祥哥的说，CSDN博客 </p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N5\">\n      <p>全栈部落</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N6\">\n      <p>你会玩魔方吗？</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N7\">\n      <p>你会留下联系方式吗？</p>\n    </div>\n    <div class=\"item\">\n      <input type=\"checkbox\" class=\"N8\">\n      <p>你会选择下载吗？</p>\n    </div>\n  </div>\n<script>\n    const boxs = document.querySelectorAll('.inbox input[type=\"checkbox\"]');\n    const boxArr = Array.from(boxs);\n    boxArr.forEach(box => box.addEventListener('click', handleCheck1));\n    \n    \n//  处理方法一：用变量 inBetween 对需要选中的元素进行标记\n    function handleCheck0(e) {\n        let inBetween = false;\n        if(e.shiftKey && this.checked){\n            boxs.forEach(input => {\n                console.log(\"input的值是:\",input);\n                console.log(\"this值是:\",this);\n                if(input === lastChecked || input ===this) {\n                    inBetween = !inBetween;\n                }\n                if(inBetween) {\n                    console.log(\"on\");\n                    input.checked = true;\n                }\n        });\n        }\n        lastChecked = this;\n    }\n\n//  处理方法二：利用数组索引获取需要选中的范围\n\n    let lastinput;//用来保存上一次的点击元素\n    let onoff; //用来保存上一次的点击状态，已提供给截取范围内CheckBox的状态\n\n    function handleCheck1(e){\n        if(e.shiftKey){                   //若果按下shift按键再点击时进入该程序，主要用来处理索引值\n            let cur=boxArr.indexOf(this);    //用来获取当前点击元素是第几个input\n            let last=boxArr.indexOf(lastinput);  //用来获取上一次点击元素是第几个input\n            boxArr.slice(Math.min(cur,last),Math.max(cur,last)+1) //slice返回一个子数组\n                .forEach(item=>item.checked = onoff); //将截取出来的各项状态设置的和上下点击元素的状态一致\n        }\n\n        lastinput = this;  //用来存放第一次或者上一次的点击元素（将当前点击元素作为上次元素，然后在有点击时和下次又组成一个范围）\n        onoff = lastinput.checked ? true : false;  //识别第一次或者上一次点击元素的状态值\n    }\n// 学习用全局变量来存放（由当前状态，转换而成的）上次状态。\n// 设置两个变量(全局变量)来分别保存上一次的点击元素和其状态，接下里选中范围的元素状态以此为依据。\n// 在shift被按下时，需要得到上次点击元素的索引，和当前点击元素（this）的索引，\n// 然后将该段内input元素的状态全部统一于上次点击后的状态。\n// 然后将当前点击元素作为上次元素，进行一个接龙。\n\n</script>\n</body>\n</html>\n<!-- \n1.shiftKey：检测 SHIFT 键是否被按住。\n事件属性可返回一个布尔值，指示当事件发生时，“SHIFT”键是否被按下并保持住。\n语法：event.shiftKey=true|false|1|0，这里的event用this无效，用e.target无效。\n2.document.querySelectorAll('.inbox input[type=\"checkbox\"]')\n通过[]和属性名称来选取指定元素\n -->"
  },
  {
    "path": "11 - 自定义视频播放器/README.md",
    "content": "# Day11 - 自定义视频播放器\n\n## 效果展示\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/11%20-%20%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%99%A8/GIF.gif)\n\n第十一天是要做一个自定义的视频播放器，在具有基本样式的前提下，实现视频的播放，暂停，进度条拖拽，音量加减，播放速度加减，快进快退的功能。\n\n## 实现思路\n1. 首先需要分别将变量绑定至页面上的元素\n2. 分别实现播放，暂停，声音加减，播放速度加减，拖拽快进，点击快进等函数\n3. 事件绑定，将页面元素绑定相应触发事件\n\n## CSS部分解读\n- 视频播放器控制台隐藏弹出的设置  \n```CSS\n/*控制台的样式设置*/\n.player__controls {\n  display:flex;   /*弹性布局，子元素生效*/\n  position: absolute;\n  bottom:0;\n  width: 100%;\n  transform: translateY(100%) translateY(-5px);\n  transition:all .3s;\n  flex-wrap:wrap;   /*运行flex的子元素进行灵活的换行布局*/\n  background:rgba(0,0,0,0.1);\n}\n\n.player:hover .player__controls {\n  transform: translateY(0);  /*这里将控制台显示出来*/\n}\n/*当有鼠标悬停视频播放器时，控制台弹出，此时设置进度条高度为15px*/\n.player:hover .progress {\n  height:15px;\n}\n```\n> 隐藏露头功能（天猫界面趴着那只猫，只露了个眼睛，悬停会跳出来，就可以据此来模拟实现）  \ntransform: translateY(100%) translateY(-5px);  \n这里的控制台有一定的高度，translateY(100%)，是为了将控制台通过位移结合overflow：hidden进行完全隐藏， 然后translateY（-5px）,是为了将控制台顶部高度为5px的控制条，向上移动然后将其显示出来。  \n\n- input的range类型：[滑动条的自定义样式设置](https://blog.csdn.net/qq_39207948/article/details/85880391)\n\n## 变量绑定\nHTML 元素中，`video` 标签是我们的视频，而下面的 `player__controls` 就是我们自己的控制面板\n\n```html\n    <div class=\"player\">\n\n        <video class=\"player__video viewer\" src=\"./mp4/demo.mp4\" loop>您的浏览器不支持播放该视频！</video>\n\n        <div class=\"player__controls\">\n            <div class=\"progress\">\n                <div class=\"progress__filled\"></div>\n            </div>\n            <button class=\"player__button toggle\" title=\"Toggle Play\">►</button>\n            <input type=\"range\" name=\"volume\" class=\"player__slider\" min=0 max=\"1\" step=\"0.05\" value=\"1\" title=\"音量\">\n            <input type=\"range\" name=\"playbackRate\" class=\"player__slider\" min=\"0.5\" max=\"2\" step=\"0.1\" value=\"1\" title=\"播放速度\">\n            <button data-skip=\"-5\" class=\"player__button\">« 5s</button>\n            <button data-skip=\"5\"  class=\"player__button\">5s »</button>\n        </div>\n    </div>\n```\n\n开始之前我们先把所有需要用到的元素节点先取到：\n\n```javascript\n        // 获取视频播放器中的各个元素\n        const player      = document.querySelector('.player');\n        const video       = player.querySelector('.viewer');\n        const progress    = player.querySelector('.progress');\n        const progressBar = player.querySelector('.progress__filled');\n        const toggle      = player.querySelector('.toggle');                        //播放/停止图标按钮\n        const ranges      = Array.from(player.querySelectorAll('.player__slider')); //音量和播放速度按钮\n        const skipButtons = Array.from(player.querySelectorAll('[data-skip]'));     //快进快退按钮\n```\n### 源代码功能函数的实现：\n#### N0.1 点击视频播放器或者暂停/播放按钮控制视频的停-播，并且暂停/播放按钮图标随着改变\n```HTML\n        video.addEventListener(\"click\",togglePlay);  //视频播放器监听点击事件，控制停-播\n        toggle.addEventListener(\"click\",togglePlay); //toggle按钮监听点击事件，控制停-播\n```\n```javascript\n        // 使用video的两个方法使得动画暂停和运行，而判断的依据就是video.paused属性。\n        function togglePlay(){\n            video.paused ? video.play() : video.pause();\n        }\n```\n接下来需要实现，toggle图标随播放状态而改变\n```HTML\n        video.addEventListener(\"play\",updateButton); //根据监听video执行暂停和播放的方法来改变按钮标志\n        video.addEventListener(\"pause\",updateButton); //根据监听video执行暂停和播放的方法来改变按钮标志\n```\n```JS\n        //改变toggle的图标\n        function updateButton(){\n            toggle.textContent = video.paused ? \"►\":\"II\" ;\n        } \n```\n#### NO.2 音量和播放速度滑动条功能的实现\n```HTML\n<input type=\"range\" name=\"volume\" class=\"player__slider\" min=0 max=\"1\" step=\"0.05\" value=\"1\" title=\"音量\">\n<input type=\"range\" name=\"playbackRate\" class=\"player__slider\" min=\"0.5\" max=\"2\" step=\"0.1\" value=\"1\" title=\"播放速度\">\n```JS\nconst ranges  = Array.from(player.querySelectorAll('.player__slider'));\n\nranges[0].addEventListener(\"change\",handle1);\nranges[1].addEventListener(\"change\",handle2);\n        // 音量改变函数\n        function handle1(){\n            console.log(\"yinliang:\",this.value);\n            video.volume=this.value;\n        }\n        //播放速度改变函数\n        function handle2(){\n            console.log(\"shudu:\",this.value);\n\n            video.playbackRate=this.value;\n        }\n```\n> 该视频播放器有两个滑动条，前者控制音量的大小，后者控制播放速度，由于input类型相同，但是绑定的事件处理函数又不相同，可以利用name值做到一个函数来处理不同的回调函数功能（设定name值和对象的属性值一致），具体代码如下：\n```JS\nranges.forEach(item=>item.addEventListener('change',rangeHandle));\n// 音量和播放速度控制函数\nfunction rangeHandle(){\n    video[this.name]=this.value;  //这里动态的进行了对象的属性值设置\n}\n```\n> 其中需要注意的是，他们分别有一个 volume 和 playbackRate 的 name 属性，我们起这两个名字是因为他们是 video 对象里对应音量和播放速度的两个属性名。这样起名并不是必须的，但可以让我们后面 js 的操作更精简。\n因为我们上面说过，input 的 name 值和 video 对象中的属性名是一样的，可以看到在 handleRangeUpdate 函数中我们利用了 this.name 的写法来代表属性，，这里的 this 一样是 addEventListener 的调用者，即 range。\n#### NO.3 视频快退和快进\n```HTML\n            <button data-skip=\"-5\" class=\"player__button\">« 5s</button>\n            <button data-skip=\"5\"  class=\"player__button\">5s »</button>\n```\n```JS\n        const skipButtons = Array.from(player.querySelectorAll('[data-skip]'));     //快进快退按钮，将伪数组转换为真正的数组\n        \n        skipButtons.forEach(item=>item.addEventListener(\"click\",skip));//给快进快退按钮添加点击事件监听函数\n        \n        // 视频快退和快进控制函数\n        function skip(){\n            video.currentTime+=(+this.dataset.skip);\n        }\n```\n> video 有一个属性叫 currentTime，可以用来设置视频当前的时间。我们只要修改这个属性就可以了  \n要注意的是，这里就不能用 this 来访问 video 对象了，因为在这里面，this 指向的是遍历得到的每一个 button，而我们是要修改 video 的 currentTime 属性。 \ndata-** 这样的属性以前提到过了，在 JavaScript 中需要通过 .dataset.** 来访问。因为我们获取到的是字符串，所以要通过\"+\"来转换成数值。\n\n#### NO.4 进度条随着视频播放而改变，进度条拖动控制视频播放功能的实现，\n- 进度条随着视频播放而改变  \n我们的进度条需要能在鼠标点击和拖动的时候改变视频播放的进度。我们先实现进度条随着视频播放更新进度的功能。  \n\n进度条显示进度的原理很简单，progress__filled 这个元素是一个 flex 定位的元素，我们改变其 flex-basis 的百分比值就可以调节它所占父元素的宽度。flex-basis 值代表 flex 元素在主轴方向上的初始尺寸。progressBar.style.flexBasis对其进行设值便可以动态改变进度条的填充长度。\n```html\n            <div class=\"progress\">\n                <div class=\"progress__filled\"></div>\n            </div>\n```\n```js\n        const progress    = player.querySelector('.progress');\n        const progressBar = player.querySelector('.progress__filled'); \n        \n        video.addEventListener(\"timeupdate\",progressFilled); \n        \n        //进度条跟随视频播放改变,那么需要实时自动来执行这个函数。\n        function progressFilled(){\n            progressBar.style.flexBasis=((video.currentTime/video.duration)*100).toFixed(2)+\"%\";\n\n        }\n```\n> 现在只要运行 progressFilled 这个函数就能够更新对应的进度条，但我们需要的是自动执行这个操作。也许你会想到利用 setInterval 设置一个定时器，其实 video 元素给我们提供了更好的方法—— timeupdate 事件。这个事件会在媒体文件的 currentTime 属性改变的时触发.\n\n- 进度条控制视频播放功能的实现（包括点击改变和拖动改变）  \n实现思路：根据进度条填充部分占据整个进度条的百分比，然后结合整个video的duration，便可得出当前的video.currentTime\n```JS\n        progress.addEventListener(\"click\",progressClickHandle);  //点击改变进度条位置来跟新视频播放进度\n        // 拖动事件拆分为：mousedown,mousemove,mouseup\n        progress.addEventListener(\"mousedown\",()=>mousedownFlag=true);  //鼠标在进度条上按下不放将标志位置为true，然后拖动即触发mousemove事件\n        progress.addEventListener(\"mousemove\",progressMoveHandle);  //拖动改变进度条位置来跟新视频播放进度\n        progress.addEventListener(\"mouseup\",()=>mousedownFlag=false); //鼠标抬起后，标志位为false，即便触发mousemove，也不会更新视频进度\n\n        //点击进度条来改变视频播放（手动更新视频播放进度的函数，点击和拖动都要使用这个函数）\n        function progressClickHandle(e){\n            video.currentTime = (e.offsetX/progress.offsetWidth)*video.duration;\n        }\n        // 鼠标按下拖动进度条时，更新视频播放进度，从而触发timeupdate事件来校正进度条长度。\n        let mousedownFlag = false;\n        function progressMoveHandle(e){\n            if(mousedownFlag){\n                progressClickHandle(e);\n            }\n\n        }\n```\n### 涉及知识点\n1.title 属性规定关于元素的额外信息。  \n&emsp; 这些信息通常会在鼠标移到元素上时显示一段工具提示文本（tooltip text）。  \n2.:focus 伪类在元素获得焦点时向元素添加特殊的样式。  \n3.flex-wrap 属性规定flex容器是单行或者多行，同时横轴的方向决定了新行堆叠的方向。  \n&emsp; 注释：IE 浏览器不支持此属性。    \n4.光标cursor的常见样式pointer|e-resize   \n5.flex-basis属性用于设置或检索弹性盒伸缩基准值  \n6.[flex属性的全面总结](https://blog.csdn.net/qq_39207948/article/details/85956861)    \n7.video 对象有一个叫 paused 的属性来判断视频是否在播放  \n&emsp; .play() 方法可以播放视频，.pause() 方法暂停播放  \n8.textContent 属性设置或返回指定节点的文本内容，以及它的所有后代。[结合demo理解记忆](https://blog.csdn.net/qq_39207948/article/details/86099905)    \n&emsp; 如果您设置了 textContent 属性，会删除所有子节点，并被替换为包含指定字符串的一个单独的文本节点。  \n9.playbackRate 属性设置或返回音频/视频的当前播放速度。  \n只有 Google Chrome 和 Safari 支持 playbackRate 属性。  \n&emsp; 1.0 正常速度  \n&emsp; 0.5 半速（更慢）  \n&emsp; 2.0 倍速（更快）  \n&emsp; -1.0 向后，正常速度  \n&emsp; -0.5 向后，半速  \n10.video.playbackRate：设置或返回音频/视频播放的速度   \n&nbsp; video.volume：设置或返回音频/视频的音量  \n11. 鼠标事件进行整理，具体都有哪些方法和属性。  \n12. currentTime 属性设置或返回音频/视频播放的当前位置（以秒计）。  \n&emsp; 当设置该属性时，播放会跳跃到指定的位置。   \n13.duration\t返回当前音频/视频的长度（以秒计）  \n14. timeupdate事件，当目前的播放位置已更改时触发。可用来实时更新进度条部分。\n"
  },
  {
    "path": "11 - 自定义视频播放器/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>自定义视频播放器</title>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n    <div class=\"player\">\n\n        <video class=\"player__video viewer\" src=\"./mp4/demo.mp4\" loop>您的浏览器不支持播放该视频！</video>\n\n        <div class=\"player__controls\">\n            <div class=\"progress\">\n                <div class=\"progress__filled\"></div>\n            </div>\n            <button class=\"player__button toggle\" title=\"Toggle Play\">►</button>\n            <input type=\"range\" name=\"volume\" class=\"player__slider\" min=0 max=\"1\" step=\"0.05\" value=\"1\" title=\"音量\">\n            <input type=\"range\" name=\"playbackRate\" class=\"player__slider\" min=\"0.5\" max=\"2\" step=\"0.1\" value=\"1\" title=\"播放速度\">\n            <button data-skip=\"-5\" class=\"player__button\">« 5s</button>\n            <button data-skip=\"5\"  class=\"player__button\">5s »</button>\n        </div>\n    </div>\n    <script>\n        // 获取视频播放器中的各个元素\n        const player      = document.querySelector('.player');\n        const video       = player.querySelector('.viewer');\n        const progress    = player.querySelector('.progress');\n        const progressBar = player.querySelector('.progress__filled');\n        const toggle      = player.querySelector('.toggle');                        //播放/停止图标按钮\n        const ranges      = Array.from(player.querySelectorAll('.player__slider')); //音量和播放速度按钮\n        const skipButtons = Array.from(player.querySelectorAll('[data-skip]'));                  //快进快退按钮\n\n        video.addEventListener(\"click\",togglePlay);  //监听点击事件，然后调用play方法或者pause方法\n        toggle.addEventListener(\"click\",togglePlay);\n        video.addEventListener(\"play\",updateButton); //根据监听video执行暂停和播放的方法来改变按钮标志\n        video.addEventListener(\"pause\",updateButton); //根据监听video执行暂停和播放的方法来改变按钮标志\n\n        ranges.forEach(item=>item.addEventListener('change',rangeHandle));   //只有change事件只有在blur失焦时才触发。\n        ranges.forEach(range => range.addEventListener('mousemove', rangeHandle));  //鼠标移动时触发事件。\n\n        skipButtons.forEach(item=>item.addEventListener(\"click\",skip));\n\n        video.addEventListener(\"timeupdate\",progressFilled);\n\n        progress.addEventListener(\"click\",progressClickHandle);  //点击改变进度条位置来跟新视频播放进度\n        // 拖动事件拆分为：mousedown,mousemove,mouseup\n        progress.addEventListener(\"mousedown\",()=>mousedownFlag=true);  //鼠标在进度条上按下不放将标志位置为true，然后拖动即触发mousemove事件\n        progress.addEventListener(\"mousemove\",progressMoveHandle);  //拖动改变进度条位置来跟新视频播放进度\n        progress.addEventListener(\"mouseup\",()=>mousedownFlag=false); //鼠标抬起后，标志位为false，即便触发mousemove，也不会更新视频进度\n\n\n        // 使用video的两个方法使得动画暂停和运行，而判断的依据就是video.paused属性。\n        function togglePlay(){\n            video.paused ? video.play() : video.pause();\n        }\n        //改变toggle的图标\n        function updateButton(){\n            toggle.textContent = video.paused ? \"►\":\"II\" ;\n        } \n        // 音量和播放速度控制函数\n        function rangeHandle(){\n            video[this.name]=this.value;  //这里动态的进行了对象的属性值设置\n        }\n        // 视频快退和快进，这里我演示视频长度仅仅为21秒\n        function skip(){\n            video.currentTime+=(+this.dataset.skip);\n        }\n        //进度条跟随视频播放改变,那么需要实时自动来执行这个函数。\n        function progressFilled(){\n            progressBar.style.flexBasis=((video.currentTime/video.duration)*100).toFixed(2)+\"%\";\n\n        }\n        //点击进度条来改变视频播放（手动更新视频播放进度的函数，点击和拖动都要使用这个函数）\n        function progressClickHandle(e){\n            video.currentTime = (e.offsetX/progress.offsetWidth)*video.duration;\n        }\n        // 鼠标按下拖动进度条时，更新视频播放进度，从而触发timeupdate事件来校正进度条长度。\n        let mousedownFlag = false;\n        function progressMoveHandle(e){\n            if(mousedownFlag){\n                progressClickHandle(e);\n            }\n\n        }\n\n\n    </script>\n</body>\n</html>\n<!-- \nbackground-size属性\nvideo 对象有一个叫 paused 的属性来判断视频是否在播放,播放返回true，暂停返回false\n.play() 方法可以播放视频，.pause() 方法暂停播放\nplaybackRate 属性设置或返回音频/视频的当前播放速度。\n只有 Google Chrome 和 Safari 支持 playbackRate 属性。\n -->"
  },
  {
    "path": "11 - 自定义视频播放器/style.css",
    "content": "\n* @Author: Administrator\n* @Date:   2018-12-29 15:53:10\n* @Last Modified by:   Administrator\n* @Last Modified time: 2019-01-05 19:52:35\n*/\nhtml {\n  box-sizing: border-box;\n}\n\n*, *:before, *:after {\n  box-sizing: inherit;\n}\n\nbody {\n  padding: 0;\n  display:flex;\n  background:#7A419B;\n  min-height:100vh;\n  background: linear-gradient(135deg, #7c1599 0%,#921099 48%,#7e4ae8 100%);\n  background-size:cover;\n  align-items: center;\n  justify-content: center;\n}\n/*整个视频播放器的样式设置*/\n.player {\n  max-width:750px;\n  border:5px solid rgba(0,0,0,0.2);\n  box-shadow:0 0 20px rgba(0,0,0,0.2);\n  position: relative;\n  font-size: 0;\n  overflow: hidden;\n}\n\n.player__video {\n  width: 100%;\n}\n/*按钮选项的通用配置，去除浏览器默认配置*/\n.player__button {\n  background:none;\n  border:0;\n  line-height:1;\n  color:white;\n  text-align: center;\n  outline:0;\n  padding: 0;\n  cursor:pointer;\n  max-width:50px;\n}\n\n.player__button:focus {\n  border-color: #ffc600;\n}\n/*滑动条的设置*/\n.player__slider {\n  width:10px;\n  height:30px;\n}\n\n/*控制台的样式设置*/\n.player__controls {\n  display:flex;   /*弹性布局，子元素生效*/\n  position: absolute;\n  bottom:0;\n  width: 100%;\n  transform: translateY(100%) translateY(-5px);\n  transition:all .3s;\n  flex-wrap:wrap;   /*运行flex的子元素进行灵活的换行布局*/\n  background:rgba(0,0,0,0.1);\n}\n/*设置控制台鼠标悬停的效果*/\n.player:hover .player__controls {\n  transform: translateY(0);\n}\n/*当有鼠标悬停视频播放器时，控制台弹出，此时设置进度条高度为15px*/\n.player:hover .progress {\n  height:15px;\n}\n\n.player__controls > * {\n  flex:1;\n}\n/*进度条样式设置*/\n.progress {\n  flex:10;\n  position: relative;\n  display:flex;\n  flex-basis:100%; /*初始长度占据100%*/\n  height:5px;\n  transition:height 0.3s;  /*当鼠标位于视频播放器，进度条由5px变成15PX*/\n  background:rgba(0,0,0,0.5);\n  cursor:e-resize;  /*定义鼠标的样式是左右箭头滑动样式*/\n}\n/*进度条填充部分*/\n.progress__filled {\n  width:50%;\n  background:#ffc600; /*橘黄色的进度条填充部分*/\n  flex:0;\n  flex-basis:0%;\n  /*transition: flex-basis 0.22s linear;*/\n}\n\n/* unholy css to style input type=\"range\" */\n/*主要是滑动条的自定义样式设置*/\n\n/*去除系统默认的滑动条样式，主要针对基于webkit的浏览器*/\ninput[type=range] {\n  -webkit-appearance: none;\n  background:transparent;\n  width: 100%;\n  margin: 0 5px;\n}\n/*原始的控件获取到焦点时，会显示包裹整个控件的边框，所以还需要把边框取消。*/\ninput[type=range]:focus {\n  outline: none;\n}\n\n/*开始自定义滑动条控件的样式*/\n\n/*自定义滑动控件的轨道*/\ninput[type=range]::-webkit-slider-runnable-track {\n  width: 100%;\n  height: 8.4px;\n  cursor: pointer;\n  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);\n  background: rgba(255,255,255,0.8);  /*滑动条轨道背景颜色*/\n  border-radius: 3.3px;\n  border: 0.2px solid rgba(1, 1, 1, 0);\n}\n/*自定义滑动控件的滑块*/\ninput[type=range]::-webkit-slider-thumb {\n  -webkit-appearance: none;\n  height: 15px;\n  width: 15px;\n  box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0);\n  border-radius: 50px;\n  background: #ffc600; /*和进度条的填充部分一个颜色*/\n  cursor: pointer;\n  margin-top: -3.5px;\n  box-shadow:0 0 2px rgba(0,0,0,0.2);\n}\n/*自定义滑动条获得焦点时的背景颜色*/\ninput[type=range]:focus::-webkit-slider-runnable-track {\n  background: #bada55;\n}\n\n/*兼容Firefox浏览器*/\ninput[type=range]::-moz-range-track {\n  width: 100%;\n  height: 8.4px;\n  cursor: pointer;\n  box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0);\n  background: #ffffff;\n  border-radius: 3.3px;\n  border: 0.2px solid rgba(1, 1, 1, 0);\n}\ninput[type=range]::-moz-range-thumb {\n  box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0);\n  height: 15px;\n  width: 15px;\n  border-radius: 50px;\n  background: #ffc600;\n  cursor: pointer;\n}\n/*flex-basis 属性用于设置或检索弹性盒伸缩基准值\n    一个长度单位或者一个百分比，规定灵活项目的初始长度。\nauto    默认值。长度等于灵活项目的长度。如果该项目未指定长度，则长度将根据内容决定。\n"
  },
  {
    "path": "12 - 键盘输入序列的验证指南/README.md",
    "content": "### Day12 - 键盘输入序列的验证指南\n\n#### 项目效果\n\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/12%20-%20%E9%94%AE%E7%9B%98%E8%BE%93%E5%85%A5%E5%BA%8F%E5%88%97%E7%9A%84%E9%AA%8C%E8%AF%81%E6%8C%87%E5%8D%97/GIF.gif)\n\n文档里提供了一个 `script` 标签，供我们从 [Cornify.com](https://www.cornify.com/) 加载一个 JS 文件，调用其中的 `cornify_add()` 方法时，会在页面中追加 `p` 标签，并在 DOM 中插入一个图标。Cornify 的具体效果如上所示。  \n#### 实现功能：  \n- 可以在输入区输入内容，且内容长度为5，在匹配区中显示输入内容，长度为4,当匹配到暗码字段时，会调用`cornify_add()`,在页面中添加一个独角兽或者彩虹特效。\n\n#### 解决思路\n1. 指定可激发特效的字符串\n2. 监测字符串变化\n3. 事件监听\n4. 正则表达式判断字符串输入 \n5. 处理输入，在符合条件时，调用 `cornify_add()`\n\n#### 代码分析\n1、获取元素和事件监听\n```\n        let input=document.querySelector(\".text\");\n        let show= document.querySelector(\".show\");\n        input.addEventListener('keyup',debounce(handle,300));\n```\n2、对键盘输入事件进行防抖处理，避免过度调用回调函数\n```\n        // 防抖处理\n        function debounce(func,wait){\n            let timeflag;  \n            return function(){\n                clearTimeout(timeflag); //清除300ms之内之前触发的定时器。\n                let arg=arguments;\n                let _this = this;\n                timeflag = setTimeout(func.bind(_this,arg),wait);\n            }\n        }\n```\n> 防抖的主要思路：用一个函数对回调函数和等待时间进行包装，这个包装函数需要在等待时间到达后返回这个回调函数，这里可以使用setTimeout函数实现，需要注意的是需要设置一个时间变量来保存定时器，在wait等待时间之内再次触发监听事件，说明上一个的定时器还存在，需要将它清除。这里的防抖代码解决了this指向和event队象的问题，如果对这块不了解，详解见https://github.com/mqyqingfeng/Blog/issues/22    \n\n3、回调函数部分\n```\n        //回调函数\n        function handle(){\n            input.value=input.value.slice(-5);\n            show.textContent=input.value.slice(-4);\n            let regexp = /love/gi;\n            if(regexp.test(show.textContent)){\n                cornify_add();//这个方法是由通过script引入的cornify.com中的cornify.js提供的。\n            }\n        }\n```\n\n\n"
  },
  {
    "path": "12 - 键盘输入序列的验证指南/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>key sequence detection</title>\n    <style>\n        html,body{\n            margin:0;\n            padding:0;\n        }\n        .contain{\n            width:200px;\n            height: 200px;\n            font-size:25px;\n            text-align: center;\n            position: absolute;\n            left:50%;\n            top:50%;\n            transform:translate(-50%,-50%);\n            z-index:30;\n        }\n        .alertInfo,.resultInfo{\n            color:green;\n            font-weight: bold;\n        }\n        .text{\n            width:100px;\n            outline:none;\n            text-align: center;\n            font-size:25px;\n            color:blue;\n            /*border:none;*/\n        }\n        .show{\n            font-size:25px;\n            color:red;\n        }\n    </style>\n</head>\n<body>\n    <div class=\"contain\">\n        <p class=\"alertInfo\">暗码:love</p>\n        <input type=\"text\" class=\"text\" placeholder=\"输入区\">\n        <p class=\"resultInfo\">匹配显示区:</p>\n        <p class=\"show\"></p>\n    </div>\n    <script type=\"text/javascript\" src=\"http://www.cornify.com/js/cornify.js\"></script>\n\n    <script>\n        let input=document.querySelector(\".text\");\n        let show= document.querySelector(\".show\");\n        input.addEventListener('keyup',debounce(handle,300));\n\n        // 防抖处理\n        function debounce(func,wait){\n            let timeflag;\n            return function(){\n                clearTimeout(timeflag); //清除300ms之内之前触发的定时器。\n                let arg=arguments;\n                let _this = this;\n                timeflag = setTimeout(func.bind(_this,arg),wait);\n            }\n        }\n        //回调函数\n        function handle(){\n            input.value=input.value.slice(-5);\n            show.textContent=input.value.slice(-4);\n            let regexp = /love/gi;\n            if(regexp.test(show.textContent)){\n                cornify_add();//这个方法是由通过script引入的cornify.com中的cornify.js提供的。\n            }\n        }\n    </script>\n</body>\n</html>"
  },
  {
    "path": "13 - 图片随屏幕滚动而滑入滑出的效果/README.md",
    "content": "### Day13 - 图片随屏幕滚动而滑入滑出的效果\n#### 项目效果\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/13%20-%20%E5%9B%BE%E7%89%87%E9%9A%8F%E5%B1%8F%E5%B9%95%E6%BB%9A%E5%8A%A8%E8%80%8C%E6%BB%91%E5%85%A5%E6%BB%91%E5%87%BA%E7%9A%84%E6%95%88%E6%9E%9C/demoshow/GIF.gif)  \n实现页面内伴随着鼠标滚动，到每个图片时图片出现，并伴随着动画出现。\n#### 实现思路\n- 1、判断图片的滑出和滑入的位置（JS实现）  \n- 2、根据位置来决定图片的过渡效果（CSS实现）\n### 代码解析\n#### JS部分\n```JS\n\tlet imgs=Array.from(document.querySelectorAll(\"img\"));//转换为真正的数组\n        window.addEventListener(\"scroll\",throttle(handle,100));\n        // 节流函数（定时器实现方式）\n        function throttle(func,wait){\n            let timeflag;\n            return function(){\n                let content=this;\n                let args=arguments;\n                if(!timeflag){\n                    timeflag=setTimeout(function(){\n                        timeflag=null;\n                        func.apply(content,args)\n                    },wait);\n                }\n            }\n\n        }\n        // 事件处理函数，每2秒执行一次，停止后也会执行一次。（无首有尾）\n        // 判断图片位置的函数，遍历每一个图片，判断每一个图片是否出现在视图中，来决定是否加类。\n        function handle(){\n          imgs.forEach(img=>{\n            let imgHalfBoolean=(window.scrollY+window.innerHeight)>img.offsetTop+img.height/2;//图片划过一半的判断条件\n            let imgFullBoolean=window.scrollY<img.offsetTop+img.height;//图片没有完全滑入顶部的判断条件\n            // 图片滑出一半且没有完全滑出时，加上过渡类\n            if(imgHalfBoolean&&imgFullBoolean){\n              img.classList.add(\"active\");\n            }else{\n              img.classList.remove(\"active\");\n            }\n          })\n        }\n```\n##### 代码知识点分析  \n- 1、节流处理： 此外由于每次滚动都触发监听事件，会降低 JavaScript 运行性能，所以用 throttle 函数来降低触发的次数。  \n- 2、图片位置的确定：主要通过四个距离值  \n(1)window.scrollY:页面滚动过的距离。  \n(2)window.innerHeight:浏览器的视距，不包括上下工具栏。   \n(3)img.offsetTop:图片相对于父元素的距离。  \n(4)img.height:图片自身的高度值。  \n详细的解释如下图：  \n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/13%20-%20%E5%9B%BE%E7%89%87%E9%9A%8F%E5%B1%8F%E5%B9%95%E6%BB%9A%E5%8A%A8%E8%80%8C%E6%BB%91%E5%85%A5%E6%BB%91%E5%87%BA%E7%9A%84%E6%95%88%E6%9E%9C/demoshow/demo.PNG)\n```JS\n\tlet imgHalfBoolean=(window.scrollY+window.innerHeight)>img.offsetTop+img.height/2;//图片划过一半的判断条件\n        let imgFullBoolean=window.scrollY<img.offsetTop+img.height;//图片没有完全滑入顶部的判断条件\n```\n其中橙色半透明部分指可滚动页面整体，橙色标注部分是指会随着页面滚动而变化的尺寸，黑色标注的尺寸是固定不变的。 页面的滑动过程经过了两个临界点，一个是下滑到图片的一半处，另一个是完全滑过图片使图片已不再视窗之内，分别决定了图片的显示和隐藏。\n#### CSS部分\n```CSS\nhtml{\n    box-sizing:border-box;\n    background: linear-gradient(silver,#ffc600);\n    font-family: 'helvetica neue';\n    font-size:20px;\n    font-weight: 200;\n}\nbody{\n    padding:0;\n    margin:0;\n}\n*,*:before,*:after{\n    box-sizing:inherit;\n}\nh1{\n    margin-top:0;\n}\n.site-wrap{\n    max-width:700px;\n    margin:100px auto;\n    background: whitesmoke;\n    padding:40px;\n    /*text-align:justify;text-align 属性规定元素中的文本的水平对齐方式。*/\n}\np{\n    text-indent:2em; /*text-indent 属性规定文本块中首行文本的缩进。*/\n    word-break:break-all;\n}\n/* 图片的位置设置 */\n.align-left{\n    float:left;\n    margin-right:30px;\n}\n.align-right{\n    float:right;\n    margin-left:30px;\n}\n/* 所有的图片处于隐藏状态，并将所有的属性改变设置为0.5s的过渡效果 */\n.slide-in {\n  opacity:0;\n  transition:all .1s;\n}\n/* 位于左侧和右侧图片的额初始位置和大小状态 */\n.align-left.slide-in{\n  transform:translateX(-30%) scale(0.9);\n}\n.align-right.slide-in{\n  transform: translate(30%) scale(0.9);\n}\n/* 具有active类的图片效果 */\n.slide-in.active{\n  opacity: 1;\n  transform: translate(0%) scale(1);\n}\n```\n"
  },
  {
    "path": "13 - 图片随屏幕滚动而滑入滑出的效果/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Document</title>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n    \n  <div class=\"site-wrap\">\n\n    <h1>Slide In On Scroll</h1>\n\n    <p>Consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem\n      dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora\n      in aspernatur pariaturlores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut\n      asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt\n      esse magni, ut, dignissimos.</p>\n    <p>Adipisicing elit. Tempore tempora rerum..</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n\n    <img src=\"http://unsplash.it/400/400\" class=\"align-left slide-in\">\n\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptates, deserunt facilis et iste corrupti omnis tenetur\n      est. Iste ut est dicta dolor itaque adipisci, dolorum minima, veritatis earum provident error molestias. Ratione magni\n      illo sint vel velit ut excepturi consectetur suscipit, earum modi accusamus voluptatem nostrum, praesentium numquam,\n      reiciendis voluptas sit id quisquam. Consequatur in quis reprehenderit modi perspiciatis necessitatibus saepe, quidem,\n      suscipit iure natus dignissimos ipsam, eligendi deleniti accusantium, rerum quibusdam fugit perferendis et optio recusandae\n      sed ratione. Culpa, dolorum reprehenderit harum ab voluptas fuga, nisi eligendi natus maiores illum quas quos et aperiam\n      aut doloremque optio maxime fugiat doloribus. Eum dolorum expedita quam, nesciunt</p>\n\n    <img src=\"http://unsplash.it/400/401\" class=\"align-right slide-in\">\n\n    <p> at provident praesentium atque quas rerum optio dignissimos repudiandae ullam illum quibusdam. Vel ad error quibusdam,\n      illo ex totam placeat. Quos excepturi fuga, molestiae ea quisquam minus, ratione dicta consectetur officia omnis, doloribus\n      voluptatibus? Veniam ipsum veritatis architecto, provident quas consequatur doloremque quam quidem earum expedita,\n      ad delectus voluptatum, omnis praesentium nostrum qui aspernatur ea eaque adipisci et cumque ab? Ea voluptatum dolore\n      itaque odio. Eius minima distinctio harum, officia ab nihil exercitationem. Tempora rem nemo nam temporibus molestias\n      facilis minus ipsam quam doloribus consequatur debitis nesciunt tempore officiis aperiam quisquam, molestiae voluptates\n      cum, fuga culpa. Distinctio accusamus quibusdam, tempore perspiciatis dolorum optio facere consequatur quidem ullam\n      beatae architecto, ipsam sequi officiis dignissimos amet impedit natus necessitatibus tenetur repellendus dolor rem!\n      Dicta dolorem, iure, facilis illo ex nihil ipsa amet officia, optio temporibus eum autem odit repellendus nisi. Possimus\n      modi, corrupti error debitis doloribus dicta libero earum, sequi porro ut excepturi nostrum ea voluptatem nihil culpa?\n      Ullam expedita eligendi obcaecati reiciendis velit provident omnis quas qui in corrupti est dolore facere ad hic, animi\n      soluta assumenda consequuntur reprehenderit! Voluptate dolor nihil veniam laborum voluptas nisi pariatur sed optio\n      accusantium quam consectetur, corrupti, sequi et consequuntur, excepturi doloremque. Tempore quis velit corporis neque\n      fugit non sequi eaque rem hic. Facere, inventore, aspernatur. Accusantium modi atque, asperiores qui nobis soluta cumque\n      suscipit excepturi possimus doloremque odit saepe perferendis temporibus molestiae nostrum voluptatum quis id sint\n      quidem nesciunt culpa. Rerum labore dolor beatae blanditiis praesentium explicabo velit optio esse aperiam similique,\n      voluptatem cum, maiores ipsa tempore. Reiciendis sed culpa atque inventore, nam ullam enim expedita consectetur id\n      velit iusto alias vitae explicabo nemo neque odio reprehenderit soluta sint eaque. Aperiam, qui ut tenetur, voluptate\n      doloremque officiis dicta quaerat voluptatem rerum natus magni. Eum amet autem dolor ullam.</p>\n\n    <img src=\"http://unsplash.it/200/500\" class=\"align-left slide-in\">\n\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero\n      placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero\n      perferendis, deserunt et incidunt eveniet <img src=\"http://unsplash.it/200/200\" class=\"align-right slide-in\"> temporibus\n      doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt\n      laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos\n      distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus\n      atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus\n      aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam\n      est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui\n      rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis</p>\n    <p>laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit\n      ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic\n      quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa\n      debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt\n      eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet,\n      provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis\n      iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus\n      distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas\n      odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate\n      saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere\n      ducimus accusantium eos veritatis neque.</p>\n\n    <img src=\"http://unsplash.it/400/400\" class=\"align-right slide-in\">\n\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero\n      placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero\n      perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore!\n      Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore\n      iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium,\n      rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel\n      non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita\n      in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi.\n      Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione.\n      Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita,\n      laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda\n      natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos\n      dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro\n      saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo,\n      ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus\n      quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores\n      quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam,\n      quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus,\n      odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore\n      non magnam, amet, facere ducimus accusantium eos veritatis neque.</p>\n\n      <script src=\"index.js\"></script>\n</body>\n</html>\n"
  },
  {
    "path": "13 - 图片随屏幕滚动而滑入滑出的效果/index.js",
    "content": "let imgs=Array.from(document.querySelectorAll(\"img\"));//转换为真正的数组\n        // imgs.forEach(item=>item.addEventListener(\"scroll\",handle));\n        window.addEventListener(\"scroll\",throttle(handle,100));\n        // 节流函数（定时器实现方式）\n        function throttle(func,wait){\n            let timeflag;\n            return function(){\n                let content=this;\n                let args=arguments;\n                if(!timeflag){\n                    timeflag=setTimeout(function(){\n                        timeflag=null;\n                        func.apply(content,args)\n                    },wait);\n                }\n            }\n\n        }\n        // 事件处理函数，每2秒执行一次，停止后也会执行一次。（无首有尾）\n        // 判断图片位置的函数，遍历每一个图片，判断每一个图片是否出现在视图中，来决定是否加类。\n        function handle(){\n          imgs.forEach(img=>{\n            let imgHalfBoolean=(window.scrollY+window.innerHeight)>img.offsetTop+img.height/2;//图片划过一半的判断条件\n            let imgFullBoolean=window.scrollY<img.offsetTop+img.height;//图片没有完全滑入顶部的判断条件\n            // 图片滑出一半且没有完全滑出时，加上过渡类\n            if(imgHalfBoolean&&imgFullBoolean){\n              img.classList.add(\"active\");\n            }else{\n              img.classList.remove(\"active\");\n            }\n          })\n        }"
  },
  {
    "path": "13 - 图片随屏幕滚动而滑入滑出的效果/style.css",
    "content": "html{\n    box-sizing:border-box;\n    background: linear-gradient(silver,#ffc600);\n    font-family: 'helvetica neue';\n    font-size:20px;\n    font-weight: 200;\n}\nbody{\n    padding:0;\n    margin:0;\n}\n*,*:before,*:after{\n    box-sizing:inherit;\n}\nh1{\n    margin-top:0;\n}\n.site-wrap{\n    max-width:700px;\n    margin:100px auto;\n    background: whitesmoke;\n    padding:40px;\n    /*text-align:justify;text-align 属性规定元素中的文本的水平对齐方式。*/\n}\np{\n    text-indent:2em; /*text-indent 属性规定文本块中首行文本的缩进。*/\n    word-break:break-all;\n}\n/* 图片的位置设置 */\n.align-left{\n    float:left;\n    margin-right:30px;\n}\n.align-right{\n    float:right;\n    margin-left:30px;\n}\n/* 所有的图片处于隐藏状态，并将所有的属性改变设置为0.5s的过渡效果 */\n.slide-in {\n  opacity:0;\n  transition:all .1s;\n}\n/* 位于左侧和右侧图片的额初始位置和大小状态 */\n.align-left.slide-in{\n  transform:translateX(-30%) scale(0.9);\n}\n.align-right.slide-in{\n  transform: translate(30%) scale(0.9);\n}\n/* 具有active类的图片效果 */\n.slide-in.active{\n  opacity: 1;\n  transform: translate(0%) scale(1);\n}"
  },
  {
    "path": "14 - JavaScript 引用和值拷贝/README.md",
    "content": "### Day14 - JavaScript 引用和值拷贝\n\n#### 项目效果\n效果展示如下面的图片，另外也可以自行在浏览器的控制台中打印显示进行理解。\n\n#### 按值操作（深拷贝）\n\n基本类型由值操作。以下类型在JavaScript中被视为基本类型：\n\n- `String`\n\n- `Number`\n\n- `Boolean`\n\n- `Null`\n\n- `Undefined`\n\n基本数据类型赋值你可以理解成值拷贝，从深拷贝和浅拷贝的角度去思考的话，你可以理解成`深拷贝`，当你修改一个变量的值时，不会影响其他变量的值。\n\n##### 实例\n\n```Javascript\nlet age1 = 100; //number类型\nlet age2 = age1; //值的赋值属于深拷贝\nconsole.log(age1, age2);  //100 100\nage1 = 200;\nconsole.log(age1, age2);  //200 100 \n```\n> 值的复制：是给另外一个变量创建了一个存储空间，二者彼此独立，修改数据互不影响。  \n由此可见，基本类型，按值操作，新建的变量会将值复制给新的变量，各自的改变不会互相影响。\n\n#### 通过引用操作 （浅拷贝）\n\n对象`Object`类型是按引用操作的，如果它不是基本类型中的一个，那么它就是对象，这里如果我们细究的话，JavaScript中每一个东西都可以当做对象，甚至是基本的类型（不包括`null`和`undefined`），但我们尽量不要钻这个牛角尖。\n\n一些JavaScript中的对象：\n\n`Object`\n\n`Function`\n\n`Array`\n\n`Set`\n\n`Map`\n\n那对于数组来说，情况是否一样呢？延续上面的思路,下面我们来看看数组。  \n```JS\nconst players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];\nconst team = players;\nconsole.log(players, team); // [\"Wes\", \"Sarah\", \"Ryan\", \"Poppy\"] [\"Wes\", \"Sarah\", \"Ryan\", \"Poppy\"]\nteam[3] = 'Lux';\nconsole.log(players, team); // [\"Wes\", \"Sarah\", \"Ryan\", \"Lux\"] [\"Wes\", \"Sarah\", \"Ryan\", \"Lux\"]\n```\n> 结果显示原数组 plaryers 也被修改了。为什么会这样？因为 team 只是这个数组的引用，并不是它的复制。team 和 players 这两个变量指向的是同一个数组，也即是仅仅浅拷贝了指针，这个变量存储的指向原来数组存储空间的方向，两个变量指向的内容是一样的，这样修改其中一个另一个也会跟着改变。   \n\n\n#### 数组复制的解决方法 （数组深拷贝）：\n- 方法一 Array.prototype.slice()  \n\n由于运行 slice ，原数组不会被修改。所以如果修改这两个数组中任意 一个，另一个都不会受到影响。\n```JS\nconst players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];\nconst team2 = players.slice();\nteam2[3] = 'Lux2';\nconsole.log(players, team2);  // [\"Wes\", \"Sarah\", \"Ryan\", \"Poppy\"] [\"Wes\", \"Sarah\", \"Ryan\", \"Lux2\"]\n```\n- 方法二 Array.prototype.concat()\n\nconcat() 方法是用来合并数组的，它也不会更改原有的数组，而是返回一个新数组，所以可以将 players 数组与一个空数组合并，得到的结果就符合预期了。\n```JS\nconst players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];\nconst team3 = [].concat(players); //或者写成 team3 = players.concat();\nteam3[3] = 'Lux3';\nconsole.log(players, team3);  // [\"Wes\", \"Sarah\", \"Ryan\", \"Poppy\"] [\"Wes\", \"Sarah\", \"Ryan\", \"Lux3\"]\n```\n- 方法三 ES6 扩展运算符\n\n扩展语法可以像扩展参数列表一样来扩展数组，效果与上述方法类似，但比较简洁。\n```JS\nconst players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];\nconst team4 = [...players]; //扩展运算符这里实际上是“打散操作”\nteam4[3] = 'Lux4';\nconsole.log(players, team4);  //[\"Wes\", \"Sarah\", \"Ryan\", \"Poppy\"] [\"Wes\", \"Sarah\", \"Ryan\", \"Lux4\"]\n```\n- 方法四 Array.from()\nArray.from() 方法从一个类似数组或可迭代对象中创建一个新的数组实例。  \n```JS\nconst players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];\nconst team5 = Array.from(players);\nteam4[3] = 'Lux5';\nconsole.log(players, team4);  //[\"Wes\", \"Sarah\", \"Ryan\", \"Poppy\"] [\"Wes\", \"Sarah\", \"Ryan\", \"Lux5\"]\n```\n#### object对象的复制方法（对象深拷贝）\n\n对于 Object 数据，我们用一个 person 对象来试试。\n\n先声明对象：\n```JS\nconst person = {\n   name: 'Web sun',\n   age: 25\n };\n ```\n然后思考一下如何可以取得它的复制，试试想当然的做法：\n```JS\nconst captain = person;\ncaptain.number = 99;\nconsole.log(person, captain);\n// Object {name: \"Web sun\", age: 25, number: 99} \n// Object {name: \"Web sun\", age: 25, number: 99}\n```\n这样好像行不通，person 的值也被更改了，那该如何才能真正复制呢,来达到两个变量之间互不影响？\n\n- 方法一 ES6的Object.assign()\n`Object.assign(target, ...sources)`用于对象的合并，将源对象中的所有可枚举属性，复制到目标对象中，并返回合并后的目标对象。后来的源对象的属性值，将会覆盖它之前的对象的属性。    \n```JS\nvar obj={\n   name:'sun',\n   height:'180cm'\n }\n var copyobj=Object.assign({},obj);\n copyobj.name='zhao';\n console.log(obj); //{name: \"sun\", height: \"180cm\"}\n console.log(copyobj); //{name: \"zhao\", height: \"180cm\"}\n```\n- 方法二 ES6的扩展运算符\n用于取出参数对象的所有可遍历属性，拷贝到当前属性中。  \n```JS\nvar obj={\n   name:'sun',\n   height:180cm\n }\n var copyobj={...obj};\n copyobj.name='zhao';\n console.log(obj); //{name: \"sun\", height: \"180cm\"}\n console.log(copyobj); //{name: \"zhao\", height: \"180cm\"}\n```\n#### 注意：上述对数组深拷贝的方法仅仅适用第一层级的值是基本数据类型的情况。若第一层级的值为对象或者数组等引用类型时，则上述方法失效。对象中若仍嵌套有对象，同样失效。\n## 拷贝所有层级\n##### 1.不仅拷贝第一层级，还能拷贝数组或对象所有层级的各项值。  \n##### 2.不是单独针对数组或对象，而是能够通用与数组、对象和其他复杂的JSON形式的对象。\n- 方法一 `JSON.parse(JSON.stringify(xxx))`\n```JS\nvar array=[{number:1},{number:2},{number:3}];\nvar copyArray=JSON.parse(JSON.stringify(array));\ncopyArray[0].number=100;\nconsole.log(array);//[{number:1},{number:2},{number:3}];\nconsole.log(copyArray); //[{number:100},{number:2},{number:3}];\n```\n> 序列化：将一个JavaScript值（数组或对象）转换为一个JSON字符串；JSON.stringify()   \n反序列化：将一个JSON字符串转换为对象；JSON.parse()\n- 方法二 深拷贝递归函数\n```JS\nfunction deepcopy(obj){\n   if(typeof obj == 'object') { //说明参数是一个对象类型，但是数组也是对象，需要具体判断是不是数组类型。\n      var result = obj.constructor==Array ? [] : {};\n      for(let i in obj){ //遍历obj的每一项\n          result[i]=typeof obj[i]==\"object\" ? deepcopy(obj[i]) : obj[i];\n      }\n   }else{\n      var result = obj;\n   }\n   return result;\n}\n```\n"
  },
  {
    "path": "14 - JavaScript 引用和值拷贝/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>JS Reference VS Copy</title>\n</head>\n\n<body>\n\n  <script>\n    /*\n        基本数据类型:\n        1.String\n        2.Number\n        3.Boolean\n        4.Null\n        5.Undefined\n    */\n    let age = 100;\n    let age2 = age;\n    console.log(age, age2);\n    age = 200;\n    console.log(age, age2);\n\n    let name = 'liyuechun';\n    let name2 = name;\n    console.log(name, name2);\n    name = 'liyc';\n    console.log(name, name2);\n\n    /*\n      引用\n      1.Object\n      2.Function\n      3.Array\n      4.Set\n      5.Map\n    */\n    const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];\n\n    // 引用拷贝\n    const team = players;\n\n    console.log(`players: ${players}`, `team:${team}`);\n\n    // 我们做如下操作：\n    team[3] = 'Lux';\n\n    console.log(`players: ${players}`, `team:${team}`);\n\n    const team2 = players.slice();\n\n    console.log(`players: ${players}`, `team:${team}`, `team2:${team2}`);\n\n\n    /*\n    深拷贝：\n    */\n    // 创建新数组并且将原来的数组拼接到新数组中\n    const team3 = [].concat(players);\n\n    // ES6 Spread语法\n    const team4 = [...players];\n    team4[3] = 'heeee hawww';\n    console.log(`team4:${team4}`);\n\n    const team5 = Array.from(players);\n    console.log(`team5:${team5}`);\n\n\n    //创建object对象\n    const person = {\n      name: '黎跃春',\n      age: 29\n    };\n\n    // 浅拷贝\n    console.log(`person:${person}`);\n    const captain = person;\n    captain.number = 99;\n    console.log(`person:${person}`);\n    console.log(`captain:${captain}`);\n\n    // 深拷贝\n    const cap2 = Object.assign({}, person, {\n      number: 99,\n      age: 12\n    });\n    console.log(`cap2:${cap2}`);\n\n    // 对象的嵌套\n    const liyc = {\n      name: '黎跃春',\n      age: 100,\n      social: {\n        sina: '黎跃春-追时间的人',\n        facebook: '黎跃春'\n      }\n    };\n\n\n    console.log(`liyc:${liyc}`);\n\n    const dev = Object.assign({}, liyc);\n    console.log(`dev:${dev}`);\n\n    const dev2 = JSON.stringify(liyc);\n    console.log(`dev2:${dev2}`);\n\n    const dev3 = JSON.parse(JSON.stringify(liyc));\n    console.log(`dev3:${dev3}`);\n\n\n  </script>\n\n</body>\n\n</html>\n"
  },
  {
    "path": "14 - JavaScript 引用和值拷贝/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>JS Reference VS Copy</title>\n</head>\n<body>\n\n  <script>\n    // start with strings, numbers and booleans\n\n    // Let's say we have an array\n    const players = ['Wes', 'Sarah', 'Ryan', 'Poppy'];\n\n    // and we want to make a copy of it.\n\n    // You might think we can just do something like this:\n\n    // however what happens when we update that array?\n\n    // now here is the problem!\n\n    // oh no - we have edited the original array too!\n\n    // Why? It's because that is an array reference, not an array copy. They both point to the same array!\n\n    // So, how do we fix this? We take a copy instead!\n\n    // one way\n\n    // or create a new array and concat the old one in\n\n    // or use the new ES6 Spread\n\n    // now when we update it, the original one isn't changed\n\n    // The same thing goes for objects, let's say we have a person object\n\n    // with Objects\n\n    // and think we make a copy:\n\n    // how do we take a copy instead?\n\n    // We will hopefully soon see the object ...spread\n\n    // Things to note - this is only 1 level deep - both for Arrays and Objects. lodash has a cloneDeep method, but you should think twice before using it.\n\n  </script>\n\n</body>\n</html>\n"
  },
  {
    "path": "15 - LocalStorage/README.md",
    "content": "\n## 效果图\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/15%20-%20LocalStorage/GIF.gif)\n\n第十五天主要是练习`LocalStorage`（本地存储）以及事件委托的使用，使用场景是一个简单的`todo list`的应用，实现基本的添加`item`，切换完成状态，将所有`todo`项存储在`localstorage`中，保证刷新浏览器后数据不丢失。\n\n## 主要思路\n\n* 提前预定义好所有用到的变量；\n\n```Javascript\n// 添加item的按钮\nconst addItems = document.querySelector('.add-items'); \n// todolist列表\nconst itemsList = document.querySelector('.plates'); \n// 本地缓存的所有todoitem\nconst items = JSON.parse(localStorage.getItem('items')) || []; \n```\n* 为`addItems`按钮添加事件函数，添加一个新的`todo item`并存储到本地缓存；\n* 监听`checkbox`的点击事件，切换是否完成的状态，并更新本地存储，保证刷新本页面是数据不会丢失；\n* 分别设置两个监听器，监听`addItems`的`submit`事件，和`itemsList`的点击事件；\n\n## 添加item事件\n* 添加item的主要代码如下\n\n```Javascript\nfunction addItem(e) {\n  // 阻止默认事件的触发，防止在提交后页面自己刷新\n  e.preventDefault(); \n  const text = this.querySelector('[name=item]').value;\n  const item = {\n    // ES6的简写形式 => text: text;\n    text, \n    done: false\n  };\n  items.push(item);\n  localStorage.setItem('items', JSON.stringify(items));\n  populateList(items, itemsList);\n  // 添加完数据后，重置输入框 \n  this.reset();      \n}\naddItems.addEventListener('submit', addItem);\n```\n* 监听`addItems`的`submit`事件，当用户点击`enter`或者点击右侧的`submit`按钮的时候触发；\n* `text,`是ES6的缩写形式，即代表`text: text；`\n*`localStorage`的常用API：\n\t* `localStorage.setItem(‘key’,value); ->` 设置本地缓存，以`key-value`的形式\n\t* `localStorage.getItem(‘key’); ->` 根据参数key取得本地缓存中对应的值\n\t* `localStorage.clear();  ->` 清空本地的缓存\n\t* `localStorage.removeItem(‘key’); ->` 删除key所对应的那一条本地缓存\n* `localStorage`中只能存储字符串，所以我们经常会用到： `JSON.stringify(object)`将一个对象转换为字符串，再使用`JSON.parse(objSting)`将一个对象字符串转换为对象\n* `this.reset();`代表将表单重置，清空表单中的值，在我们进行了一次submit之后，如果不重置表单的话，表单中的值将不会消失，这将大大影响用户体验\n\n## 切换完成状态事件\n\n```Javascript\nfunction toggleDone(e) {\n  // if(!e.target.nodeName.match('INPUT')) return;\n  \n  // 跳过所有的input，只处理label\n  if (!e.target.matches('input')) return; \n  const node = e.target;\n  const index = node.dataset.index;\n  items[index].done = !items[index].done;\n  localStorage.setItem('items', JSON.stringify(items));\n  populateList(items, itemsList);\n}\nitemsList.addEventListener('click', toggleDone);\n```\n* 此处使用到了事件委托，所谓事件委托，我是这么理解的：\n\t* 假设我们队一个input列表进行了事件监听，但我们如法保证，此列表在接下来的状态下是否进行了更新，刷新等改变原来节点的操作，如果有这样的操作出现，那么我们之前的事件监听器就无法再起到监听的作用；\n\t* 但我们可以对input列表的父元素进行事件监听，让它们的父元素处于监听状态，当我们所点击的元素是其子元素的话，就告诉它的子元素，执行相应的事件；\n\t* 相当于委托父元素帮我们监听所有子元素，这样无论子元素列表进行怎么样的更新，改变，只要父元素节点不发生改变就可以持续起到监听的 作用。\n\t* 通过`e.target.matches('input')`可以判断所点击的元素是不是input元素，`e.target`返回所点击的宿主元素。\n* 通过获取到所点击的列表的序号，更改其`done`属性，更新后进行存储，就可以实现完成状态的事件。\n\n## 列表显示函数\n\n```Javascript\n   // 设置默认值，防止传参数出错的时候crash\nfunction populateList(populates = [], place {   \n    place.innerHTML = populates.map((populate, index) => {\n    //之所以用‘’空字符是因为如果用null的话，会出现在html中\n    return `\n      <li>\n        <input type=\"checkbox\" id=item${index} data-index=${index} ${populate.done ? 'checked' : ''}>\n        <label for=\"item${index}\">${(populate.text)}</label>\n      </li>\n    `; \n    // join()之后一定要加''，表示空字符，否则会加入逗号，造成错误  \n  }).join(''); \n}\n```\n* 将所有的列表项转化为`li`传入页面的`html`中\n* 将此函数抽象出来，以方便以后实现同样类似的操作，将一个数组中的元素动态添加到页面的一个节点中\n\n## 清除缓存\n\n```Javascript\n    // 在关闭浏览器时或者刷新页面时清除缓存\n    window.onbeforeunload = function (e) {\n      localStorage.removeItem('items');\n      e.returnValue=true;  //弹出提示框阻止onunload事件的运行\n    };\n```\n> onbeforeunload 事件在即将离开当前页面（刷新或关闭）时触发。该事件可用于弹出对话框，提示用户是继续浏览页面还是离开当前页面。\n注意：当该事件返回的字符串（事前设置好的event.returnValue的值）不为null或者undefined时，弹出确认窗口让用户自行选择是否关闭当前页面。（换句话说就是使用event.returnValue可以阻止onunload卸载页面，因为它会出现一个提示窗口，让用户自己判断是否离开当前页）一些浏览器将该事件返回的字符串显示在弹出窗上。从Firefox 4、 Chrome 51、Opera 38 和Safari 9.1开始，通用确认信息代替事件返回的字符串。\n\n* 有些时候，我们仅仅是为了练习`localStorage`的使用，并不想在浏览器中留下过多的缓存，那么这个方法就派上了用场。\n* 当页面重新刷新或者关闭之前，执行`localStorage.removeItem('items’);`清除页面的缓存。\n* **慎用**，尤其在生产环境中。\n\n## 整体代码架构\n\n```Javascript\nconst addItems = document.querySelector('.add-items');\nconst itemsList = document.querySelector('.plates');\nconst items = JSON.parse(localStorage.getItem('items')) || [];\n\nfunction addItem(e) {\n e.preventDefault();\n const text = this.querySelector('[name=item]').value;\n const item = {\n   text, // ES6的简写形式 => text = text;\n   done: false\n };\n items.push(item);\n localStorage.setItem('items', JSON.stringify(items));\n populateList(items, itemsList);\n this.reset(); // 添加完数据后，重置输入框      \n}\n\nfunction populateList(populates = [], place) { // 设置默认值，防止传参数出错的时候crash\n place.innerHTML = populates.map((populate, index) => {\n   return `\n     <li>\n       <input type=\"checkbox\" id=item${index} data-index=${index} ${populate.done ? 'checked' : ''}>\n       <label for=\"item${index}\">${(populate.text)}</label>\n     </li>\n   `; //之所以用‘’空字符是因为如果用null的话，会出现在html中\n }).join(''); // join()之后一定要加''，表示空字符，否则会加入逗号，造成错误  \n}\n\nfunction toggleDone(e) {\n // if(!e.target.nodeName.match('INPUT')) return;\n if (!e.target.matches('input')) return; // 跳过所有的input，只处理label\n const node = e.target;\n const index = node.dataset.index;\n items[index].done = !items[index].done;\n localStorage.setItem('items', JSON.stringify(items));\n populateList(items, itemsList);\n}\n\naddItems.addEventListener('submit', addItem);\nitemsList.addEventListener('click', toggleDone);\n\npopulateList(items, itemsList);\n```\n* 在页面加载的时候，先获取本地缓存的`items`，若存在就传给变量`items`，若第一次登录或者无`item`，初始化为空数组；\n* 在页面加载的时候先加载页面的所有`todolist`，执行一遍`populateList(items, itemsList);`函数即可。\n\n\n\n"
  },
  {
    "path": "15 - LocalStorage/demo.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n  <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n  <title>Document</title>\n</head>\n<body>\n  <div class=\"wrapper\">\n      <h2>LOCAL TAPAS</h2>\n      <p></p>\n      <ul class=\"plates\">\n        <li>Loading Tapasss...</li>\n      </ul>\n      <form class=\"add-items\">\n        <input type=\"text\" name=\"item\" placeholder=\"Item Name\" required>\n        <input type=\"submit\" value=\"+ Add Item\">\n      </form>\n    </div>\n  <script>\n    let Form=document.querySelector(\".add-items\");\n    let List=document.querySelector(\".plates\");\n    let itemsLocalStorage = JSON.parse(localStorage.getItem(\"items\")) || [];\n    Form.addEventListener(\"submit\",addItems);\n    function addItems(e){\n      e.preventDefault();\n      let item=this.querySelector(\"[name=item]\").value;\n      let contain={\n        text:item,\n        done:false\n      }\n      itemsLocalStorage.push(contain);\n      localStorage.setItem(\"items\",JSON.stringify(itemsLocalStorage));\n      addList(itemsLocalStorage,List);\n      this.reset();\n    // 将localStorage中保存条目渲染到展示列表中。\n    function addList(arr,place){\n      place.innerHTML = arr.map((item,index)=>{\n        return `\n          <li>\n            <input type=\"checkbox\" id=${index} ${item.done ? checked : false}></input>\n            <label for=${index}>${item.text}</label>\n          </li>\n          `\n      }).join(\"\");\n    \n    }\n    }\n\n  </script>\n</body>\n</html>"
  },
  {
    "path": "15 - LocalStorage/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>LocalStorage</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n  <!--\n      Fish SVG Cred:\n      https://thenounproject.com/search/?q=fish&i=589236\n   -->\n\n  <svg xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" x=\"0px\" y=\"0px\" viewBox=\"0 0 512 512\"\n    enable-background=\"new 0 0 512 512\" xml:space=\"preserve\">\n    <g>\n      <path d=\"M495.9,425.3H16.1c-5.2,0-10.1,2.9-12.5,7.6c-2.4,4.7-2.1,10.3,0.9,14.6l39,56.4c2.6,3.8,7,6.1,11.6,6.1h401.7   c4.6,0,9-2.3,11.6-6.1l39-56.4c3-4.3,3.3-9.9,0.9-14.6C506,428.2,501.1,425.3,495.9,425.3z M449.4,481.8H62.6L43,453.6H469   L449.4,481.8z\"\n      />\n      <path d=\"M158.3,122c7.8,0,14.1-6.3,14.1-14.1V43.4c0-7.8-6.3-14.1-14.1-14.1c-7.8,0-14.1,6.3-14.1,14.1v64.5   C144.2,115.7,150.5,122,158.3,122z\"\n      />\n      <path d=\"M245.1,94.7c7.8,0,14.1-6.3,14.1-14.1V16.1c0-7.8-6.3-14.1-14.1-14.1C237.3,2,231,8.3,231,16.1v64.5   C231,88.4,237.3,94.7,245.1,94.7z\"\n      />\n      <path d=\"M331.9,122c7.8,0,14.1-6.3,14.1-14.1V43.4c0-7.8-6.3-14.1-14.1-14.1s-14.1,6.3-14.1,14.1v64.5   C317.8,115.7,324.1,122,331.9,122z\"\n      />\n      <path d=\"M9.6,385.2c5.3,2.8,11.8,1.9,16.2-2.2l50.6-47.7c56.7,46.5,126.6,71.9,198.3,71.9c0,0,0,0,0,0   c87.5,0,169.7-36.6,231.4-103.2c5-5.4,5-13.8,0-19.2c-61.8-66.5-144-103.2-231.4-103.2c-72,0-142.2,25.6-199,72.5l-50-47.1   c-4.4-4.1-10.9-5-16.2-2.2c-5.3,2.8-8.3,8.7-7.4,14.6l11.6,75L2.2,370.6C1.3,376.5,4.2,382.4,9.6,385.2z M380.9,230.8   c34.9,14.3,67.2,35.7,95.3,63.6c-10.1,10-20.8,19.2-31.9,27.5c-22.4-3.3-29.6-8.8-30.7-9.7c-4-5.7-11.8-7.7-18.1-4.4   c-6.9,3.6-9.5,12.2-5.9,19.1c1.9,3.5,7.3,10.3,22.4,16c-10.1,5.7-20.5,10.7-31.1,15.1C352.4,320.2,352.4,268.6,380.9,230.8z    M36.3,255.6l29.4,27.7c5.3,5,13.6,5.1,19.1,0.3c53.2-47.6,120.7-73.7,190-73.7c26.9,0,53.2,3.9,78.5,11.3   c-29.3,44.6-29.3,102,0,146.6c-25.3,7.4-51.6,11.3-78.5,11.3c-69,0-136.3-26-189.4-73.2c-2.7-2.4-13.4-6.3-19.1,0.3l-30.1,28.3   l5.7-40C42.2,293,36.3,255.6,36.3,255.6z\"\n      />\n      <circle cx=\"398.8\" cy=\"273.8\" r=\"14.1\" />\n    </g>\n  </svg>\n\n  <div class=\"wrapper\">\n    <h2>LOCAL TAPAS</h2>\n    <p></p>\n    <ul class=\"plates\">\n      <li>Loading Tapasss...</li>\n    </ul>\n    <form class=\"add-items\">\n      <input type=\"text\" name=\"item\" placeholder=\"Item Name\" autocomplete=\"off\" required>\n      <input type=\"submit\" value=\"+ Add Item\">\n    </form>\n  </div>\n\n  <script>\n\n    // 在关闭浏览器之后清除缓存\n    window.onbeforeunload = function (e) {\n      localStorage.removeItem('items');\n      e.returnValue=false;\n    };\n    // window.onbeforeunload = function (event) {\n    //   var message = 'Important: Please click on \\'Save\\' button to leave this page.';\n    //   if (typeof event == 'undefined') {\n    //     event = window.event;\n    //   }\n    //   if (event) {\n    //     event.returnValue = message;\n    //   }\n    //   return message;\n    // };\n    const addItems = document.querySelector('.add-items'); //获取的是form表单\n    const itemsList = document.querySelector('.plates');  //获取ul无序列表\n    const items = JSON.parse(localStorage.getItem('items')) || []; //从localStorage中获取键名为items的值，并转换为json格式，若果取到则进行赋值，若果没有则赋值[]；\n    //增加列表项，然后添加到ul列表中，并更新到localStorage \n    function addItem(e) {\n      e.preventDefault();  //阻止submit默认的页面刷新行为。\n      const text = this.querySelector('[name=item]').value;   //获取输入框中的内容\n      const item = {                                         //对象容器存储输入框内容\n        text, // ES6的简写形式 => text = text;\n        done: false\n      };\n      items.push(item);\n      localStorage.setItem('items', JSON.stringify(items));\n      populateList(items, itemsList);\n      this.reset(); // 添加完数据后，重置输入框      \n    }\n// 将获取到的输入框内容添加到ul列表中\n    function populateList(populates = [], place) { // 设置默认值，防止传参数出错的时候crash\n      place.innerHTML = populates.map((populate, index) => {\n        return `\n          <li>\n            <input type=\"checkbox\" id=item${index} data-index=${index} ${populate.done ? 'checked' : ''}>\n            <label for=\"item${index}\">${(populate.text)}</label>\n          </li>\n        `; //之所以用‘’空字符是因为如果用null的话，会出现在html中\n      }).join(''); // join()之后一定要加''，表示空字符，否则会加入逗号，造成错误  \n    }\n// 更改列表项选中状态，更新到localStorage和界面显示\n    function toggleDone(e) {\n      // if(!e.target.nodeName.match('INPUT')) return;\n      if (e.target.matches('input')) return; // 跳过所有的input，只处理label\n      const node = e.target;\n      const index = node.dataset.index; //访问自定义属性\n      items[index].done = !items[index].done;\n      localStorage.setItem('items', JSON.stringify(items));\n      populateList(items, itemsList);   \n    }\n    // 主函数\n    addItems.addEventListener('submit', addItem); //form表单监听是否有submit提交发生\n    itemsList.addEventListener('click', toggleDone); //点击列表项触发toggleDone函数\n    populateList(items, itemsList);  //每次加载页面时，先将localStorage中保存的items内容加载到列表中显示出来。\n\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "15 - LocalStorage/style.css",
    "content": "html {\n    box-sizing: border-box;\n    background:url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat;\n    background-size:cover;\n    min-height:100vh;\n    display:flex;\n    justify-content: center;\n    align-items: center;\n    text-align: center;\n    font-family: Futura,\"Trebuchet MS\",Arial,sans-serif\n  }\n  *, *:before, *:after {box-sizing: inherit; }\n\n  svg {\n    fill:white;\n    background: rgba(0,0,0,0.1);\n    padding: 20px;\n    border-radius: 50%;\n    width:200px;\n    margin-bottom: 50px;\n  }\n\n  .wrapper {\n    padding: 20px;\n    max-width: 350px;\n    background: rgba(255,255,255,0.95);\n    box-shadow: 0 0 0 10px rgba(0,0,0,0.1);\n  }\n\n  h2 {\n    text-align: center;\n    margin: 0;\n    font-weight: 200;\n  }\n\n  .plates {\n    margin: 0;\n    padding: 0;\n    text-align: left;\n    list-style: none;\n  }\n\n  .plates li {\n    border-bottom: 1px solid rgba(0,0,0,0.2);\n    padding: 10px 0;\n    font-weight: 100;\n    display: flex;\n  }\n\n  .plates label {\n    flex:1;\n    cursor: pointer;\n\n  }\n\n  .plates input {\n    display: none;\n  }\n\n  .plates input + label:before {\n    content: '⬜️';\n    margin-right: 10px;\n  }\n\n  .plates input:checked + label:before {\n    content: '☑';\n  }\n\n  .add-items {\n    margin-top: 20px;\n  }\n\n  .add-items input {\n    padding:10px;\n    outline:0;\n    border:1px solid rgba(0,0,0,0.1);\n  }"
  },
  {
    "path": "16 - 移动鼠标让字体呈现彩虹效果/README.md",
    "content": "# Day16 - 鼠标移动让文字出现🌈效果中文指南\n\n## 效果图\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/16%20-%20%E7%A7%BB%E5%8A%A8%E9%BC%A0%E6%A0%87%E8%AE%A9%E5%AD%97%E4%BD%93%E5%91%88%E7%8E%B0%E5%BD%A9%E8%99%B9%E6%95%88%E6%9E%9C/show.PNG)\n\n鼠标移动时，元素的字体阴影随着鼠标移动的方向发生改变，达到字体阴影随着鼠标一起走的效果。\n## 基础知识\n#### text-shadow\n  `text-shadow: h-shadow v-shadow blur color; `\n`none`：无阴影\n`<length>`①：第1个长度值用来设置对象的阴影水平偏移值。可以为负值\n`<length>`②：第2个长度值用来设置对象的阴影垂直偏移值。可以为负值\n`<length>`③：如果提供了第3个长度值则用来设置对象的阴影模糊值。不允许负值\n`<color>`：设置对象的阴影的颜色。\n\n#### 解构赋值\n> [参考文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)\n\n解构赋值（destructuring assignment）语法是一个Javascript表达式，它使得从数组或者对象中提取数据赋值给不同的变量成为可能。\n\n```js\nlet a, b, rest;\n\n/* array 解构赋值 */\n[a, b] = [1, 2];\nconsole.log(a); // 1\nconsole.log(b); // 2\n\n[a, b, ...rest] = [1, 2, 3, 4, 5];\nconsole.log(a); // 1\nconsole.log(b); // 2\nconsole.log(rest); // [3, 4, 5]\n\n/* object 解构赋值:这里的小括号不能掉 */\n({a, b} = {a:1, b:2});\nconsole.log(a); // 1\nconsole.log(b); // 2\n\n/* object & ...rest 解构赋值 */\n({a, b, ...rest} = {a:1, b:2, c:3, d:4});\n// {a: 1, b: 2, c: 3, d: 4}\n\nrest;\n// {c: 3, d: 4}\n```\n#### MouseEvent\n\n![](http://om1c35wrq.bkt.clouddn.com/D6300A0F-CD68-4CE2-AEB7-22DF2CA6FF3F.png)\n[https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent)\n\n[clientX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) 设置或获取鼠标指针位置相对于当前窗口的 x 坐标，其中客户区域不包括窗口自身的控件和滚动条。  \n[clientY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY) 设置或获取鼠标指针位置相对于当前窗口的 y 坐标，其中客户区域不包括窗口自身的控件和滚动条。  \n[offsetX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX) 设置或获取鼠标指针位置相对于触发事件的对象的 x 坐标。  \n[offsetY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetY) 设置或获取鼠标指针位置相对于触发事件的对象的 y 坐标。 \n[screenX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenX) 设置或获取获取鼠标指针位置相对于用户屏幕的 x 坐标。  \n[screenY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenY) 设置或获取鼠标指针位置相对于用户屏幕的 y 坐标。  \n[x](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/x) 设置或获取鼠标指针位置相对于父文档的 x 像素坐标(亦即相对于当前窗口)。  \n[y](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/y) 设置或获取鼠标指针位置相对于父文档的 y 像素坐标(亦即相对于当前窗口)。  \n[pageX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX) 设置或获取指针位置相对于整个文档的x坐标  \n[pageY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY) 设置或获取指针位置相对于整个文档的y坐标  \n\n#### 页面元素offset的几个属性示例\n\n* HTMLElement.offsetParent：是一个只读属性，指向最近的包含该元素的定位元素.如果没有定位的元素，则 offsetParent 为最近的 table 元素对象或根元素（标准模式下为 html；quirks 模式下为 body）。当元素的 style.display 设置为 \"none\" 时，offsetParent 返回 null。\noffsetParent 很有用，因为 _offsetTop_ 和 _offsetLeft_ 都是相对于其内边距边界的。  \n* HTMLElement.offsetTop：指的是当前元素到其offsetParent指向元素的__上边距__的距离。    \n* HTMLElement.offsetLeft：指的是当前元素到其offsetParent指向元素的__左边距__的距离。  \n* HTMLElement.offsetHeight：指的是当前元素的__高度__，包含__content，padding，border__的高度值，但不包括__margin__的值。  \n* HTMLElement.offsetWidth：指的是当前元素的__宽度__，包含__content，padding，border__的高度值，但不包括__margin__的值。  \n\n## js代码\n\n```javascript\nconst hero = document.querySelector('.hero');\nconst text = hero.querySelector('h1');\nconst walk = 40;  // 鼠标左右移动共移动的距离\n\nfunction draw(e){\n  const { offsetWidth: width, offsetHeight: height} = hero;\n  let { offsetX: x, offsetY: y} = e;\n\n  // 使鼠标移动到中间元素上，x、y的值连续变化\n  if(e.target !== this){\n  // if(e.target == text){\n    x = x + e.target.offsetLeft;\n    y = y + e.target.offsetTop;\n  }\n  // const xaisx = (x/width*walk)-(walk/2);\n  // const yaisx = (y/height*walk)-(walk/2);\n  const xaisx = Math.floor((x/width*walk)-(walk/2));\n  const yaisx = Math.floor((y/height*walk)-(walk/2));\n  text.style.textShadow = `\n    ${xaisx}px ${yaisx * -1}px 2px rgba(0,255,0,0.7),\n    ${xaisx * -1}px ${yaisx}px 2px rgba(255,0,0,0.7),\n    ${yaisx}px ${xaisx * -1}px 2px rgba(188,188,188,0.7),\n    ${yaisx * -1}px ${xaisx}px 2px rgba(0,0,255,0.7)      \n    `; // 多写几个就有了霓虹灯的效果\n}\nhero.addEventListener('mousemove',draw);\n```\n* 分别获取到鼠标所在位置相对于页面左侧和顶端的距离，将这两个距离映射为自己想要移动的距离上（`walk`）；\n* 其中当鼠标移动中间的文字上的时候，由于`e.target`变化了，所以造成x的值不连续，因此需要监测`e.target`的值，判断是否指在了文字上；\n* 为元素设置字体阴影，text-shadow样式，也可以设置多个，达到类似霓虹灯的效果；\n* 对元素添加`mousemove`事件。\n\n\n"
  },
  {
    "path": "16 - 移动鼠标让字体呈现彩虹效果/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Mouse Shadow</title>\n</head>\n\n<body>\n\n  <div class=\"hero\">\n    <h1 contenteditable>🔥liyc1215</h1>\n  </div>\n\n  <style>\n    html {\n      color: black;\n      font-family: sans-serif;\n    }\n\n    .hero {\n      min-height: 100vh;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      color: orange;\n    }\n\n    h1 {\n      text-shadow: 10px 10px 0 rgba(0, 0, 0, 1);\n      font-size: 100px;\n    }\n  </style>\n\n  <script>\n    const hero = document.querySelector('.hero');\n    const text = hero.querySelector('h1');\n    const walk = 40; // 鼠标左右移动共移动的距离\n\n    function draw(e) {\n      const {\n        offsetWidth: width,\n        offsetHeight: height\n      } = hero;\n      let {\n        offsetX: x,\n        offsetY: y\n      } = e;\n\n      // 使鼠标移动到中间元素上，x、y的值连续变化\n      if (e.target !== this) {\n        // if(e.target == text){\n        x = x + e.target.offsetLeft;\n        y = y + e.target.offsetTop;\n      }\n      // const xaisx = (x/width*walk)-(walk/2);\n      // const yaisx = (y/height*walk)-(walk/2);\n      const xaisx = Math.floor((x / width * walk) - (walk / 2));\n      const yaisx = Math.floor((y / height * walk) - (walk / 2));\n      text.style.textShadow =\n        `\n      ${xaisx}px ${yaisx * -1}px 2px rgba(0,255,0,0.7),\n      ${xaisx * -1}px ${yaisx}px 2px rgba(255,0,0,0.7),\n      ${yaisx}px ${xaisx * -1}px 2px rgba(188,188,188,0.7),\n      ${yaisx * -1}px ${xaisx}px 2px rgba(0,0,255,0.7)      \n      `; // 多写几个就有了霓虹灯的效果\n    }\n    hero.addEventListener('mousemove', draw);\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "17 - 数组排序/README.md",
    "content": "## Day17 - Sort Without Articles(无冠词排序）\n### 效果图\n\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/17%20-%20%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F/show.PNG)\n##### 实现效果：对列表中列表项内容进行无冠词排序，在排序时不考虑a,an,the（大小写）的影响，点击ascending按钮进行升序，按下descending进行降序。\n### 源码分析\n```CSS\nbody{\n    background: url(\"https://source.unsplash.com/nDqA4d5NL0k/2000x2000\");\n    background-size:cover;\n    padding:50px;\n    display:flex;\n    flex-direction: column; /*这里是将ul,和div进行垂直居中排列，二者相对位置是上下，不加的话相对位置水平*/\n    align-items:center;\n    min-height:100vh;\n}\n```\n```HTML\n    <ul id=\"bands\"></ul>\n    <div class=\"button\">\n        <button name=\"ascending\">升序</button>\n        <button name=\"descengding\">降序</button>\n    </div>\n```\n> 这里给两个按钮进行命名，用以区分两个按钮，在进行CSS布局时使用`flex-direction：column`将ul和div处于竖直布局。\n```JS\nconst button=[...document.querySelectorAll(\"button\")];//使用扩展运算符，这里是打散，将伪数组转化为真正的数组\nbutton.forEach(item=>item.addEventListener(\"click\",paixu));\n```\n> 这里取得所有的按钮元素，并给每个按钮添加一个鼠标点击事件，注意项：伪元素转换为真正元素，可以使用扩展运算符，也可以使用`Array.form()`.  \n\n排序函数：\n```JS        \n//用sort比较函数以及del函数将每一项去除完冠词后进行比较，然后根据比较条件的正负，对原来项进行排序,sort会改变原数组\nfunction paixu(e){\n    let flag=this.getAttribute(\"name\");//使用name值对相同元素进行区别\n    if(flag==\"ascending\"){\n        bands.sort((a,b)=>del(a)>del(b)?1:-1); \n    }else{\n        bands.sort((a,b)=>del(a)>del(b)?-1:1);\n    }\n    show(bands,bandsele);\n}\n```\n>  这里的思路：刚开始使用的是两个函数，点击不同的按钮跳转到各自的事件处理函数上去。但是写完发现这两个函数及其相似，过于冗余，使用了name属性，然后当点击不同的按钮时，获取各自的name值，然后根据name值来判断点击的是哪一个按钮，来处理升序还是降序。   \n\n这里涉及的知识点：    \n- 1.使用`get.Attribute()`来获取属性值，但是具体获取那个元素的，这里使用了事件项event,这里的this就是e.target：表示当前元素。  \n- 2.sort数组的排序函数，根据判断程序来判断条件的正负，正的升序，负的降序，会改变原来的数组，所以，在最后有个显示的语句show(bands,bandsele);    \n\n删去冠词的函数：  \n```JS\n   // 取出冠词a,an,the的函数，返回一个新的字符串\n    function del(item){\n        return item.replace(/^(a|an|the)\\s{1}/ig,''); \n    }\n```\n> \\s{1}表示有一个空格,replace返回的事一个新字符串。  \n\n列表项显示函数：\n```JS\n    //将数组中的元素进行显示在指定列表中的函数,遍历整个数组元素，然后将返回的元素进行拼接整体显示在innerHTML\n    function show(arr,place){\n        place.innerHTML=arr.map(item=>{return `<li>${item}</li>`}).join('');\n    }\n```\n> 这个函数相当实用，map函数返回一个新的数组。\n"
  },
  {
    "path": "17 - 数组排序/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>无冠词排序</title>\n    <style>\n        body{\n            background: url(\"https://source.unsplash.com/nDqA4d5NL0k/2000x2000\");\n            background-size:cover;\n            padding:50px;\n            display:flex;\n            flex-direction: column; /*这里是将ul,和div进行垂直居中排列，二者相对位置是上下，不加的话相对位置水平*/\n            /* justify-content: center; */\n            align-items:center;\n            min-height:100vh;\n        }\n        #bands{\n            list-style: inside square;\n            font-size:20px;\n            background: white;\n            width:500px;\n            margin:auto;\n            padding:0px;\n            box-shadow: 0 0 0 20px rgba(0,0,0,0.25);\n        }\n        #bands li{\n            border-bottom:1px solid #efefef;\n            padding:20px;\n        }\n        #bands li:last-chid{\n            border-bottom:none;\n        }\n        .button{\n            margin-top:25px;\n            margin-bottom: 50px;\n        }\n        button{\n            font-size: 25px;\n            width:100px;\n            padding:3px;\n            border-radius: 5px;\n            margin-left: 10px;\n            box-shadow: 0 0 5px 0 rgba(0,0,0,0.25);       \n        }\n    </style>\n</head>\n<body>\n    <ul id=\"bands\"></ul>\n    <div class=\"button\">\n        <button name=\"ascending\">升序</button>\n        <button name=\"descengding\">降序</button>\n    </div>\n    <script>\n        const bands = ['The Plot in You', 'The Devil Wears Prada', 'Pierce the Veil', 'Norma Jean', 'The Bled',\n                                'Say Anything', 'The Midway State', 'We Came as Romans', 'Counterparts', 'Oh, Sleeper', 'A Skylit Drive',\n                                'Anywhere But Here', 'An Old Dog'\n                            ];\n        const bandsele=document.querySelector(\"#bands\");\n        show(bands,bandsele);\n        const button=[...document.querySelectorAll(\"button\")];//使用扩展运算符，这里是打散，将伪数组转化为真正的数组\n        button.forEach(item=>item.addEventListener(\"click\",paixu));\n\n\n        // 取出冠词a,an,the的函数，返回一个新的字符串\n        function del(item){\n            return item.replace(/^(a|an|the)\\s{1}/ig,'');\n        }\n        //用sort比较函数以及del函数将每一项去除完冠词后进行比较，然后根据比较条件的正负，对原来项进行排序,sort会改变原数组\n        function paixu(e){\n            let flag=this.getAttribute(\"name\");//使用name值对相同元素进行区别\n            if(flag==\"ascending\"){\n                bands.sort((a,b)=>del(a)>del(b)?1:-1);\n            }else{\n                bands.sort((a,b)=>del(a)>del(b)?-1:1);\n            }\n            show(bands,bandsele);\n        }\n        //将数组中的元素进行显示在指定列表中的函数,遍历整个数组元素，然后将返回的元素进行拼接整体显示在innerHTML\n        function show(arr,place){\n            place.innerHTML=arr.map(item=>{return `<li>${item}</li>`}).join('');\n        }\n\n    </script>\n</body>\n</html>"
  },
  {
    "path": "18 - Day18 - Reduce、Map混合使用计算时分秒/README.md",
    "content": "## Day18 - Reduce、Map混合使用计算时分秒\n### 效果图\n\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/18%20-%20Day18%20-%20Reduce%E3%80%81Map%E6%B7%B7%E5%90%88%E4%BD%BF%E7%94%A8%E8%AE%A1%E7%AE%97%E6%97%B6%E5%88%86%E7%A7%92/show.PNG)\n\n第18天挑战的内容主要是如何将一系列的`data-time`加起来，最终计算总时间，总时间用时分秒显示。  \n挑战升级：点击显示时长按钮将每个video的时间显示出来，将选中的videos进行总时长的计算。\n\n### 代码解析\ncss页面布局和示例17：无冠词排序基本一致\n主要分析JavaScript代码：\n```JS\n  <script>\n      let list=[...document.querySelectorAll(\"li\")];\n      let input = document.querySelector(\"input\");\n      let button = document.querySelector(\"button\");\n      button.addEventListener(\"click\",totalTime);\n      //将每项的时间长度也显示出来\n      list.forEach(item=>{\n        let datatime=item.dataset.time;\n        item.innerHTML=item.innerHTML+\"------------时长：\"+datatime;\n      });\n      //总时间计算函数\n      function totalTime(){\n        let sum = list.reduce((cal,item)=>{\n                let datatime=item.getAttribute(\"data-time\");\n                let [mins,seconds] = datatime.split(\":\").map(item=>parseFloat(item));//解构赋值\n                return cal+(mins*60+seconds); //reduce求和注意要让cal带入计算\n              },0);\n        let hours=sum/3600;\n        input.value=Math.floor(sum/3600)+\"个小时\"+Math.floor((sum%3600)/60)+\"分\"+sum%60+\"秒\";\n      }\n  </script>\n```\n> 主要内容就是两部分:  \n1、获得各项的data-time值，然后进行累加\n2、根据总秒数进行时分秒的计算  \n\n  `let [mins,seconds] = datatime.split(\":\").map(item=>parseFloat(item));`  \n 解构赋值:这里是将时和秒数进行切割形成新的数组，然后对数组中的每一项用parseFloat()进行字符串转换为数值，然后进行解构赋值，例如：[mins,seconds]=[03,58];这里将03赋值给mins,将58赋值给seconds.  \n  \n  `input.value=Math.floor(sum/3600)+\"个小时\"+Math.floor((sum%3600)/60)+\"分\"+sum%60+\"秒\";`  \n  \n总秒数除以3600得到的商就是小时个数，然后进行向下取整；  \n总秒数对3600取余，实际得到的是出去小时后还剩下多少秒，然后对剩下的秒数除以60（一分钟60秒）得到有多少分钟；  \n总秒数对60取余：即是说总秒数中把能凑成60的，也就是说能组成分钟的除去后还剩多少秒，就是要求的秒数。\n"
  },
  {
    "path": "18 - Day18 - Reduce、Map混合使用计算时分秒/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Adding Up Times with Reduce</title>\n  <style>\n      body{\n          background: url(\"https://source.unsplash.com/nDqA4d5NL0k/2000x2000\");\n          background-size:cover;\n          padding:50px;\n          display:flex;\n          flex-direction: column; /*这里是将ul,和div进行垂直居中排列，二者相对位置是上下，不加的话相对位置水平*/\n          /* justify-content: center; */\n          align-items:center;\n          min-height:100vh;\n      }\n      .videos{\n            list-style: inside square;\n            font-size:20px;\n            background: white;\n            width:500px;\n            margin:auto;\n            padding:0px;\n            box-shadow: 0 0 0 20px rgba(0,0,0,0.25);\n        }\n      .videos li{\n            border-bottom:1px solid #efefef;\n            padding:20px;\n            /* white-space: pre; */\n        }\n      .videos li:last-chid{\n            border-bottom:none;\n        }\n      .result{\n          margin-top:30px;\n          margin-bottom: 50px;\n          /* display: flex;\n          justify-content: center;\n          align-items: center; */\n      }\n      button{\n          font-size: 25px;\n          width:100px;\n          padding:3px;\n          border-radius: 5px;\n          margin-right: 10px;\n          box-shadow: 0 0 5px 0 rgba(0,0,0,0.25);       \n      }\n      input{\n        height:30px;\n        font-size: 25px;\n        text-align: center;\n        padding:3px;\n      }\n  </style>\n</head>\n\n<body>\n  <ul class=\"videos\">\n    <li data-time=\"5:43\">\n      Video 1\n    </li>\n    <li data-time=\"2:33\">\n      Video 2\n    </li>\n    <li data-time=\"3:45\">\n      Video 3\n    </li>\n    <li data-time=\"0:47\">\n      Video 4\n    </li>\n    <li data-time=\"5:21\">\n      Video 5\n    </li>\n    <li data-time=\"6:56\">\n      Video 6\n    </li>\n    <li data-time=\"3:46\">\n      Video 7\n    </li>\n    <li data-time=\"5:25\">\n      Video 8\n    </li>\n    <li data-time=\"3:14\">\n      Video 9\n    </li>\n    <li data-time=\"3:31\">\n      Video 10\n    </li>\n    <li data-time=\"5:59\">\n      Video 11\n    </li>\n    <li data-time=\"3:07\">\n      Video 12\n    </li>\n    <li data-time=\"11:29\">\n      Video 13\n    </li>\n    <li data-time=\"8:57\">\n      Video 14\n    </li>\n    <li data-time=\"5:49\">\n      Video 15\n    </li>\n    <li data-time=\"5:52\">\n      Video 16\n    </li>\n    <li data-time=\"5:50\">\n      Video 17\n    </li>\n    <li data-time=\"9:13\">\n      Video 18\n    </li>\n    <li data-time=\"11:51\">\n      Video 19\n    </li>\n    <li data-time=\"7:58\">\n      Video 20\n    </li>\n    <li data-time=\"4:40\">\n      Video 21\n    </li>\n    <li data-time=\"4:45\">\n      Video 22\n    </li>\n    <li data-time=\"6:46\">\n      Video 23\n    </li>\n    <li data-time=\"7:24\">\n      Video 24\n    </li>\n    <li data-time=\"7:12\">\n      Video 25\n    </li>\n    <li data-time=\"5:23\">\n      Video 26\n    </li>\n    <li data-time=\"3:34\">\n      Video 27\n    </li>\n    <li data-time=\"8:22\">\n      Video 28\n    </li>\n    <li data-time=\"5:17\">\n      Video 29\n    </li>\n    <li data-time=\"3:10\">\n      Video 30\n    </li>\n    <li data-time=\"4:43\">\n      Video 31\n    </li>\n    <li data-time=\"19:43\">\n      Video 32\n    </li>\n    <li data-time=\"0:47\">\n      Video 33\n    </li>\n    <li data-time=\"0:47\">\n      Video 34\n    </li>\n    <li data-time=\"3:14\">\n      Video 35\n    </li>\n    <li data-time=\"3:59\">\n      Video 36\n    </li>\n    <li data-time=\"2:43\">\n      Video 37\n    </li>\n    <li data-time=\"4:17\">\n      Video 38\n    </li>\n    <li data-time=\"6:56\">\n      Video 39\n    </li>\n    <li data-time=\"3:05\">\n      Video 40\n    </li>\n    <li data-time=\"2:06\">\n      Video 41\n    </li>\n    <li data-time=\"1:59\">\n      Video 42\n    </li>\n    <li data-time=\"1:49\">\n      Video 43\n    </li>\n    <li data-time=\"3:36\">\n      Video 44\n    </li>\n    <li data-time=\"7:10\">\n      Video 45\n    </li>\n    <li data-time=\"3:44\">\n      Video 46\n    </li>\n    <li data-time=\"3:44\">\n      Video 47\n    </li>\n    <li data-time=\"4:36\">\n      Video 48\n    </li>\n    <li data-time=\"3:16\">\n      Video 49\n    </li>\n    <li data-time=\"1:10\">\n      Video 50\n    </li>\n    <li data-time=\"6:10\">\n      Video 51\n    </li>\n    <li data-time=\"2:14\">\n      Video 52\n    </li>\n    <li data-time=\"3:44\">\n      Video 53\n    </li>\n    <li data-time=\"5:05\">\n      Video 54\n    </li>\n    <li data-time=\"6:03\">\n      Video 55\n    </li>\n    <li data-time=\"12:39\">\n      Video 56\n    </li>\n    <li data-time=\"1:56\">\n      Video 57\n    </li>\n    <li data-time=\"4:04\">\n      Video 58\n    </li>\n  </ul>\n  <div class=\"result\">\n      <button>总时长</button>\n      <input type=\"text\" placeholder=\"这里显示video的总时长\" readonly></input>\n  </div>\n  <script>\n      let list=[...document.querySelectorAll(\"li\")];\n      let input = document.querySelector(\"input\");\n      let button = document.querySelector(\"button\");\n      button.addEventListener(\"click\",totalTime);\n      list.forEach(item=>{\n        let datatime=item.dataset.time;\n        item.innerHTML=item.innerHTML+\"------------时长：\"+datatime;\n      });\n      //总时间计算函数\n      function totalTime(){\n        let sum = list.reduce((cal,item)=>{\n                let datatime=item.getAttribute(\"data-time\");\n                let [mins,seconds] = datatime.split(\":\").map(item=>parseFloat(item));//解构赋值\n                return cal+(mins*60+seconds); //reduce求和注意要让cal带入计算\n              },0);\n        let hours=sum/3600;\n        input.value=Math.floor(sum/3600)+\"个小时\"+Math.floor((sum%3600)/60)+\"分\"+sum%60+\"秒\";\n      }\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "19 - Webcam Fun/README.md",
    "content": "# Day19 - 摄像、拍照，滤镜中文指南\n## 效果图\n![](http://om1c35wrq.bkt.clouddn.com/day19.gif)\n![](http://om1c35wrq.bkt.clouddn.com/day19-red%E6%95%88%E6%9E%9C%E5%9B%BE.png)\nJS30天第19天挑战的是如何调用摄像头录像、播放，如何捕捉视频将其绘制`canvas`，还有拍照，以及滤镜的制作。\n## 运行项目\n1. 通过`npm install`安装依赖包\n2. 通过`npm start`启动服务器\n3. 浏览器直接访问`http://localhost:3000`\n\n```js\nliyuechun:19 - Webcam Fun yuechunli$ pwd\n/Users/liyuechun/Documents/js30/JavaScript30-liyuechun/19 - Webcam Fun\nliyuechun:19 - Webcam Fun yuechunli$ ls\nREADME.md\t\tpackage-lock.json\tscripts.js\nindex.html\t\tpackage.json\t\tstyle.css\nliyuechun:19 - Webcam Fun yuechunli$ npm install\n\n> fsevents@1.1.2 install /Users/liyuechun/Documents/js30/JavaScript30-liyuechun/19 - Webcam Fun/node_modules/fsevents\n> node install\n\n[fsevents] Success: \"/Users/liyuechun/Documents/js30/JavaScript30-liyuechun/19 - Webcam Fun/node_modules/fsevents/lib/binding/Release/node-v57-darwin-x64/fse.node\" already installed\nPass --update-binary to reinstall or --build-from-source to recompile\nnpm WARN gum@1.0.0 No repository field.\n\nadded 411 packages in 5.921s\nliyuechun:19 - Webcam Fun yuechunli$ npm start\n\n> gum@1.0.0 start /Users/liyuechun/Documents/js30/JavaScript30-liyuechun/19 - Webcam Fun\n> browser-sync start --server --files '*.css, *.html, *.js'\n\n[Browsersync] Access URLs:\n --------------------------------------\n       Local: http://localhost:3000\n    External: http://192.168.1.116:3000\n --------------------------------------\n          UI: http://localhost:3001\n UI External: http://192.168.1.116:3001\n --------------------------------------\n[Browsersync] Serving files from: ./\n[Browsersync] Watching files...\n```\n\n## 主要思路\n\n* 获取到浏览器的摄像头的影像\n* 将影像的记录导出到canvas中\n* 通过获取canvas中的图片信息，对图片添加滤镜\n\n\n## Browsersync\n\n#### 项目结构\n\n![](http://om1c35wrq.bkt.clouddn.com/Snip20170809_21.png)\n\n#### 了解Browsersync\n\n省时的浏览器同步测试工具,Browsersync能让浏览器实时、快速响应您的文件更改（html、js、css、sass、less等）并自动刷新页面。更重要的是`Browsersync可以同时在PC、平板、手机`等设备下进项调试。您可以想象一下：“假设您的桌子上有pc、ipad、iphone、android等设备，同时打开了您需要调试的页面，当您使用browsersync后，您的任何一次代码保存，以上的设备都会同时显示您的改动”。无论您是前端还是后端工程师，使用它将提高您30%的工作效率。\n\n![](http://om1c35wrq.bkt.clouddn.com/sync-demo.gif)\n\n有了它，您不用在多个浏览器、多个设备间来回切换，频繁的刷新页面。更神奇的是您在一个浏览器中滚动页面、点击等行为也会同步到其他浏览器和设备中，这一切还可以通过可视化界面来控制。\n\n![](http://om1c35wrq.bkt.clouddn.com/scroll-demo.gif)\n\n## 获取影像\n\n```javascript\nfunction getVideo(){\n    navigator.mediaDevices.getUserMedia({video:true,audio:false})\n        .then(videostream => {\n            console.log(videostream);\n            video.src = URL.createObjectURL(videostream); // 创建url（creates  a URL for the specified object）\n            video.play();\n        })\n        .catch((err) => {\n            console.error('OH,Don\\'t have permission to use your local cam!',err);\n        });\n}\n```\n\n- [MediaDevices.getUserMedia()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia)\n> `MediaDevices.getUserMedia()`方法提示用户允许使用一个视频和/或一个音频输入设备，例如相机或屏幕共享和/或麦克风。如果用户给予许可，就返回一个`Promise`对象，`MediaStream`对象作为此`Promise`对象的`Resolved`［成功］状态的回调函数参数，相应的，如果用户拒绝了许可，或者没有媒体可用的情况下，`PermissionDeniedError`或者`NotFoundError`作为此`Promise`的`Rejected`［失败］状态的回调函数参数。注意，由于用户不会被要求必须作出允许或者拒绝的选择，所以返回的`Promise`对象可能既不会触发`resolve`也不会触发`reject`。\n\n\n- [URL.createObjectURL()](https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL)。\n> `URL.createObjectURL()` 静态方法会创建一个`DOMString`，其中包含一个表示参数中给出的对象的`URL`。这个`URL` 的生命周期和创建它的窗口中的`document` 绑定。这个新的`URL`对象表示指定的`File` 对象或`Blob` 对象。\n\n## canvas绘图\n```javascript\nfunction printToCanvas(){\n    let width = video.videoWidth;\n    let height = video.videoHeight;\n    canvas.height = height;\n    canvas.width = width; // 勿忘：设置canvas的宽和高\n    console.log(width,height);\n    return setInterval(() => {\n        ctx.drawImage(video,0,0,width,height);\n\n        // get the image data\n        let imagedata = ctx.getImageData(0,0,width,height);\n        // console.log(imagedata.data);\n\n        // mess the image data\n        // imagedata = redEffect(imagedata);\n        // imagedata = rgbsplit(imagedata);\n        // ctx.globalAlpha = 0.2;\n        imagedata = greenScreen(imagedata);\n\n        // put the image data back\n        ctx.putImageData(imagedata,0,0);\n    },16);\n}\n```\n* `ctx.drawImage()`\n>它能够将当前的视频流（video）中的一帧画在canvas中。\n\n- [getImageData()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getImageData)\n> `ctx.getImageData()`返回一个ImageData对象，用来描述canvas区域隐含的像素数据，这个区域通过矩形表示，起始点为(sx, sy)、宽为sw、高为sh。\n\n- [putImageData()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData)\n> `ctx.putImageData()`:该方法是 Canvas 2D API 将数据从已有的 ImageData 对象绘制到位图的方法。 如果提供了脏矩形，只能绘制矩形的像素。\n\n- imagedata信息\n> imagedata中有大量的数据，其中分别代表了图片的颜色信息，分别为red，green，blue，alpha的值，因此我们可以同添加自定义滤镜，通过改变颜色的rgba的值，控制页面的效果。\n\n## 摄像记录导出到canvas中\n\n```javascript\nfunction takePhoto(){\n    // 播放音效\n    snap.currentTime = 0;\n    snap.play();\n    \n    // 获取图像数据\n    let data = canvas.toDataURL('image/jpeg');\n    // console.log(data);\n    let link = document.createElement('a');\n    link.href = data;\n    link.setAttribute('downlond','handsome');\n    link.innerHTML = `<img src=${data} alt=handsome>`\n    strip.insertBefore(link,strip.firstChild);\n}\n```\n- 在没次点击照相的时候，都要求播一遍音效，并且为了模拟现实情况，我们在用户点击时，设置当前的播放时间为0，再播放音效。\n- [canvas.toDataURL('image/jpeg');](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL)方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型，默认为 PNG 格式。图片的分辨率为96dpi。 \n- 接下来新建一个a元素，设置其href的值为data。在插入在文档中。实现截图成功的效果。\n## 自定义滤镜\n\n```javascript\n// 红色特效滤镜\nfunction redEffect(imagedata){\n    for(let i = 0;i<imagedata.data.length;i+=4){\n        imagedata.data[i + 0] += 200; // red\n        imagedata.data[i + 1] -= 50; // green\n        imagedata.data[i + 2] *= 0.5; // blue\n    }\n    return imagedata;\n}\n\n// RGB分离\nfunction rgbsplit(imagedata){\n    for(let i = 0;i<imagedata.data.length;i+=4){\n        imagedata.data[i - 100] = imagedata.data[i + 0]; // red\n        imagedata.data[i + 150] = imagedata.data[i + 1]; // green\n        imagedata.data[i - 150] = imagedata.data[i + 2]; // blue\n    }\n    return imagedata;\n}\n\n// 绿屏（部分消失）\nfunction greenScreen(imagedata) {\n  const levels = {};\n\n  document.querySelectorAll('.rgb input').forEach((input) => {\n    levels[input.name] = input.value;\n  });\n\n  for (i = 0; i < pixels.data.length; i = i + 4) {\n    red = imagedata.data[i + 0];\n    green = imagedata.data[i + 1];\n    blue = imagedata.data[i + 2];\n    alpha = imagedata.data[i + 3];\n\n    if (red >= levels.rmin\n      && green >= levels.gmin\n      && blue >= levels.bmin\n      && red <= levels.rmax\n      && green <= levels.gmax\n      && blue <= levels.bmax) {\n      // take it out!\n      imagedata.data[i + 3] = 0;\n    }\n  }\n\n  return imagedata;\n}\n```\n这部分主要定义了三个滤镜，由于我们通过`ctx.getImageData`可以获取到页面颜色的rgba的值，因此我们添加滤镜的原理也是这样，通过循环改变一张图片中的所有rgba的值即可。\n"
  },
  {
    "path": "19 - Webcam Fun/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Get User Media Code Along!</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n  <div class=\"photobooth\">\n    <div class=\"controls\">\n      <button onClick=\"takePhoto()\">Take Photo</button>\n       <div class=\"rgb\">\n        <label for=\"rmin\">Red Min:</label>\n        <input type=\"range\" min=0 max=255 name=\"rmin\">\n        <label for=\"rmax\">Red Max:</label>\n        <input type=\"range\" min=0 max=255 name=\"rmax\">\n\n        <br>\n\n        <label for=\"gmin\">Green Min:</label>\n        <input type=\"range\" min=0 max=255 name=\"gmin\">\n        <label for=\"gmax\">Green Max:</label>\n        <input type=\"range\" min=0 max=255 name=\"gmax\">\n\n        <br>\n\n        <label for=\"bmin\">Blue Min:</label>\n        <input type=\"range\" min=0 max=255 name=\"bmin\">\n        <label for=\"bmax\">Blue Max:</label>\n        <input type=\"range\" min=0 max=255 name=\"bmax\">\n      </div>\n    </div>\n\n    <canvas class=\"photo\"></canvas>\n    <video class=\"player\"></video>\n    <div class=\"strip\"></div>\n  </div>\n\n  <audio class=\"snap\" src=\"http://wesbos.com/demos/photobooth/snap.mp3\" hidden></audio>\n\n  <script src=\"scripts.js\"></script>\n\n</body>\n</html>\n"
  },
  {
    "path": "19 - Webcam Fun/package.json",
    "content": "{\n  \"name\": \"gum\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"scripts.js\",\n  \"scripts\": {\n    \"start\": \"browser-sync start --server --files '*.css, *.html, *.js'\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"browser-sync\": \"^2.12.5\"\n  }\n}\n"
  },
  {
    "path": "19 - Webcam Fun/scripts.js",
    "content": "const video = document.querySelector('.player');\nconst canvas = document.querySelector('.photo');\nconst ctx = canvas.getContext('2d');\nconst strip = document.querySelector('.strip');\nconst snap = document.querySelector('.snap');\n\nfunction getVideo(){\n    navigator.mediaDevices.getUserMedia({video:true,audio:false})\n        .then(videostream => {\n            console.log(videostream);\n            video.src = URL.createObjectURL(videostream); // 创建url（creates  a URL for the specified object）\n            video.play();\n        })\n        .catch((err) => {\n            console.error('OH,Don\\'t have permission to use your local cam!',err);\n        });\n}\n\nfunction printToCanvas(){\n    let width = video.videoWidth;\n    let height = video.videoHeight;\n    canvas.height = height;\n    canvas.width = width; // 勿忘：设置canvas的宽和高\n    console.log(width,height);\n    return setInterval(() => {\n        ctx.drawImage(video,0,0,width,height);\n\n        // get the image data\n        let imagedata = ctx.getImageData(0,0,width,height);\n        // console.log(imagedata.data);\n\n        // mess the image data\n        imagedata = redEffect(imagedata);\n        // imagedata = rgbsplit(imagedata);\n        // ctx.globalAlpha = 0.2;\n        // imagedata = greenScreen(imagedata);\n\n        // put the image data back\n        ctx.putImageData(imagedata,0,0);\n    },16);\n}\n\nfunction takePhoto(){\n    // 播放音效\n    snap.currentTime = 0;\n    snap.play();\n\n    // 获取图像数据\n    let data = canvas.toDataURL('image/jpeg');\n    // console.log(data);\n    let link = document.createElement('a');\n    link.href = data;\n    link.setAttribute('downlond','handsome');\n    link.innerHTML = `<img src=${data} alt=handsome>`\n    strip.insertBefore(link,strip.firstChild);\n}\n\n// 红色特效滤镜\nfunction redEffect(imagedata){\n    for(let i = 0;i<imagedata.data.length;i+=4){\n        imagedata.data[i + 0] += 200; // red\n        imagedata.data[i + 1] -= 50; // green\n        imagedata.data[i + 2] *= 0.5; // blue\n    }\n    return imagedata;\n}\n\n// RGB分离\nfunction rgbsplit(imagedata){\n    for(let i = 0;i<imagedata.data.length;i+=4){\n        imagedata.data[i - 100] = imagedata.data[i + 0]; // red\n        imagedata.data[i + 150] = imagedata.data[i + 1]; // green\n        imagedata.data[i - 150] = imagedata.data[i + 2]; // blue\n    }\n    return imagedata;\n}\n\n// 绿屏（部分消失）\nfunction greenScreen(imagedata) {\n  const levels = {};\n\n  document.querySelectorAll('.rgb input').forEach((input) => {\n    levels[input.name] = input.value;\n  });\n\n  for (i = 0; i < pixels.data.length; i = i + 4) {\n    red = imagedata.data[i + 0];\n    green = imagedata.data[i + 1];\n    blue = imagedata.data[i + 2];\n    alpha = imagedata.data[i + 3];\n\n    if (red >= levels.rmin\n      && green >= levels.gmin\n      && blue >= levels.bmin\n      && red <= levels.rmax\n      && green <= levels.gmax\n      && blue <= levels.bmax) {\n      // take it out!\n      imagedata.data[i + 3] = 0;\n    }\n  }\n\n  return pixels;\n}\n\ngetVideo();\nvideo.addEventListener('canplay',printToCanvas);\n"
  },
  {
    "path": "19 - Webcam Fun/style.css",
    "content": "html {\n  box-sizing: border-box;\n}\n\n*, *:before, *:after {\n  box-sizing: inherit;\n}\n\nhtml {\n  font-size: 10px;\n  background:#ffc600;\n}\n\n.photobooth {\n  background:white;\n  max-width:150rem;\n  margin: 2rem auto;\n  border-radius:2px;\n}\n\n/*clearfix*/\n.photobooth:after {\n  content: '';\n  display: block;\n  clear: both;\n}\n\n.photo {\n  width:100%;\n  float:left;\n}\n\n.player {\n  position: absolute;\n  top:20px;\n  right: 20px;\n  width:200px;\n}\n\n/*\n  Strip!\n*/\n\n.strip {\n  padding:2rem;\n}\n.strip img {\n  width:100px;\n  overflow-x: scroll;\n  padding:0.8rem 0.8rem 2.5rem 0.8rem;\n  box-shadow:0 0 3px rgba(0,0,0,0.2);\n  background:white;\n}\n\n.strip a:nth-child(5n+1) img { transform: rotate(10deg); }\n.strip a:nth-child(5n+2) img { transform: rotate(-2deg); }\n.strip a:nth-child(5n+3) img { transform: rotate(8deg); }\n.strip a:nth-child(5n+4) img { transform: rotate(-11deg); }\n.strip a:nth-child(5n+5) img { transform: rotate(12deg); }\n"
  },
  {
    "path": "20 - Speech Detection/README.md",
    "content": "# Day20 - 语言识别系统中文指南\n\n> 本文出自：[春哥个人博客：http://www.liyuechun.org](http://liyuechun.org)\n> 作者：©[黎跃春-追时间的人](http://weibo.com/mobiledevelopment)\n> 简介：[JavaScript30](https://javascript30.com) 是 [Wes Bos](https://github.com/wesbos) 推出的一个 30 天挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。目的是帮助人们用纯 JavaScript 来写东西，不借助框架和库，也不使用编译器和引用。现在你看到的是这系列指南的第 20 篇。完整中文版指南及视频教程在 [从零到壹全栈部落](http://kongyixueyuan.com/course/4188)。\n\n## 运行项目\n\n\n```js\n$ npm install\n$ npm start\n```\n\n浏览器打开`http://localhost:3000/index-FINISHED.html`\n\n效果图如下：\n\n![](http://om1c35wrq.bkt.clouddn.com/Snip20170811_1.png)\n\n![](http://om1c35wrq.bkt.clouddn.com/day20-100.gif)\n\n\n## 程序源码\n\n### HTML代码\n\n\n\n```js html  css\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Speech Detection</title>\n</head>\n\n<body>\n\n  <div class=\"words\" contenteditable>\n  </div>\n  \n  <style>\n    html {\n      font-size: 10px;\n    }\n\n    body {\n      background: #ffc600;\n      font-family: 'helvetica neue';\n      font-weight: 200;\n      font-size: 20px;\n    }\n\n    .words {\n      max-width: 500px;\n      margin: 50px auto;\n      background: white;\n      border-radius: 5px;\n      box-shadow: 10px 10px 0 rgba(0, 0, 0, 0.1);\n      padding: 1rem 2rem 1rem 5rem;\n      background: -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, #fff)) 0 4px;\n      background-size: 100% 3rem;\n      position: relative;\n      line-height: 3rem;\n    }\n\n    p {\n      margin: 0 0 3rem;\n    }\n\n    .words:before {\n      content: '';\n      position: absolute;\n      width: 4px;\n      top: 0;\n      left: 30px;\n      bottom: 0;\n      border: 1px solid;\n      border-color: transparent #efe4e4;\n    }\n  </style>\n\n</body>\n\n</html>\n```\n\n### JS代码\n\n```js\n  <script>\n    // 根据浏览器之间的兼容性，需要同时添加浏览器前缀\n    window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;\n\n    // 实例化SpeechRecognition对象\n    const recognition = new SpeechRecognition();\n\n    // interimResults 属性的默认值是 false ，代表语音识别器的返回值不会改变。在这个演示中，我们把它设置为 true ，这样随着我们的输入，识别结果有可能会改变。仔细观看演示，灰色的文字是临时性的，有时会改变，而黑色文本是最终结果，不会改变。\n    recognition.interimResults = true;\n\n    // 创建p便签，附加到DOM树中\n    let p = document.createElement('p');\n    const words = document.querySelector('.words');\n    words.appendChild(p);\n\n    // 监听recognition的result事件，获取到语音输入的文字\n    recognition.addEventListener('result', (e) => {\n      const results = Array.from(e.results) // e.results中保存的是识别的结果，本来并不是数组，需要将其转换为数组，方便使用其map、join等方法。\n        .map(result => result[0])\n        .map(result => result.transcript) // 获取到每一段话，是一个数组类型\n        .join(''); // 将每一段话连接成字符串\n\n      // 可以动态的将其中的某一个词语换掉\n      const poopScript = results.replace(/good/gi, '👍');\n      p.textContent = poopScript;\n\n      // 如果当前一段输入结束了，也就是有停顿，就会新建一个p便签\n      if (e.results[0].isFinal) {\n        p = document.createElement('p');\n        words.appendChild(p);\n      }\n    });\n\n    // 监听recognition的end事件，当前输入结束后，再次开始，使其一直处于输入状态\n    recognition.addEventListener('end', recognition.start);\n\n    // 开启recognition\n    recognition.start();\n  </script>\n```\n\n#### JS实现思路\n* 新建一个语音识别的对象\n* 开启该语音识别对象的识别服务\n* 监听`result`事件，实时获取语音输入内容\n* 监听`end`事件，当结束时再次开启语音识别，使其持续监听\n\n#### JS源码解析\n\n - [SpeechRecognition参考文档](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition)\n \n - 其中监听`result`事件，根据事件返回值获取到语音输入的内容\n![](http://om1c35wrq.bkt.clouddn.com/Snip20170811_2.png)\n\n可以看到`transcript`中保存的是语音输入的内容。其中可以看到还有一个属性为`confidence`，代表这段话是别的精度，越大正确率越高。\n\n-`SpeechRecognition`属性\n\n```js\n<!--新建语音识别的对象-->\nvar recognition = new SpeechRecognition();\n<!--continuous默认值为false，当continuous值为true时，表示，一句话结束后，语音识别继续识别-->\nrecognition.continuous = false;\n<!--设置按照什么语言来识别-->\nrecognition.lang = 'en-US';\n<!--interimResults 属性的默认值是 false ，代表语音识别器的返回值不会改变。在这个演示中，我们把它设置为 true ，这样随着我们的输入，识别结果有可能会改变。仔细观看演示，灰色的文字是临时性的，有时会改变，而黑色文本是最终结果，不会改变。-->\nrecognition.interimResults = false;\n...\n```\n\n第20天的内容就到这里，主要学习`SpeechRecognition`相关属性的使用。\n\n\n## 源码下载\n\n[Github Source Code](https://github.com/liyuechun/JavaScript30-liyuechun)\n\n|全栈部落|区块链部落|\n|:---------:|:------:|\n|![](http://orhm8wuhd.bkt.clouddn.com/quanzhanbuluo100.jpeg)|![](http://orhm8wuhd.bkt.clouddn.com/qukuailian100.jpg)|\n\n\n\n\n\n\n"
  },
  {
    "path": "20 - Speech Detection/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Speech Detection</title>\n</head>\n<body>\n\n  <div class=\"words\" contenteditable>\n  </div>\n\n<script>\n  window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;\n\n  const recognition = new SpeechRecognition();\n  recognition.interimResults = true;\n\n  let p = document.createElement('p');\n  const words = document.querySelector('.words');\n  words.appendChild(p);\n\n  recognition.addEventListener('result', e => {\n    const transcript = Array.from(e.results)\n      .map(result => result[0])\n      .map(result => result.transcript)\n      .join('');\n\n      const poopScript = transcript.replace(/poop|poo|shit|dump/gi, '💩');\n      p.textContent = poopScript;\n\n      if (e.results[0].isFinal) {\n        p = document.createElement('p');\n        words.appendChild(p);\n      }\n  });\n\n  recognition.addEventListener('end', recognition.start);\n\n  recognition.start();\n\n</script>\n\n\n  <style>\n    html {\n      font-size: 10px;\n    }\n\n    body {\n      background:#ffc600;\n      font-family: 'helvetica neue';\n      font-weight: 200;\n      font-size: 20px;\n    }\n\n    .words {\n      max-width:500px;\n      margin:50px auto;\n      background:white;\n      border-radius:5px;\n      box-shadow:10px 10px 0 rgba(0,0,0,0.1);\n      padding:1rem 2rem 1rem 5rem;\n      background: -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, #fff)) 0 4px;\n      background-size: 100% 3rem;\n      position: relative;\n      line-height:3rem;\n    }\n    p {\n      margin: 0 0 3rem;\n    }\n\n    .words:before {\n      content: '';\n      position: absolute;\n      width: 4px;\n      top: 0;\n      left: 30px;\n      bottom: 0;\n      border: 1px solid;\n      border-color: transparent #efe4e4;\n    }\n  </style>\n\n</body>\n</html>\n"
  },
  {
    "path": "20 - Speech Detection/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Speech Detection</title>\n</head>\n<body>\n\n  <div class=\"words\" contenteditable>\n  </div>\n\n<script>\n  window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;\n\n\n</script>\n\n\n  <style>\n    html {\n      font-size: 10px;\n    }\n\n    body {\n      background:#ffc600;\n      font-family: 'helvetica neue';\n      font-weight: 200;\n      font-size: 20px;\n    }\n\n    .words {\n      max-width:500px;\n      margin:50px auto;\n      background:white;\n      border-radius:5px;\n      box-shadow:10px 10px 0 rgba(0,0,0,0.1);\n      padding:1rem 2rem 1rem 5rem;\n      background: -webkit-gradient(linear, 0 0, 0 100%, from(#d9eaf3), color-stop(4%, #fff)) 0 4px;\n      background-size: 100% 3rem;\n      position: relative;\n      line-height:3rem;\n    }\n    p {\n      margin: 0 0 3rem;\n    }\n\n    .words:before {\n      content: '';\n      position: absolute;\n      width: 4px;\n      top: 0;\n      left: 30px;\n      bottom: 0;\n      border: 1px solid;\n      border-color: transparent #efe4e4;\n    }\n  </style>\n\n</body>\n</html>\n"
  },
  {
    "path": "20 - Speech Detection/package.json",
    "content": "{\n  \"name\": \"gum\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"scripts.js\",\n  \"scripts\": {\n    \"start\": \"browser-sync start --directory --server --files '*.css, *.html, *.js'\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"browser-sync\": \"^2.12.5\"\n  }\n}\n"
  },
  {
    "path": "21 - Geolocation/README.md",
    "content": "# Day21 - Geolocation 中文指南\n\n### 涉及知识点主要是geolocation,这里不做详细介绍，[具体内容见我总结的博客，请戳我！](https://blog.csdn.net/qq_39207948/article/details/88081604)\n\n## 运行项目\n\n```js\nliyuechun:21 - Geolocation yuechunli$ npm install\n......\nliyuechun:21 - Geolocation yuechunli$ npm start\n\n> gum@1.0.0 start /Users/liyuechun/Documents/js30/JavaScript30-liyuechun/21 - Geolocation\n> browser-sync start --directory --server --files '*.css, *.html, *.js' --https\n\n[Browsersync] Access URLs:\n -------------------------------------\n       Local: https://localhost:3000\n    External: https://192.168.1.7:3000\n -------------------------------------\n          UI: http://localhost:3001\n UI External: http://192.168.1.7:3001\n -------------------------------------\n[Browsersync] Serving files from: ./\n[Browsersync] Watching files...\n```\n\n## 效果图\n\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/21%20-%20Geolocation/show.PNG)\n\n第21天的目的是练习`NavigatorGeolocation.geolocation`这一webAPI的使用，通过使用此API可以访问到设备的位置信息。这允许网站或应用根据用户的位置提供个性化结果。\n\n## UI源码\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n  <meta name=\"viewport\" content=\"width=device-width\">\n</head>\n\n<body>\n  <svg class=\"arrow\" xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.1\" x=\"0px\" y=\"0px\"\n    viewBox=\"0 0 64 64\" enable-background=\"new 0 0 64 64\" xml:space=\"preserve\">\n    <g>\n      <path fill=\"#ffffff\" d=\"M32,1.824C15.361,1.824,1.825,15.361,1.825,32c0,16.639,13.537,30.176,30.175,30.176   S62.175,48.639,62.175,32C62.175,15.361,48.639,1.824,32,1.824z M29.715,3.988h1.12l2.333,3.807V3.988h1.069v5.701h-1.155   l-2.298-3.718v3.718h-1.069V3.988z M9.323,33.917H8.102l-1.136-4.262l-1.132,4.262H4.587l-1.361-5.701h1.178l0.859,3.916   l1.042-3.916h1.369l0.999,3.982l0.875-3.982h1.159L9.323,33.917z M33.995,59.828c-0.181,0.285-0.438,0.497-0.77,0.636   c-0.332,0.139-0.745,0.208-1.241,0.208c-0.721,0-1.274-0.167-1.661-0.5c-0.386-0.333-0.617-0.819-0.692-1.456l1.12-0.109   c0.067,0.376,0.204,0.652,0.41,0.828c0.206,0.176,0.484,0.264,0.834,0.264c0.371,0,0.65-0.078,0.838-0.235   c0.188-0.157,0.282-0.34,0.282-0.55c0-0.135-0.04-0.25-0.119-0.344c-0.079-0.095-0.217-0.177-0.414-0.247   c-0.135-0.047-0.442-0.13-0.922-0.249c-0.617-0.153-1.05-0.341-1.299-0.564c-0.35-0.314-0.525-0.696-0.525-1.147   c0-0.29,0.082-0.562,0.247-0.815c0.165-0.253,0.402-0.445,0.712-0.577c0.31-0.132,0.684-0.198,1.122-0.198   c0.716,0,1.254,0.157,1.616,0.471c0.362,0.314,0.552,0.733,0.57,1.256l-1.151,0.051c-0.049-0.293-0.155-0.504-0.317-0.632   c-0.162-0.128-0.405-0.193-0.729-0.193c-0.334,0-0.596,0.069-0.786,0.206c-0.122,0.088-0.183,0.206-0.183,0.354   c0,0.135,0.057,0.25,0.171,0.346c0.145,0.122,0.498,0.249,1.058,0.381c0.56,0.132,0.974,0.269,1.243,0.41   c0.268,0.141,0.478,0.334,0.63,0.58c0.152,0.245,0.227,0.548,0.227,0.908C34.267,59.237,34.176,59.543,33.995,59.828z M32,52.795   c-11.466,0-20.795-9.329-20.795-20.795c0-11.466,9.329-20.795,20.795-20.795S52.795,20.534,52.795,32   C52.795,43.466,43.466,52.795,32,52.795z M55.014,33.917v-5.701h4.227v0.965h-3.076v1.264h2.862v0.96h-2.862v1.552h3.185v0.961   H55.014z\"\n      />\n      <g>\n        <path fill=\"#000000\" d=\"M48.904,31.863c-4.074-1.358-8.148-2.717-12.226-4.066c-0.265-0.087-0.399-0.223-0.486-0.486    c-1.349-4.077-2.708-8.151-4.066-12.226c-0.029-0.087-0.074-0.169-0.132-0.3c-0.054,0.152-0.09,0.245-0.122,0.34    c-1.352,4.053-2.707,8.104-4.048,12.161c-0.096,0.292-0.246,0.428-0.532,0.522c-4.056,1.342-8.108,2.696-12.16,4.049    c-0.097,0.032-0.189,0.074-0.344,0.137c0.172,0.06,0.267,0.093,0.362,0.125c4.074,1.358,8.148,2.717,12.224,4.072    c0.204,0.068,0.337,0.158,0.412,0.386c1.243,3.757,2.498,7.511,3.75,11.265c0.144,0.432,0.291,0.862,0.463,1.373    c0.068-0.185,0.108-0.285,0.142-0.386c1.349-4.042,2.701-8.083,4.04-12.128c0.094-0.284,0.231-0.438,0.523-0.534    c4.056-1.341,8.108-2.696,12.161-4.048c0.099-0.033,0.195-0.076,0.347-0.137C49.067,31.925,48.987,31.891,48.904,31.863z     M37.475,32.038c-1.316,0.439-2.631,0.879-3.947,1.314c-0.095,0.031-0.139,0.081-0.17,0.173c-0.434,1.313-0.873,2.625-1.311,3.937    c-0.012,0.033-0.024,0.066-0.046,0.126c-0.056-0.166-0.104-0.306-0.15-0.446c-0.407-1.219-0.814-2.437-1.218-3.657    c-0.025-0.074-0.068-0.104-0.134-0.125c-1.323-0.44-2.646-0.881-3.968-1.322c-0.031-0.01-0.062-0.022-0.118-0.041    c0.05-0.02,0.081-0.034,0.112-0.045c1.315-0.439,2.631-0.879,3.947-1.314c0.093-0.03,0.142-0.075,0.173-0.17    c0.435-1.316,0.875-2.632,1.314-3.947c0.01-0.031,0.022-0.062,0.039-0.11c0.019,0.042,0.033,0.069,0.043,0.097    c0.441,1.323,0.882,2.645,1.321,3.969c0.028,0.085,0.072,0.129,0.158,0.158c1.324,0.438,2.646,0.879,3.969,1.32    c0.027,0.009,0.053,0.02,0.1,0.038C37.538,32.013,37.507,32.027,37.475,32.038z\"\n        />\n        <path fill=\"#000000\" d=\"M24.436,27.633c-1.069-2.137-2.119-4.237-3.216-6.43c2.189,1.094,4.292,2.145,6.433,3.216    c-0.359,0.713-0.706,1.404-1.057,2.091c-0.023,0.045-0.078,0.082-0.127,0.106C25.807,26.949,25.143,27.28,24.436,27.633z\"\n        />\n        <path fill=\"#000000\" d=\"M39.573,27.632c-0.696-0.348-1.351-0.673-2.002-1.005c-0.076-0.038-0.155-0.104-0.193-0.177    c-0.338-0.661-0.666-1.326-1.019-2.033c2.121-1.061,4.228-2.115,6.43-3.217C41.69,23.399,40.635,25.509,39.573,27.632z\"\n        />\n        <path fill=\"#000000\" d=\"M24.436,36.339c0.712,0.357,1.394,0.698,2.074,1.043c0.046,0.024,0.088,0.073,0.113,0.121    c0.339,0.671,0.674,1.345,1.028,2.051c-2.126,1.063-4.232,2.117-6.43,3.216C22.317,40.577,23.37,38.472,24.436,36.339z\"\n        />\n        <path fill=\"#000000\" d=\"M36.358,39.555c0.354-0.707,0.688-1.38,1.028-2.05c0.028-0.056,0.084-0.111,0.14-0.139    c0.67-0.339,1.343-0.674,2.047-1.026c1.066,2.131,2.118,4.235,3.215,6.43C40.601,41.676,38.503,40.628,36.358,39.555z\"\n        />\n      </g>\n    </g>\n  </svg>\n\n\n  <h1 class=\"speed\">\n    <span class=\"speed-value\">0</span>\n    <span class=\"units\">KM/H</span>\n  </h1>\n\n  <style>\n    html {\n      font-size: 100px;\n    }\n\n    body {\n      margin: 0;\n      font-family: sans-serif;\n      min-height: 100vh;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      flex-direction: column;\n      background: radial-gradient(black 15%, transparent 16%) 0 0,\n      radial-gradient(black 15%, transparent 16%) 8px 8px,\n      radial-gradient(rgba(255, 255, 255, .1) 15%, transparent 20%) 0 1px,\n      radial-gradient(rgba(255, 255, 255, .1) 15%, transparent 20%) 8px 9px;\n      background-color: #282828;\n      background-size: 16px 16px;\n      background-attachment: fixed;\n    }\n\n    .arrow {\n      width: 250px;\n      overflow: hidden;\n      transition: all 0.2s;\n      transform: rotate(0deg);\n      display: inline-block;\n    }\n\n    h1 {\n      color: white;\n      font-weight: 100;\n      font-size: 60px;\n      display: flex;\n      align-items: center;\n    }\n\n    .units {\n      font-size: 15px;\n    }\n    /*Compass: https://thenounproject.com/search/?q=compass&i=592352*/\n  </style>\n</body>\n\n</html>\n```\n\n![](http://om1c35wrq.bkt.clouddn.com/WX20170813-114518@2x.png)\n\n\n## JS源码\n\n```js\n  <script>\n let arrow=document.querySelector(\".arrow\");\n      let speed=document.querySelector(\".speed-value\");\n    //geolocation 配置项\n      let options={\n        enableHeightAccuracy : false,//是否开启高精度定位，开启后刷新速度慢，耗电耗流量\n        timeout : 5000, //等待响应的最大时间，超时响应，默认是0毫秒，表示无穷时间，即无限制的等待响应。\n        maximumAge: 0  //表示应用程序的缓存时间。单位毫秒，默认是0，意味着每次请求都是立即去获取一个全新的对象内容。\n      }\n    //成功回调函数\n      function sucess(pos){\n        console.log(pos);\n        let crd = pos.coords;// 获取位置对象的热区，包括各种位置信息。\n        console.log(\"你当前的位置是：\");\n        console.log('Latitude:' + crd.latitude);//获取纬度\n        console.log('Longitude:' + crd.longitude);//获取经度\n        console.log('More or less' + crd.accuracy + 'meters');//获取测量经度误差\n        // 根据geolocation地理对象的位置信息来更新显示\n        arrow.style.transform=`rotate(${crd.heading}deg)`;//更新指南针的方向\n        speend.innerHTML=crd.speed;//速度值\n\n        arrow.style.transform=`rotate(30deg)`;//由于PC浏览器没有方向和速度感应器，所以赋值进行试验，更新指南针的方向\n        speed.innerHTML='30';\n      }\n    // 失败回调函数\n      function error(error){\n        console.log(error.code);\n      }\n\n    // 判断浏览器是否支持geolocation对象\n      if(navigator.geolocation){\n          navigator.geolocation.getCurrentPosition(sucess, error, options);\n      }else{\n          console.log(\"你的浏览器暂时不支持geolocation API\");\n      }\n  </script>\n```\n"
  },
  {
    "path": "21 - Geolocation/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Document</title>\n  <meta name=\"viewport\" content=\"width=device-width\">\n  <style>\n    html {\n      font-size: 100px;\n    }\n\n    body {\n      margin: 0;\n      font-family: sans-serif;\n      min-height: 100vh;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      flex-direction: column;\n      background: radial-gradient(black 15%, transparent 16%) 0 0,\n      radial-gradient(black 15%, transparent 16%) 8px 8px,\n      radial-gradient(rgba(255, 255, 255, .1) 15%, transparent 20%) 0 1px,\n      radial-gradient(rgba(255, 255, 255, .1) 15%, transparent 20%) 8px 9px;\n      background-color: #282828;\n      background-size: 16px 16px;\n      background-attachment: fixed;\n    }\n\n    .arrow {\n      width: 250px;\n      overflow: hidden;\n      transition: all 0.2s;\n      transform: rotate(0deg);\n      display: inline-block;\n    }\n\n    h1 {\n      color: white;\n      font-weight: 100;\n      font-size: 60px;\n      display: flex;\n      align-items: center;\n    }\n\n    .units {\n      font-size: 15px;\n    }\n    /*Compass: https://thenounproject.com/search/?q=compass&i=592352*/\n  </style>\n</head>\n<body>\n  <svg class=\"arrow\" xmlns=\"https://www.w3.org/2000/svg\" xmlns:xlink=\"https://www.w3.org/1999/xlink\" version=\"1.1\" x=\"0px\" y=\"0px\"\n    viewBox=\"0 0 64 64\" enable-background=\"new 0 0 64 64\" xml:space=\"preserve\">\n    <g>\n      <path fill=\"#ffffff\" d=\"M32,1.824C15.361,1.824,1.825,15.361,1.825,32c0,16.639,13.537,30.176,30.175,30.176   S62.175,48.639,62.175,32C62.175,15.361,48.639,1.824,32,1.824z M29.715,3.988h1.12l2.333,3.807V3.988h1.069v5.701h-1.155   l-2.298-3.718v3.718h-1.069V3.988z M9.323,33.917H8.102l-1.136-4.262l-1.132,4.262H4.587l-1.361-5.701h1.178l0.859,3.916   l1.042-3.916h1.369l0.999,3.982l0.875-3.982h1.159L9.323,33.917z M33.995,59.828c-0.181,0.285-0.438,0.497-0.77,0.636   c-0.332,0.139-0.745,0.208-1.241,0.208c-0.721,0-1.274-0.167-1.661-0.5c-0.386-0.333-0.617-0.819-0.692-1.456l1.12-0.109   c0.067,0.376,0.204,0.652,0.41,0.828c0.206,0.176,0.484,0.264,0.834,0.264c0.371,0,0.65-0.078,0.838-0.235   c0.188-0.157,0.282-0.34,0.282-0.55c0-0.135-0.04-0.25-0.119-0.344c-0.079-0.095-0.217-0.177-0.414-0.247   c-0.135-0.047-0.442-0.13-0.922-0.249c-0.617-0.153-1.05-0.341-1.299-0.564c-0.35-0.314-0.525-0.696-0.525-1.147   c0-0.29,0.082-0.562,0.247-0.815c0.165-0.253,0.402-0.445,0.712-0.577c0.31-0.132,0.684-0.198,1.122-0.198   c0.716,0,1.254,0.157,1.616,0.471c0.362,0.314,0.552,0.733,0.57,1.256l-1.151,0.051c-0.049-0.293-0.155-0.504-0.317-0.632   c-0.162-0.128-0.405-0.193-0.729-0.193c-0.334,0-0.596,0.069-0.786,0.206c-0.122,0.088-0.183,0.206-0.183,0.354   c0,0.135,0.057,0.25,0.171,0.346c0.145,0.122,0.498,0.249,1.058,0.381c0.56,0.132,0.974,0.269,1.243,0.41   c0.268,0.141,0.478,0.334,0.63,0.58c0.152,0.245,0.227,0.548,0.227,0.908C34.267,59.237,34.176,59.543,33.995,59.828z M32,52.795   c-11.466,0-20.795-9.329-20.795-20.795c0-11.466,9.329-20.795,20.795-20.795S52.795,20.534,52.795,32   C52.795,43.466,43.466,52.795,32,52.795z M55.014,33.917v-5.701h4.227v0.965h-3.076v1.264h2.862v0.96h-2.862v1.552h3.185v0.961   H55.014z\"/>\n      <g>\n        <path fill=\"#000000\" d=\"M48.904,31.863c-4.074-1.358-8.148-2.717-12.226-4.066c-0.265-0.087-0.399-0.223-0.486-0.486    c-1.349-4.077-2.708-8.151-4.066-12.226c-0.029-0.087-0.074-0.169-0.132-0.3c-0.054,0.152-0.09,0.245-0.122,0.34    c-1.352,4.053-2.707,8.104-4.048,12.161c-0.096,0.292-0.246,0.428-0.532,0.522c-4.056,1.342-8.108,2.696-12.16,4.049    c-0.097,0.032-0.189,0.074-0.344,0.137c0.172,0.06,0.267,0.093,0.362,0.125c4.074,1.358,8.148,2.717,12.224,4.072    c0.204,0.068,0.337,0.158,0.412,0.386c1.243,3.757,2.498,7.511,3.75,11.265c0.144,0.432,0.291,0.862,0.463,1.373    c0.068-0.185,0.108-0.285,0.142-0.386c1.349-4.042,2.701-8.083,4.04-12.128c0.094-0.284,0.231-0.438,0.523-0.534    c4.056-1.341,8.108-2.696,12.161-4.048c0.099-0.033,0.195-0.076,0.347-0.137C49.067,31.925,48.987,31.891,48.904,31.863z     M37.475,32.038c-1.316,0.439-2.631,0.879-3.947,1.314c-0.095,0.031-0.139,0.081-0.17,0.173c-0.434,1.313-0.873,2.625-1.311,3.937    c-0.012,0.033-0.024,0.066-0.046,0.126c-0.056-0.166-0.104-0.306-0.15-0.446c-0.407-1.219-0.814-2.437-1.218-3.657    c-0.025-0.074-0.068-0.104-0.134-0.125c-1.323-0.44-2.646-0.881-3.968-1.322c-0.031-0.01-0.062-0.022-0.118-0.041    c0.05-0.02,0.081-0.034,0.112-0.045c1.315-0.439,2.631-0.879,3.947-1.314c0.093-0.03,0.142-0.075,0.173-0.17    c0.435-1.316,0.875-2.632,1.314-3.947c0.01-0.031,0.022-0.062,0.039-0.11c0.019,0.042,0.033,0.069,0.043,0.097    c0.441,1.323,0.882,2.645,1.321,3.969c0.028,0.085,0.072,0.129,0.158,0.158c1.324,0.438,2.646,0.879,3.969,1.32    c0.027,0.009,0.053,0.02,0.1,0.038C37.538,32.013,37.507,32.027,37.475,32.038z\"/>\n        <path fill=\"#000000\" d=\"M24.436,27.633c-1.069-2.137-2.119-4.237-3.216-6.43c2.189,1.094,4.292,2.145,6.433,3.216    c-0.359,0.713-0.706,1.404-1.057,2.091c-0.023,0.045-0.078,0.082-0.127,0.106C25.807,26.949,25.143,27.28,24.436,27.633z\"/>\n        <path fill=\"#000000\" d=\"M39.573,27.632c-0.696-0.348-1.351-0.673-2.002-1.005c-0.076-0.038-0.155-0.104-0.193-0.177    c-0.338-0.661-0.666-1.326-1.019-2.033c2.121-1.061,4.228-2.115,6.43-3.217C41.69,23.399,40.635,25.509,39.573,27.632z\"/>\n        <path fill=\"#000000\" d=\"M24.436,36.339c0.712,0.357,1.394,0.698,2.074,1.043c0.046,0.024,0.088,0.073,0.113,0.121    c0.339,0.671,0.674,1.345,1.028,2.051c-2.126,1.063-4.232,2.117-6.43,3.216C22.317,40.577,23.37,38.472,24.436,36.339z\"/>\n        <path fill=\"#000000\" d=\"M36.358,39.555c0.354-0.707,0.688-1.38,1.028-2.05c0.028-0.056,0.084-0.111,0.14-0.139    c0.67-0.339,1.343-0.674,2.047-1.026c1.066,2.131,2.118,4.235,3.215,6.43C40.601,41.676,38.503,40.628,36.358,39.555z\"/>\n      </g>\n    </g>\n  </svg>\n  <h1 class=\"speed\">\n    <span class=\"speed-value\">0</span>\n    <span class=\"units\">KM/H</span>\n  </h1>\n  <script>\n      let arrow=document.querySelector(\".arrow\");\n      let speed=document.querySelector(\".speed-value\");\n    //geolocation 配置项\n      let options={\n        enableHeightAccuracy : false,//是否开启高精度定位，开启后刷新速度慢，耗电耗流量\n        timeout : 5000, //等待响应的最大时间，超时响应，默认是0毫秒，表示无穷时间，即无限制的等待响应。\n        maximumAge: 0  //表示应用程序的缓存时间。单位毫秒，默认是0，意味着每次请求都是立即去获取一个全新的对象内容。\n      }\n    //成功回调函数\n      function sucess(pos){\n        console.log(pos);\n        let crd = pos.coords;// 获取位置对象的热区，包括各种位置信息。\n        console.log(\"你当前的位置是：\");\n        console.log('Latitude:' + crd.latitude);//获取纬度\n        console.log('Longitude:' + crd.longitude);//获取经度\n        console.log('More or less' + crd.accuracy + 'meters');//获取测量经度误差\n        // 根据geolocation地理对象的位置信息来更新显示\n        arrow.style.transform=`rotate(${crd.heading}deg)`;//更新指南针的方向\n        speend.innerHTML=crd.speed;//速度值\n\n        arrow.style.transform=`rotate(30deg)`;//由于PC浏览器没有方向和速度感应器，所以赋值进行试验，更新指南针的方向\n        speed.innerHTML='30';\n      }\n    // 失败回调函数\n      function error(error){\n        console.log(error.code);\n      }\n\n    // 判断浏览器是否支持geolocation对象\n      if(navigator.geolocation){\n          navigator.geolocation.getCurrentPosition(sucess, error, options);\n      }else{\n          console.log(\"你的浏览器暂时不支持geolocation API\");\n      }\n  </script>\n</body>\n</html>"
  },
  {
    "path": "21 - Geolocation/package.json",
    "content": "{\n  \"name\": \"gum\",\n  \"version\": \"1.0.0\",\n  \"description\": \"\",\n  \"main\": \"scripts.js\",\n  \"scripts\": {\n    \"start\": \"browser-sync start --directory --server --files '*.css, *.html, *.js' --https\"\n  },\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"devDependencies\": {\n    \"browser-sync\": \"^2.12.5\"\n  }\n}\n"
  },
  {
    "path": "22 - Follow Along Link Highlighter/README.md",
    "content": "# Day22 - 鼠标锚点动画生成指南\n\n## 效果图\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/22%20-%20Follow%20Along%20Link%20Highlighter/SHOWGIF.gif)\n\n第22天的练习是一个动画练习，当鼠标移动到锚点处，会有一个白色的色块移动到当前锚点所在的位置。演示图如下所示：\n\n### 解决思路：  \n实际上是通过一个背景颜色Wie白色的小块进行覆盖导航元素这个白色小块的宽高及位置有getBoundingClientRect来获取，鼠标进入不同的元素就获取该元素的宽高和位置数据，然后赋值给这个白色小块，则白色小块定位到该元素位置处，作为背景来使用。\n\n\n## JS源码\n\n```js\n    <script>\n        let A=[...document.querySelectorAll(\"a\")];\n        A.forEach(item=>item.addEventListener(\"mouseenter\",addHightLight));\n        //创建类名为hightLight的白色小块 -->\n        let SPAN=document.createElement(\"span\");\n        SPAN.setAttribute(\"class\",\"hightlight\");\n        document.body.appendChild(SPAN);\n        SPAN.style.display=\"none\";\n\n        function addHightLight(){\n            let RECT=this.getBoundingClientRect();\n            let rects={\n                width:RECT.width,\n                height:RECT.height,\n                left:RECT.left+window.scrollX,\n                top:RECT.top+window.scrollY\n            }\n            // SPAN.style=`width:${rects.width},height:${rects.height},left:${rects.left},top:${rects.top}`;\n            // SPAN.style.top=\n            // SPAN.style.width = `${rects.width}px`;\n            // SPAN.style.height = `${rects.height}px`;\n            // // SPAN.style.transform = `translate(${rects.left}px, ${rects.top}px)`;用了位置移动，等价于下面的这两条语句\n            // SPAN.style.left = `${rects.left}px`;\n            // SPAN.style.top = `${rects.top}px`;\n            // SPAN.style.display=\"block\";\n// 采用cssText写法片段式样式改变，可以减少回流和重绘，上述的语句没改变一个样式都会触发回流和重绘。\n            SPAN.style.cssText=`width:${rects.width}px;height:${rects.height}px;left:${rects.left}px;top:${rects.top}px;display:block;`;\n\n        }\n    </script>\n```\n\n## 代码解析\n\n- 通过HTML源码我们不难发现，所有锚点都是由`a`标签组成，所以在`js`代码中我们首先先获取所有的`a`标签对象，返回一个伪数组，用扩展运算符将其真正数组化，将其存储到`triggers`变量中，然后给每个a标签添加鼠标进入监听事件。\n\n```js\n        let A=[...document.querySelectorAll(\"a\")];\n        A.forEach(item=>item.addEventListener(\"mouseenter\",addHightLight));\n```\n- 在效果图中高亮状态的小块其实就是一个`span`标签，在JS代码中创建了一个`span`标签，并且为其添加了一个`highlight`的`class`，由于hightlight的样式中\n是绝对定位，并且top和left的值均为0；那么在初始化时是位于左上角的，通过display进行隐藏，然后在改变之后在进行显示，中间加上过渡效果。\n```js\n       //创建类名为hightLight的白色小块 -->\n        let SPAN=document.createElement(\"span\");\n        SPAN.setAttribute(\"class\",\"hightlight\");//或者使用SPAN.classList.add(\"hightlight\");\n        document.body.appendChild(SPAN);\n        SPAN.style.display=\"none\"; \n```\n\n- `getBoundingClientRect()`\n\n[getBoundingClientRect](https://blog.csdn.net/qq_39207948/article/details/88147479)\n\n`Element.getBoundingClientRect()`方法返回元素的大小及其相对于视口的位置。\n\n**语法：**\n\n```js\nrectObject = object.getBoundingClientRect();\n```\n\n**值：**\n返回值是一个 [DOMRect](https://developer.mozilla.org/zh-CN/docs/Mozilla/Tech/XPCOM/Reference/Interface/nsIDOMClientRect) 对象，这个对象是由该元素的 [getClientRects()](https://developer.mozilla.org/zh-CN/docs/Web/API/Element/getClientRects) 方法返回的一组矩形的集合, 即：是与该元素相关的CSS 边框集合 。\n\n**DOMRect属性表：**\n\n|属性|类型|描述|\n|:-----------|:-----------:|:-----------|\n|bottom|float|Y 轴，相对于视口原点（viewport origin）矩形盒子的底部。**只读**。 |\n|height|float|矩形盒子的高度（等同于 bottom 减 top）。**只读**。|\n|left|float|X 轴，相对于视口原点（viewport origin）矩形盒子的左侧。**只读**。 |\n|right|float|X 轴，相对于视口原点（viewport origin）矩形盒子的右侧。**只读**。 |\n|top|float|Y 轴，相对于视口原点（viewport origin）矩形盒子的顶部。**只读**。|\n|width|float|矩形盒子的宽度（等同于 right 减 left）。**只读**。 |\n|x|float|X轴横坐标，矩形盒子左边相对于视口原点（viewport origin）的距离。**只读**。|\n|y|float|Y轴纵坐标，矩形盒子顶部相对于视口原点（viewport origin）的距离。**只读**。|\n\n\n`DOMRect` 对象包含了一组用于描述边框的只读属性——left、top、right和bottom，单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。\n\n![](http://om1c35wrq.bkt.clouddn.com/day22-rect.png)\n\n空边框盒（译者注：没有内容的边框）会被忽略。如果所有的元素边框都是空边框，那么这个矩形给该元素返回的 width、height 值为0，left、top值为第一个css盒子（按内容顺序）的top-left值。\n\n当计算边界矩形时，会考虑视口区域（或其他可滚动元素）内的滚动操作，也就是说，当滚动位置发生了改变，top和left属性值就会随之立即发生变化（因此，它们的值是相对于视口的，而不是绝对的）。如果不希望属性值随视口变化，那么只要给top、left属性值加上当前的滚动位置（通过window.scrollX和window.scrollY），这样就可以获取与当前的滚动位置无关的常量值。\n\n- `highlightLink`方法\n\n```js\n    function addHightLight(){\n        let RECT=this.getBoundingClientRect();  \n        let rects={\n            width:RECT.width,\n            height:RECT.height,\n            left:RECT.left+window.scrollX,\n            top:RECT.top+window.scrollY\n        }\n        // SPAN.style=`width:${rects.width},height:${rects.height},left:${rects.left},top:${rects.top}`;\n        // SPAN.style.top=\n        // SPAN.style.width = `${rects.width}px`;\n        // SPAN.style.height = `${rects.height}px`;\n        // // SPAN.style.transform = `translate(${rects.left}px, ${rects.top}px)`;用了位置移动，等价于下面的这两条语句\n        // SPAN.style.left = `${rects.left}px`;\n        // SPAN.style.top = `${rects.top}px`;\n        // SPAN.style.display=\"block\";\n// 采用cssText写法片段式样式改变，可以减少回流和重绘，上述的语句没改变一个样式都会触发回流和重绘。\n        SPAN.style.cssText=`width:${rects.width}px;height:${rects.height}px;left:${rects.left}px;top:${rects.top}px;display:block;`;\n\n    }\n```\n> 给a元素添加高亮背景的函数。获取到当前进入的a元素，然后将该元素的top\\left\\width\\height用一个对象进行保存。然后将span的白色小块的样式位置进行改变，移动到鼠标进入的这个元素。  \n\n\n- <nav> 标签定义导航链接的部分。  \n\n并不是所有的 HTML 文档都要使用到 <nav> 元素。<nav> 元素只是作为标注一个导航链接的区域。\n## 所有的工作做完，上面的这套程序存在bug，当鼠标进入一个页面，出现白色背景块时，此时改变页面的大小，白色背景块的位置出现错位，相对于视口的位置不改变。\n"
  },
  {
    "path": "22 - Follow Along Link Highlighter/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"ie=edge\">\n    <title>随鼠标滑动小块</title>\n    <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n    <nav>\n        <ul class=\"menu\">\n            <li><a href=\"\">Home</a></li>\n            <li><a href=\"\">Order Status</a></li>\n            <li><a href=\"\">Tweets</a></li>\n            <li><a href=\"\">Read Our History</a></li>\n            <li><a href=\"\">Contact Us</a></li>\n        </ul>\n    </nav>\n    <div class=\"wrapper\">\n        <p>Lorem ipsum dolor sit amet, <a href=\"\">consectetur</a> adipisicing elit. Est <a href=\"\">explicabo</a> unde natus necessitatibus esse obcaecati distinctio, aut itaque, qui vitae!</p>\n        <p>Aspernatur sapiente quae sint <a href=\"\">soluta</a> modi, atque praesentium laborum pariatur earum <a href=\"\">quaerat</a> cupiditate consequuntur facilis ullam dignissimos, aperiam quam veniam.</p>\n        <p>Cum ipsam quod, incidunt sit ex <a href=\"\">tempore</a> placeat maxime <a href=\"\">corrupti</a> possimus <a href=\"\">veritatis</a> ipsum fugit recusandae est doloremque? Hic, <a href=\"\">quibusdam</a>, nulla.</p>\n        <p>Esse quibusdam, ad, ducimus cupiditate <a href=\"\">nulla</a>, quae magni odit <a href=\"\">totam</a> ut consequatur eveniet sunt quam provident sapiente dicta neque quod.</p>\n        <p>Aliquam <a href=\"\">dicta</a> sequi culpa fugiat <a href=\"\">consequuntur</a> pariatur optio ad minima, maxime <a href=\"\">odio</a>, distinctio magni impedit tempore enim repellendus <a href=\"\">repudiandae</a> quas!</p>\n    </div>\n    <script>\n        window.onresize=function(){\n            // location.reload();//页面进行刷新\n            SPAN.style.display=\"none\";//将span进行隐藏，触发一次回流和重排\n        }\n        let A=[...document.querySelectorAll(\"a\")];\n        A.forEach(item=>item.addEventListener(\"mouseenter\",addHightLight));\n        //创建类名为hightLight的白色小块 -->\n        let SPAN=document.createElement(\"span\");\n        SPAN.setAttribute(\"class\",\"hightlight\");//或者使用SPAN.classList.add(\"hightlight\");\n        document.body.appendChild(SPAN);\n\n\n        SPAN.style.display=\"none\";\n\n        function addHightLight(){\n            let RECT=this.getBoundingClientRect();\n            let rects={\n                width:RECT.width,\n                height:RECT.height,\n                left:RECT.left+window.scrollX,\n                top:RECT.top+window.scrollY\n            }\n            // SPAN.style=`width:${rects.width},height:${rects.height},left:${rects.left},top:${rects.top}`;\n            // SPAN.style.top=\n            // SPAN.style.width = `${rects.width}px`;\n            // SPAN.style.height = `${rects.height}px`;\n            // // SPAN.style.transform = `translate(${rects.left}px, ${rects.top}px)`;用了位置移动，等价于下面的这两条语句\n            // SPAN.style.left = `${rects.left}px`;\n            // SPAN.style.top = `${rects.top}px`;\n            // SPAN.style.display=\"block\";\n// 采用cssText写法片段式样式改变，可以减少回流和重绘，上述的语句没改变一个样式都会触发回流和重绘。\n            SPAN.style.cssText=`width:${rects.width}px;height:${rects.height}px;left:${rects.left}px;top:${rects.top}px;display:block;`;\n\n        }\n    </script>\n    \n</body>\n</html>\n<!-- 实际上是通过一个背景颜色Wie白色的小块进行覆盖导航元素\n这个白色小块的宽高及位置有getBoundingClientRect来获取，\n鼠标进入不同的元素就获取该元素的宽高和位置数据，然后赋值给这个白色小块，\n则白色小块定位到该元素位置处，作为背景来使用。\n-->"
  },
  {
    "path": "22 - Follow Along Link Highlighter/style.css",
    "content": "html {\n    box-sizing: border-box;\n  }\n  *, *:before, *:after {\n    box-sizing: inherit;\n  }\n  body {\n    min-height: 100vh;\n    margin: 0; /* Important! */\n    font-family: sans-serif;\n    background:\n      linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%),\n      linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%),\n      linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%),\n      linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%);\n  }\n  .wrapper {\n    margin:0 auto;\n    max-width:700px;\n    font-size: 20px;\n    line-height: 2; /*以字体的两倍高度设置行间距*/\n    position: relative;\n  }\n  a {\n    text-decoration: none;\n    padding:5px;\n    color:black;\n    background:rgba(0,0,0,0.05);\n    border-radius: 20px\n  }\n  \n  .menu {\n    padding: 0;\n    display: flex;\n    list-style: none;\n    justify-content: center;\n    margin:100px 0;\n  }\n  .menu a {\n    display: inline-block;\n    margin:0 20px;\n    color:black;\n  }\n   /* 添加的动态类 */\n  .hightlight {\n    transition: all 0.2s;/*所有元素的过渡效果在0.2s内完成*/\n    position: absolute;\n    top:0;\n    left:0;\n    background:white;\n    display: block;\n    border-bottom:2px solid white;\n    border-radius:20px;\n    box-shadow: 0 0 10px rgba(0,0,0,0.2);\n    z-index: -1;\n  }\n  "
  },
  {
    "path": "23 - Speech Synthesis/23 - Speech Synthesis/README.md",
    "content": "\n# Day23 - Speech Synthesis\n\n## 效果图\n\n![](http://om1c35wrq.bkt.clouddn.com/Snip20170813_1.png)\n\n第23天要做一个语音的记事本类似的场景，输入一段内容，选择不同的语言可以进行朗读。还可以选择不同的语速和语调。\n\n## 基础知识\n\n#### 一、示例\n\n[speak-easy-synthesis](https://github.com/mdn/web-speech-api/tree/master/speak-easy-synthesis)\n\n```js\nvar synth = window.speechSynthesis;\n\nvar inputForm = document.querySelector('form');\nvar inputTxt = document.querySelector('.txt');\nvar voiceSelect = document.querySelector('select');\n\nvar pitch = document.querySelector('#pitch');\nvar pitchValue = document.querySelector('.pitch-value');\nvar rate = document.querySelector('#rate');\nvar rateValue = document.querySelector('.rate-value');\n\nvar voices = [];\n\nfunction populateVoiceList() {\n  voices = synth.getVoices();\n\n  for(i = 0; i < voices.length ; i++) {\n    var option = document.createElement('option');\n    option.textContent = voices[i].name + ' (' + voices[i].lang + ')';\n    \n    if(voices[i].default) {\n      option.textContent += ' -- DEFAULT';\n    }\n\n    option.setAttribute('data-lang', voices[i].lang);\n    option.setAttribute('data-name', voices[i].name);\n    voiceSelect.appendChild(option);\n  }\n}\n\npopulateVoiceList();\nif (speechSynthesis.onvoiceschanged !== undefined) {\n  speechSynthesis.onvoiceschanged = populateVoiceList;\n}\n\ninputForm.onsubmit = function(event) {\n  event.preventDefault();\n\n  var utterThis = new SpeechSynthesisUtterance(inputTxt.value);\n  var selectedOption = voiceSelect.selectedOptions[0].getAttribute('data-name');\n  for(i = 0; i < voices.length ; i++) {\n    if(voices[i].name === selectedOption) {\n      utterThis.voice = voices[i];\n    }\n  }\n  utterThis.pitch = pitch.value;\n  utterThis.rate = rate.value;\n  synth.speak(utterThis);\n\n  inputTxt.blur();\n}\n```\n\n![](http://om1c35wrq.bkt.clouddn.com/Snip20170813_3.png)\n\n#### 二、SpeechSynthesis\n\n[参考文档](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesis)\n\n\n##### 1、属性\n\n- `SpeechSynthesis.paused`（只读）\n\n判断是否是处于暂停状态。\n\n- `SpeechSynthesis.pending` (只读)\n\n判断是否处于等待状态。\n\n- `SpeechSynthesis.speaking ` (只读)\n\n判断是否处于在读中。\n\n##### 2、事件\n\n- `SpeechSynthesis.onvoiceschanged`\n\n监听翻译的语言是否发生了变化。\n\n#### 3、方法\n\n- `SpeechSynthesis.cancel()`\n\n取消。\n\n- `SpeechSynthesis.getVoices()`\n\n获取所有当前设备支持的`SpeechSynthesisVoice `对象。\n\n- `SpeechSynthesis.pause()`\n\n暂停。\n\n- `SpeechSynthesis.resume()`\n\n恢复。\n\n- `SpeechSynthesis.speak()`\n\n开始语音读取。\n\n#### 三、SpeechSynthesisUtterance \n\n[参考文档](https://developer.mozilla.org/en-US/docs/Web/API/SpeechSynthesisUtterance)\n\n##### 1、构造函数\n\n- `SpeechSynthesisUtterance.SpeechSynthesisUtterance()`\n\n返回一个新的`SpeechSynthesisUtterance`对象实例。\n\n##### 2、属性\n\n- SpeechSynthesisUtterance.lang\n\n获取或者是设置`utterance`的语言。\n\n- SpeechSynthesisUtterance.pitch\n\n获取或者是设置`utterance`的音高。\n\n- SpeechSynthesisUtterance.rate\n\n获取或者是设置`utterance`的播放速率。\n\n\n- SpeechSynthesisUtterance.text\n\n获取或者是设置`utterance`需要播放的文本内容。\n\n\n- SpeechSynthesisUtterance.voice\n\n获得或设定将被用来说话的声音。\n\n- SpeechSynthesisUtterance.volume\n\n获取或者是设置`utterance`的播放音量。\n\n## HTML源码\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Speech Synthesis</title>\n  <link href='https://fonts.googleapis.com/css?family=Pacifico' rel='stylesheet' type='text/css'>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n    <div class=\"voiceinator\">\n\n      <h1>The Voiceinator 5000</h1>\n\n      <select name=\"voice\" id=\"voices\">\n        <option value=\"\">Select A Voice</option>\n      </select>\n\n      <label for=\"rate\">Rate:</label>\n      <input name=\"rate\" type=\"range\" min=\"0\" max=\"3\" value=\"1\" step=\"0.1\">\n\n      <label for=\"pitch\">Pitch:</label>\n\n      <input name=\"pitch\" type=\"range\" min=\"0\" max=\"2\" step=\"0.1\">\n      <textarea name=\"text\">Hello! I love JavaScript 👍</textarea>\n      <button id=\"stop\">Stop!</button>\n      <button id=\"speak\">Speak</button>\n\n    </div>\n</body>\n</html>\n```\n\n## CSS源码\n\n```css\nhtml {\n  font-size: 10px;\n  box-sizing: border-box;\n}\n\n*, *:before, *:after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: sans-serif;\n  background-color:#3BC1AC;\n  display:flex;\n  min-height: 100vh;\n  align-items: center;\n\n  background-image:\n  radial-gradient(circle at 100% 150%, #3BC1AC 24%, #42D2BB 25%, #42D2BB 28%, #3BC1AC 29%, #3BC1AC 36%, #42D2BB 36%, #42D2BB 40%, transparent 40%, transparent),\n  radial-gradient(circle at 0    150%, #3BC1AC 24%, #42D2BB 25%, #42D2BB 28%, #3BC1AC 29%, #3BC1AC 36%, #42D2BB 36%, #42D2BB 40%, transparent 40%, transparent),\n  radial-gradient(circle at 50%  100%, #42D2BB 10%, #3BC1AC 11%, #3BC1AC 23%, #42D2BB 24%, #42D2BB 30%, #3BC1AC 31%, #3BC1AC 43%, #42D2BB 44%, #42D2BB 50%, #3BC1AC 51%, #3BC1AC 63%, #42D2BB 64%, #42D2BB 71%, transparent 71%, transparent),\n  radial-gradient(circle at 100% 50%, #42D2BB 5%, #3BC1AC 6%, #3BC1AC 15%, #42D2BB 16%, #42D2BB 20%, #3BC1AC 21%, #3BC1AC 30%, #42D2BB 31%, #42D2BB 35%, #3BC1AC 36%, #3BC1AC 45%, #42D2BB 46%, #42D2BB 49%, transparent 50%, transparent),\n  radial-gradient(circle at 0    50%, #42D2BB 5%, #3BC1AC 6%, #3BC1AC 15%, #42D2BB 16%, #42D2BB 20%, #3BC1AC 21%, #3BC1AC 30%, #42D2BB 31%, #42D2BB 35%, #3BC1AC 36%, #3BC1AC 45%, #42D2BB 46%, #42D2BB 49%, transparent 50%, transparent);\n  background-size:100px 50px;\n}\n\n\n.voiceinator {\n  padding:2rem;\n  width:50rem;\n  margin:0 auto;\n  border-radius:1rem;\n  position: relative;\n  background:white;\n  overflow: hidden;\n  z-index: 1;\n  box-shadow:0 0 5px 5px rgba(0,0,0,0.1);\n}\n\nh1 {\n  width:calc(100% + 4rem);\n  margin: -2rem 0 2rem -2rem;\n  padding:.5rem;\n  background: #ffc600;\n  border-bottom: 5px solid #F3C010;\n  text-align: center;\n  font-size: 5rem;\n  font-weight: 100;\n  font-family: 'Pacifico', cursive;\n  text-shadow:3px 3px 0 #F3C010;\n\n}\n\n.voiceinator input,\n.voiceinator button,\n.voiceinator select,\n.voiceinator textarea {\n  width: 100%;\n  display: block;\n  margin:10px 0;\n  padding:10px;\n  border:0;\n  font-size: 2rem;\n  background:#F7F7F7;\n  outline:0;\n}\n\ntextarea {\n  height: 20rem;\n}\n\ninput[type=\"select\"] {\n\n}\n\n.voiceinator button {\n  background:#ffc600;\n  border:0;\n  width: 49%;\n  float:left;\n  font-family: 'Pacifico', cursive;\n  margin-bottom: 0;\n  font-size: 2rem;\n  border-bottom: 5px solid #F3C010;\n  cursor:pointer;\n  position: relative;\n}\n\n.voiceinator button:active {\n  top:2px;\n}\n\n.voiceinator button:nth-of-type(1) {\n  margin-right: 2%;\n}\n```\n\n\n## JS源码\n\n```Javascript\n  // 实例化一个语音对象，并获得页面上的各DOM元素\n  const msg = new SpeechSynthesisUtterance();\n  const synth = window.speechSynthesis;\n  let voices = [];\n  const voicesDropdown = document.querySelector('[name=\"voice\"]');\n  const options = document.querySelectorAll('[type=\"range\"], [name=\"text\"]');\n  const speakButton = document.querySelector('#speak');\n  const stopButton = document.querySelector('#stop');\n  msg.text = document.querySelector('[name=\"text\"]').value;\n\n  // 设置各种语言的下拉选择框\n  function populateVoices() {\n    voices = this.getVoices();\n    voicesDropdown.innerHTML = voices\n      .filter(voice => voice.lang.includes('en'))\n      .map(voice => `<option value=\"${voice.name}\">${voice.name} (${voice.lang})</option>`)\n      .join('');\n  }\n\n  // 设置当前语音的语言\n  function setVoice() {\n    msg.voice = voices.find(voice => voice.name === this.value);\n    toggle();\n  }\n\n  // 切换语音的播放和暂停\n  function toggle(startOver = true) {\n    synth.cancel();\n    if (startOver) {\n      synth.speak(msg);\n    }\n  }\n\n  // 设置语音的语速和语调\n  function setOption() {\n    console.log(this.name, this.value);\n    msg[this.name] = this.value;\n    toggle();\n  }\n\n  // 监听语音对象的语言改变的事件\n  synth.addEventListener('voiceschanged', populateVoices);\n  // 当切换语言选择下拉菜单时被调用\n  voicesDropdown.addEventListener('change', setVoice);\n  // 为语速和语调设置改变的事件监听\n  options.forEach(option => option.addEventListener('change', setOption));\n  // 分别监听播放和暂停事件\n  speakButton.addEventListener('click', toggle);\n  stopButton.addEventListener('click', () => toggle(false));\n```\n\n\n\n\n"
  },
  {
    "path": "23 - Speech Synthesis/23 - Speech Synthesis/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Speech Synthesis</title>\n  <link href='https://fonts.googleapis.com/css?family=Pacifico' rel='stylesheet' type='text/css'>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <div class=\"voiceinator\">\n\n    <h1>The Voiceinator 5000</h1>\n\n    <select name=\"voice\" id=\"voices\">\n        <option value=\"\">Select A Voice</option>\n      </select>\n\n    <label for=\"rate\">Rate:</label>\n    <input name=\"rate\" type=\"range\" min=\"0\" max=\"3\" value=\"1\" step=\"0.1\">\n\n    <label for=\"pitch\">Pitch:</label>\n\n    <input name=\"pitch\" type=\"range\" min=\"0\" max=\"2\" step=\"0.1\">\n    <textarea name=\"text\">Hello! I love JavaScript 👍</textarea>\n    <button id=\"stop\">Stop!</button>\n    <button id=\"speak\">Speak</button>\n\n  </div>\n\n  <script>\n    // 实例化一个语音对象，并获得页面上的各DOM元素\n    const msg = new SpeechSynthesisUtterance();\n    const synth = window.speechSynthesis;\n    let voices = [];\n    const voicesDropdown = document.querySelector('[name=\"voice\"]');\n    const options = document.querySelectorAll('[type=\"range\"], [name=\"text\"]');\n    const speakButton = document.querySelector('#speak');\n    const stopButton = document.querySelector('#stop');\n    msg.text = document.querySelector('[name=\"text\"]').value;\n\n    // 设置各种语言的下拉选择框\n    function populateVoices() {\n      voices = this.getVoices();\n      voicesDropdown.innerHTML = voices\n        .filter(voice => voice.lang.includes('en'))\n        .map(voice => `<option value=\"${voice.name}\">${voice.name} (${voice.lang})</option>`)\n        .join('');\n    }\n\n    // 设置当前语音的语言\n    function setVoice() {\n      msg.voice = voices.find(voice => voice.name === this.value);\n      toggle();\n    }\n\n    // 切换语音的播放和暂停\n    function toggle(startOver = true) {\n      synth.cancel();\n      if (startOver) {\n        synth.speak(msg);\n      }\n    }\n\n    // 设置语音的语速和语调\n    function setOption() {\n      console.log(this.name, this.value);\n      msg[this.name] = this.value;\n      toggle();\n    }\n\n    // 监听语音对象的语言改变的事件\n    synth.addEventListener('voiceschanged', populateVoices);\n    // 当切换语言选择下拉菜单时被调用\n    voicesDropdown.addEventListener('change', setVoice);\n    // 为语速和语调设置改变的事件监听\n    options.forEach(option => option.addEventListener('change', setOption));\n    // 分别监听播放和暂停事件\n    speakButton.addEventListener('click', toggle);\n    stopButton.addEventListener('click', () => toggle(false));\n    \n  </script>\n\n</body>\n\n</html>"
  },
  {
    "path": "23 - Speech Synthesis/23 - Speech Synthesis/style.css",
    "content": "html {\n  font-size: 10px;\n  box-sizing: border-box;\n}\n\n*, *:before, *:after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n  padding: 0;\n  font-family: sans-serif;\n  background-color:#3BC1AC;\n  display:flex;\n  min-height: 100vh;\n  align-items: center;\n\n  background-image:\n  radial-gradient(circle at 100% 150%, #3BC1AC 24%, #42D2BB 25%, #42D2BB 28%, #3BC1AC 29%, #3BC1AC 36%, #42D2BB 36%, #42D2BB 40%, transparent 40%, transparent),\n  radial-gradient(circle at 0    150%, #3BC1AC 24%, #42D2BB 25%, #42D2BB 28%, #3BC1AC 29%, #3BC1AC 36%, #42D2BB 36%, #42D2BB 40%, transparent 40%, transparent),\n  radial-gradient(circle at 50%  100%, #42D2BB 10%, #3BC1AC 11%, #3BC1AC 23%, #42D2BB 24%, #42D2BB 30%, #3BC1AC 31%, #3BC1AC 43%, #42D2BB 44%, #42D2BB 50%, #3BC1AC 51%, #3BC1AC 63%, #42D2BB 64%, #42D2BB 71%, transparent 71%, transparent),\n  radial-gradient(circle at 100% 50%, #42D2BB 5%, #3BC1AC 6%, #3BC1AC 15%, #42D2BB 16%, #42D2BB 20%, #3BC1AC 21%, #3BC1AC 30%, #42D2BB 31%, #42D2BB 35%, #3BC1AC 36%, #3BC1AC 45%, #42D2BB 46%, #42D2BB 49%, transparent 50%, transparent),\n  radial-gradient(circle at 0    50%, #42D2BB 5%, #3BC1AC 6%, #3BC1AC 15%, #42D2BB 16%, #42D2BB 20%, #3BC1AC 21%, #3BC1AC 30%, #42D2BB 31%, #42D2BB 35%, #3BC1AC 36%, #3BC1AC 45%, #42D2BB 46%, #42D2BB 49%, transparent 50%, transparent);\n  background-size:100px 50px;\n}\n\n\n.voiceinator {\n  padding:2rem;\n  width:50rem;\n  margin:0 auto;\n  border-radius:1rem;\n  position: relative;\n  background:white;\n  overflow: hidden;\n  z-index: 1;\n  box-shadow:0 0 5px 5px rgba(0,0,0,0.1);\n}\n\nh1 {\n  width:calc(100% + 4rem);\n  margin: -2rem 0 2rem -2rem;\n  padding:.5rem;\n  background: #ffc600;\n  border-bottom: 5px solid #F3C010;\n  text-align: center;\n  font-size: 5rem;\n  font-weight: 100;\n  font-family: 'Pacifico', cursive;\n  text-shadow:3px 3px 0 #F3C010;\n\n}\n\n.voiceinator input,\n.voiceinator button,\n.voiceinator select,\n.voiceinator textarea {\n  width: 100%;\n  display: block;\n  margin:10px 0;\n  padding:10px;\n  border:0;\n  font-size: 2rem;\n  background:#F7F7F7;\n  outline:0;\n}\n\ntextarea {\n  height: 20rem;\n}\n\ninput[type=\"select\"] {\n\n}\n\n.voiceinator button {\n  background:#ffc600;\n  border:0;\n  width: 49%;\n  float:left;\n  font-family: 'Pacifico', cursive;\n  margin-bottom: 0;\n  font-size: 2rem;\n  border-bottom: 5px solid #F3C010;\n  cursor:pointer;\n  position: relative;\n}\n\n.voiceinator button:active {\n  top:2px;\n}\n\n.voiceinator button:nth-of-type(1) {\n  margin-right: 2%;\n}\n"
  },
  {
    "path": "23 - Speech Synthesis/speak-easy-synthesis/index.html",
    "content": "<!DOCTYPE html>\n<html>\n  <head>\n    <meta charset=\"utf-8\">\n    <meta http-equiv=\"X-UA-Compatible\" content=\"IE=edge,chrome=1\">\n    <meta name=\"viewport\" content=\"width=device-width\">\n\n    <title>Speech synthesiser</title>\n\n    <link rel=\"stylesheet\" href=\"style.css\">\n    <!--[if lt IE 9]>\n      <script src=\"//html5shiv.googlecode.com/svn/trunk/html5.js\"></script>\n    <![endif]-->\n  </head>\n\n  <body>\n    <h1>Speech synthesiser</h1>\n\n    <p>Enter some text in the input below and press return  or the \"play\" button to hear it. change voices using the dropdown menu.</p>\n    \n    <form>\n    <input type=\"text\" class=\"txt\">\n    <div>\n      <label for=\"rate\">Rate</label><input type=\"range\" min=\"0.5\" max=\"2\" value=\"1\" step=\"0.1\" id=\"rate\">\n      <div class=\"rate-value\">1</div>\n      <div class=\"clearfix\"></div>\n    </div>\n    <div>\n      <label for=\"pitch\">Pitch</label><input type=\"range\" min=\"0\" max=\"2\" value=\"1\" step=\"0.1\" id=\"pitch\">\n      <div class=\"pitch-value\">1</div>\n      <div class=\"clearfix\"></div>\n    </div>\n    <select>\n\n    </select>\n    <div class=\"controls\">\n      <button id=\"play\" type=\"submit\">Play</button>\n    </div>\n    </form>\n\n    <script src=\"script.js\"></script>\n  </body>\n</html>"
  },
  {
    "path": "23 - Speech Synthesis/speak-easy-synthesis/manifest.webapp",
    "content": "{\n  \"name\": \"SpeechSyn\",\n  \"description\": \"Web Speech API speech synthesis demo\",\n  \"launch_path\": \"/index.html\",\n  \"icons\": {\n    \"512\": \"/img/ws512.png\",\n    \"128\": \"/img/ws128.png\"\n  },\n  \"developer\": {\n    \"name\": \"Chris Mills\",\n    \"url\": \"https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API\"\n  },\n  \"default_locale\": \"en\"\n}"
  },
  {
    "path": "23 - Speech Synthesis/speak-easy-synthesis/script.js",
    "content": "var synth = window.speechSynthesis;\n\nvar inputForm = document.querySelector('form');\nvar inputTxt = document.querySelector('.txt');\nvar voiceSelect = document.querySelector('select');\n\nvar pitch = document.querySelector('#pitch');\nvar pitchValue = document.querySelector('.pitch-value');\nvar rate = document.querySelector('#rate');\nvar rateValue = document.querySelector('.rate-value');\n\nvar voices = [];\n\nfunction populateVoiceList() {\n  voices = synth.getVoices();\n  var selectedIndex = voiceSelect.selectedIndex < 0 ? 0 : voiceSelect.selectedIndex;\n  voiceSelect.innerHTML = '';\n  for(i = 0; i < voices.length ; i++) {\n    var option = document.createElement('option');\n    option.textContent = voices[i].name + ' (' + voices[i].lang + ')';\n    \n    if(voices[i].default) {\n      option.textContent += ' -- DEFAULT';\n    }\n\n    option.setAttribute('data-lang', voices[i].lang);\n    option.setAttribute('data-name', voices[i].name);\n    voiceSelect.appendChild(option);\n  }\n  voiceSelect.selectedIndex = selectedIndex;\n}\n\npopulateVoiceList();\nif (speechSynthesis.onvoiceschanged !== undefined) {\n  speechSynthesis.onvoiceschanged = populateVoiceList;\n}\n\nfunction speak(){\n  if(inputTxt.value !== ''){\n    var utterThis = new SpeechSynthesisUtterance(inputTxt.value);\n    var selectedOption = voiceSelect.selectedOptions[0].getAttribute('data-name');\n    for(i = 0; i < voices.length ; i++) {\n      if(voices[i].name === selectedOption) {\n        utterThis.voice = voices[i];\n      }\n    }\n    utterThis.pitch = pitch.value;\n    utterThis.rate = rate.value;\n    synth.speak(utterThis);\n  }\n}\n\ninputForm.onsubmit = function(event) {\n  event.preventDefault();\n\n  speak();\n\n  inputTxt.blur();\n}\n\npitch.onchange = function() {\n  pitchValue.textContent = pitch.value;\n}\n\nrate.onchange = function() {\n  rateValue.textContent = rate.value;\n}\n\nvoiceSelect.onchange = function(){\n  speak();\n}"
  },
  {
    "path": "23 - Speech Synthesis/speak-easy-synthesis/style.css",
    "content": "body, html {\n  margin: 0;\n}\n\nhtml {\n  height: 100%;\n}\n\nbody {\n  height: 90%;\n  max-width: 800px;\n  margin: 0 auto;\n}\n\nh1, p {\n  font-family: sans-serif;\n  text-align: center;\n  padding: 20px;\n}\n\n.txt, select, form > div {\n  display: block;\n  margin: 0 auto;\n  font-family: sans-serif;\n  font-size: 16px;\n  padding: 5px;\n}\n\n.txt {\n  width: 80%;\n}\n\nselect {\n  width: 83%;\n}\n\nform > div {\n  width: 81%;\n}\n\n.txt, form > div {\n  margin-bottom: 10px;\n  overflow: auto;\n}\n\n.clearfix {\n  clear: both;\n}\n\nlabel {\n  float: left;\n  width: 10%;\n  line-height: 1.5;\n}\n\n.rate-value, .pitch-value {\n  float: right;\n  width: 5%;\n  line-height: 1.5;\n}\n\n#rate, #pitch {\n  float: right;\n  width: 81%;\n}\n\n.controls {\n  text-align: center;\n  margin-top: 10px;\n}\n\n.controls button {\n  padding: 10px;\n}"
  },
  {
    "path": "24 - Sticky Nav/README.md",
    "content": "# Day24 - Sticky Nav 粘性导航栏\n## 效果图\n![](http://om1c35wrq.bkt.clouddn.com/day24-xiaoguotu.gif)\n\n### 代码解析\n### 涉及知识点\n### \n"
  },
  {
    "path": "24 - Sticky Nav/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Sticky Nav</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <header>\n    <h1>A story about getting lost.</h1>\n  </header>\n\n  <nav id=\"main\">\n    <ul>\n      <li class=\"logo\"><a href=\"#\">LOST.</a></li>\n      <li><a href=\"#\">Home</a></li>\n      <li><a href=\"#\">About</a></li>\n      <li><a href=\"#\">Images</a></li>\n      <li><a href=\"#\">Locations</a></li>\n      <li><a href=\"#\">Maps</a></li>\n    </ul>\n  </nav>\n\n  <div class=\"site-wrap\">\n\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui\n      libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas\n      laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.</p>\n\n    <img src=\"http://unsplash.it/400/400\" class=\"align-left slide-in\">\n\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptates, deserunt facilis et iste corrupti omnis tenetur\n      est. Iste ut est dicta dolor itaque adipisci, dolorum minima, veritatis earum provident error molestias. Ratione magni\n      illo sint vel velit ut excepturi consectetur suscipit, earum modi accusamus voluptatem nostrum, praesentium numquam,\n      reiciendis voluptas sit id quisquam. Consequatur in quis reprehenderit modi perspiciatis necessitatibus saepe, quidem,\n      suscipit iure natus dignissimos ipsam, eligendi deleniti accusantium, rerum quibusdam fugit perferendis et optio recusandae\n      sed ratione. Culpa, dolorum reprehenderit harum ab voluptas fuga, nisi eligendi natus maiores illum quas quos et aperiam\n      aut doloremque optio maxime fugiat doloribus. Eum dolorum expedita quam, nesciunt</p>\n\n    <img src=\"http://unsplash.it/400/400\" class=\"align-right slide-in\">\n\n    <p> at provident praesentium atque quas rerum optio dignissimos repudiandae ullam illum quibusdam. Vel ad error quibusdam,\n      illo ex totam placeat. Quos excepturi fuga, molestiae ea quisquam minus, ratione dicta consectetur officia omnis, doloribus\n      voluptatibus? Veniam ipsum veritatis architecto, provident quas consequatur doloremque quam quidem earum expedita,\n      ad delectus voluptatum, omnis praesentium nostrum qui aspernatur ea eaque adipisci et cumque ab? Ea voluptatum dolore\n      itaque odio. Eius minima distinctio harum, officia ab nihil exercitationem. Tempora rem nemo nam temporibus molestias\n      facilis minus ipsam quam doloribus consequatur debitis nesciunt tempore officiis aperiam quisquam, molestiae voluptates\n      cum, fuga culpa. Distinctio accusamus quibusdam, tempore perspiciatis dolorum optio facere consequatur quidem ullam\n      beatae architecto, ipsam sequi officiis dignissimos amet impedit natus necessitatibus tenetur repellendus dolor rem!\n      Dicta dolorem, iure, facilis illo ex nihil ipsa amet officia, optio temporibus eum autem odit repellendus nisi. Possimus\n      modi, corrupti error debitis doloribus dicta libero earum, sequi porro ut excepturi nostrum ea voluptatem nihil culpa?\n      Ullam expedita eligendi obcaecati reiciendis velit provident omnis quas qui in corrupti est dolore facere ad hic, animi\n      soluta assumenda consequuntur reprehenderit! Voluptate dolor nihil veniam laborum voluptas nisi pariatur sed optio\n      accusantium quam consectetur, corrupti, sequi et consequuntur, excepturi doloremque. Tempore quis velit corporis neque\n      fugit non sequi eaque rem hic. Facere, inventore, aspernatur. Accusantium modi atque, asperiores qui nobis soluta cumque\n      suscipit excepturi possimus doloremque odit saepe perferendis temporibus molestiae nostrum voluptatum quis id sint\n      quidem nesciunt culpa. Rerum labore dolor beatae blanditiis praesentium explicabo velit optio esse aperiam similique,\n      voluptatem cum, maiores ipsa tempore. Reiciendis sed culpa atque inventore, nam ullam enim expedita consectetur id\n      velit iusto alias vitae explicabo nemo neque odio reprehenderit soluta sint eaque. Aperiam, qui ut tenetur, voluptate\n      doloremque officiis dicta quaerat voluptatem rerum natus magni. Eum amet autem dolor ullam.</p>\n\n\n\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero\n      placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero\n      perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore!\n      Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore\n      iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium,\n      rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel\n      non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita\n      in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi.\n      Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione.\n      Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita,\n      laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda\n      natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos\n      dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro\n      saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo,\n      ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus\n      quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores\n      quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam,\n      quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus,\n      odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore\n      non magnam, amet, facere ducimus accusantium eos veritatis neque.</p>\n\n    <img src=\"http://unsplash.it/400/400\" class=\"align-right slide-in\">\n\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero\n      placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero\n      perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore!\n      Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore\n      iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium,\n      rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel\n      non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita\n      in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi.\n      Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione.\n      Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita,\n      laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda\n      natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos\n      dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro\n      saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo,\n      ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus\n      quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores\n      quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam,\n      quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus,\n      odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore\n      non magnam, amet, facere ducimus accusantium eos veritatis neque.</p>\n    <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero\n      placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero\n      perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore!\n      Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore\n      iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium,\n      rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel\n      non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita\n      in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi.\n      Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione.\n      Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita,\n      laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda\n      natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos\n      dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro\n      saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo,\n      ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus\n      quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores\n      quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam,\n      quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus,\n      odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore\n      non magnam, amet, facere ducimus accusantium eos veritatis neque.</p>\n  </div>\n\n  <script>\n    let nav = document.querySelector('nav#main');\n    let navTop = nav.offsetTop;\n\n    function fixNav() {\n      if (window.scrollY >= navTop) {\n        document.body.style.paddingTop = `${nav.offsetHeight}px`;\n        document.body.classList.add('fixed-nav');\n      } else {\n        document.body.style.paddingTop = 0;\n        document.body.classList.remove('fixed-nav');\n      }\n    }\n    document.addEventListener('scroll', fixNav);\n  </script>\n\n</body>\n\n</html>"
  },
  {
    "path": "24 - Sticky Nav/style.css",
    "content": "html {\n  box-sizing: border-box;\n  background: #eeeeee;\n  font-family: 'helvetica neue';\n  font-size: 20px;\n  font-weight: 200;\n}\n\nbody {\n  margin: 0;\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\n.site-wrap {\n  max-width: 700px;\n  margin: 70px auto;\n  background: white;\n  padding: 40px;\n  text-align: justify;\n  box-shadow: 0 0 10px 5px rgba(0, 0, 0, 0.05);\n  transform: scale(0.98);\n  transition: transform 0.5s;\n}\n\n.fixed-nav .site-wrap {\n  transform: scale(1);\n}\n\nheader {\n  text-align: center;\n  height: 50vh;\n  background: url(http://wes.io/iEgP/wow-so-deep.jpg) bottom center no-repeat;\n  background-size: cover;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n\nh1 {\n  color: white;\n  font-size: 7vw;\n  text-shadow: 3px 4px 0 rgba(0, 0, 0, 0.2)\n}\n\nnav {\n  background: black;\n  top: 0;\n  width: 100%;\n  transition: all 0.5s;\n  position: relative;\n  z-index: 1;\n}\n\n.fixed-nav nav {\n  position: fixed;\n  box-shadow: 0 10px 10px rgba(0, 0, 0, 0.2);\n}\n\nnav ul {\n  margin: 0;\n  padding: 0;\n  list-style: none;\n  display: flex;\n}\n\nnav li {\n  flex: 1;\n  text-align: center;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n}\n\nli.logo {\n  max-width: 0;\n  overflow: hidden;\n  background: white;\n  transition: all .5s;\n  font-weight: 600;\n  font-size: 30px;\n}\n\n.fixed-nav li.logo {\n  max-width: 500px;\n}\n\nli.logo a {\n  color: black;\n}\n\nnav a {\n  text-decoration: none;\n  padding: 20px;\n  display: inline-block;\n  color: white;\n  transition: all 0.2s;\n  text-transform: uppercase;\n}"
  },
  {
    "path": "25 - Event Capture, Propagation, Bubbling and Once/README.md",
    "content": "\n\n# Day25 - Event Capture, Propagation, Bubbling and Once\n\n\n## 效果图\n\n\n![](http://om1c35wrq.bkt.clouddn.com/Snip20170813_2.png)\n\n第25天的训练是学习DOM的事件机制，主要包括事件捕获，事件冒泡，单次执行事件。\n\n\n\n## 源代码\n\n[addEventListener参考文档](https://developer.mozilla.org/zh-CN/docs/Web/API/EventTarget/addEventListener)\n\n```Javascript\n <script>\n        let divs = document.querySelectorAll('div');\n        let one = document.querySelector('.one');\n        let two = document.querySelector('.two');\n        let three = document.querySelector('.three');\n        divs.forEach(div => div.addEventListener('click', logText, {\n            once: true,\n            capture: false\n        }));\n\n        // one.addEventListener('click', logText1, {\n        //     // capture: true\n        // });\n        // two.addEventListener('click', logText2, {\n        //     // capture: true\n        // });\n        // three.addEventListener('click', logText3, {\n        //     capture: true\n        // });\n\n       \n        function logText(e) {\n            console.log(this.classList.value);\n            // e.stopPropagation();\n        }\n\n        function logText1(e) {\n            console.log(this.classList.value);\n            // e.stopPropagation();\n        }\n\n        function logText2(e) {\n            console.log(this.classList.value);\n            // e.stopPropagation();\n        }\n\n        function logText3(e) {\n            console.log(this.classList.value);\n            e.stopPropagation();\n        }\n        \n</script>\n```\n同时也看一下HTML的文档结构，对于事件机制的理解也很重要：\n\n```html\n<div class=\"one\">\n    <div class=\"two\">\n        <div class=\"three\">\n        </div>\n    </div>\n</div>\n```\n* `EventTarget.addEventListener('eventName',callback,option)`：元素的事件注册方法，接收三个参数，第一个参数为事件的名称（点击`click`、双击`dbclick`、改变`change`等），第二个参数是该事件的回调函数，也称为监听器，第三个参数为事件注册的选项对象，通常会包含两个配置项（是否事件捕获`capture`以及单次执行`once`事件，它们两个的默认值都是`false`）。\n* 当我们点击`class=\"three\"`的`div`的时候，我们也相当于同时点击了`class=\"two\"`和`class=\"one\"`。\n* `e.stopPropagation();`是否停止冒泡，如果调用了这个方法，就不会触发父组建的方法。\n\n\n\n\n\n"
  },
  {
    "path": "25 - Event Capture, Propagation, Bubbling and Once/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n    <meta charset=\"UTF-8\">\n    <title>Understanding JavaScript's Capture</title>\n</head>\n\n<body class=\"bod\">\n\n    <div class=\"one\">\n        <div class=\"two\">\n            <div class=\"three\">\n            </div>\n        </div>\n    </div>\n\n    <style>\n        html {\n            box-sizing: border-box;\n        }\n\n        *,\n        *:before,\n        *:after {\n            box-sizing: inherit;\n        }\n\n        div {\n            width: 100%;\n            padding: 100px;\n        }\n\n        .one {\n            background: thistle;\n        }\n\n        .two {\n            background: mistyrose;\n        }\n\n        .three {\n            background: coral;\n        }\n    </style>\n\n    <script>\n        let divs = document.querySelectorAll('div');\n        let one = document.querySelector('.one');\n        let two = document.querySelector('.two');\n        let three = document.querySelector('.three');\n        divs.forEach(div => div.addEventListener('click', logText, {\n            once: true,\n            capture: false\n        }));\n\n        // one.addEventListener('click', logText1, {\n        //     // capture: true\n        // });\n        // two.addEventListener('click', logText2, {\n        //     // capture: true\n        // });\n        // three.addEventListener('click', logText3, {\n        //     capture: true\n        // });\n\n       \n        function logText(e) {\n            console.log(this.classList.value);\n            // e.stopPropagation();\n        }\n\n        function logText1(e) {\n            console.log(this.classList.value);\n            // e.stopPropagation();\n        }\n\n        function logText2(e) {\n            console.log(this.classList.value);\n            // e.stopPropagation();\n        }\n\n        function logText3(e) {\n            console.log(this.classList.value);\n            e.stopPropagation();\n        }\n    </script>\n</body>\n\n</html>"
  },
  {
    "path": "26 - Stripe Follow Along Nav/README.md",
    "content": "\n# Day26 - Stripe Follow Along Nav\n\n## 效果图\n\n![](http://om1c35wrq.bkt.clouddn.com/day25.gif)\n\n## 源码\n\n```javascript\n// javascript\nconst nav = document.querySelector('.top');\nconst menu = document.querySelectorAll('ul.cool > li');\nconst background = document.querySelector('.dropdownBackground');\n\nfunction mouseEnter() {\n  this.classList.add('trigger-enter');\n  setTimeout(() => this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'), 150);\n  background.classList.add('open');\n\n  const dropdown = this.querySelector('.dropdown');\n  const dropdownCoords = dropdown.getBoundingClientRect();\n  const navCoords = nav.getBoundingClientRect();\n  const coords = {\n    width: dropdownCoords.width,\n    height: dropdownCoords.height,\n    left: dropdownCoords.left - navCoords.left,\n    top: dropdownCoords.top - navCoords.top\n  };\n  background.style.setProperty('width', `${coords.width}px`);\n  background.style.setProperty('height', `${coords.height}px`);\n  background.style.setProperty('transform', `translate3D(${coords.left}px,${coords.top}px,0)`);\n  // background.style.setProperty('top', `${coords.top}px`);\n  // background.style.setProperty('left', `${coords.left}px`);\n}\n\nfunction mouseLeave() {\n  this.classList.remove('trigger-enter','trigger-enter-active');\n  // this.classList.remove('trigger-enter');\n  // this.classList.remove('');\n  background.classList.remove('open');\n}\nmenu.forEach(ele => ele.addEventListener('mouseenter', mouseEnter));\nmenu.forEach(ele => ele.addEventListener('mouseleave', mouseLeave));\n```\n\n```css\n<!-- 部分CSS -->\n.dropdown {\n  opacity: 0;\n  position: absolute;\n  overflow: hidden;\n  padding: 20px;\n  top: -20px;\n  border-radius: 2px;\n  transition: all 0.5s;\n  transform: translateY(100px);\n  will-change: opacity;\n  display: none;\n}\n\n.trigger-enter .dropdown {\n  display: block;\n}\n\n.trigger-enter-active .dropdown {\n  opacity: 1;\n}\n\n.dropdownBackground {\n  width: 100px;\n  height: 100px;\n  position: absolute;\n  background: #fff;\n  border-radius: 4px;\n  box-shadow: 0 50px 100px rgba(50, 50, 93, .1), 0 15px 35px rgba(50, 50, 93, .15), 0 5px 15px rgba(0, 0, 0, .1);\n  transition: all 0.3s, opacity 0.1s, transform 0.2s;\n  transform-origin: 50% 0;\n  display: flex;\n  justify-content: center;\n  opacity: 0;\n}\n\n.dropdownBackground.open {\n  opacity: 1;\n}\n```\n\n## 代码解析\n\n- 鼠标进入监听\n\n```js\nmenu.forEach(ele => ele.addEventListener('mouseenter', mouseEnter));\n```\n\n- 鼠标移除监听\n\n```js\nmenu.forEach(ele => ele.addEventListener('mouseleave', mouseLeave));\n```\n\n- 过渡动画的实现原理\n\n当鼠标移动到某一个选项后，首先使下拉菜单显示，但是在150ms内使其显示出来，这里用了`settimeout(fn,150)`，来延迟添加下拉菜单的`trigger-enter-active`类名，这样就会有一个过渡的效果了。\n\n- 鼠标进入时添加类名\n```\nthis.classList.add('trigger-enter');\nsetTimeout(() => this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'), 150);\nbackground.classList.add('open');\n```\n这里有一点需要注意，因为我们设置了150ms延迟之后添加`trigger-enter-active`类，那么有可能会发生这样的情况：当我们以飞快的速度在各个选项之间切换的时候，有可能还没有到150ms鼠标就已经移出了选项了，这时在150ms之后，就会多添加了`trigger-enter-active`类在每一个选项里面，造成意想不到的错误。\n因此我们加了一句判断，150ms后只有当该鼠标还悬停在这个选项之中的时候，我们才添加`trigger-enter-active`类。\n\n- 动态的设置白色背景块的位置信息\n\n\n```\nconst coords = {\n\twidth: dropdownCoords.width,\n\theight: dropdownCoords.height,\n\tleft: dropdownCoords.left - navCoords.left,\n\ttop: dropdownCoords.top - navCoords.top\n};\nbackground.style.setProperty('width', `${coords.width}px`);\nbackground.style.setProperty('height', `${coords.height}px`);\nbackground.style.setProperty('transform', `translate3D(${coords.left}px,${coords.top}px,0)`);\n// background.style.setProperty('top', `${coords.top}px`);\n// background.style.setProperty('left', `${coords.left}px`);\n```\n\n这里有一点需要注意的是，我们在为白色背景块设置左边距和上边距的时候，要分别减去导航栏的上边距和左边距。因为我们的导航栏上面可能会有其他的内容，若不将这段距离减去，就会造成白色背景块位置偏移。\n\n`translate3D(x,y,z)`这里之所以使用`translate3D`，是因为`translate3D`属性会触发硬件加速，开启了硬件加速的`transform`是不会触发界面`repaint`的，拥有更好的性能。\n\n\n\n\n"
  },
  {
    "path": "26 - Stripe Follow Along Nav/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Follow Along Nav</title>\n</head>\n\n<body>\n  <h2>Cool</h2>\n  <nav class=\"top\">\n    <div class=\"dropdownBackground\">\n      <span class=\"arrow\"></span>\n    </div>\n\n    <ul class=\"cool\">\n      <li>\n        <a href=\"#\">About Me</a>\n        <div class=\"dropdown dropdown1\">\n          <div class=\"bio\">\n            <img src=\"http://liyuechun.org/images/qukuailian.jpg\">\n            <p>专注于JS全栈开发以及区块链、高可用架构技术的研究与学习</p>\n          </div>\n        </div>\n      </li>\n      <li>\n        <a href=\"#\">Courses</a>\n        <ul class=\"dropdown courses\">\n          <li>\n            <span class=\"code\">视频</span>\n            <a href=\"http://www.kongyixueyuan.com\">区块链</a>\n          </li>\n          <li>\n            <span class=\"code\">RFB</span>\n            <a href=\"https://ReactForBeginners.com\">React For Beginners</a>\n          </li>\n          <li>\n            <span class=\"code\">ES6</span>\n            <a href=\"https://ES6.io\">ES6 For Everyone</a>\n          </li>\n          <li>\n            <span class=\"code\">STPU</span>\n            <a href=\"https://SublimeTextBook.com\">Sublime Text Power User</a>\n          </li>\n          <li>\n            <span class=\"code\">WTF</span>\n            <a href=\"http://flexbox.io\">What The Flexbox?!</a>\n          </li>\n          <li>\n            <span class=\"code\">LRX</span>\n            <a href=\"http://LearnRedux.com\">Learn Redux</a>\n          </li>\n          <li>\n            <span class=\"code\">CLPU</span>\n            <a href=\"http://CommandLinePowerUser.com\">Command Line Power User</a>\n          </li>\n          <li>\n            <span class=\"code\">MMD</span>\n            <a href=\"http://MasteringMarkdown.com\">Mastering Markdown</a>\n          </li>\n        </ul>\n      </li>\n      <li>\n        <a href=\"#\">Other Links</a>\n        <ul class=\"dropdown dropdown3\">\n          <li><a class=\"button\" href=\"http://twitter.com/wesbos\">Twiter</a></li>\n          <li><a class=\"button\" href=\"http://facebook.com/wesbos.developer\">Facebook</a></li>\n          <li><a class=\"button\" href=\"liyuechun.org\">Blog</a></li>\n          <li><a class=\"button\" href=\"http://weibo.com/mobiledevelopment\">Sina Weibo</a></li>\n        </ul>\n      </li>\n    </ul>\n  </nav>\n\n  <style>\n    html {\n      box-sizing: border-box;\n      font-family: \"Arial Rounded MT Bold\", \"Helvetica Rounded\", Arial, sans-serif\n    }\n\n    *,\n    *:before,\n    *:after {\n      box-sizing: inherit;\n    }\n\n    body {\n      margin: 0;\n      min-height: 100vh;\n      background: linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%),\n      linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%),\n      linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%),\n      linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%);\n    }\n\n    h2 {\n      margin-top: 0;\n      padding-top: .8em;\n    }\n\n    nav {\n      position: relative;\n      perspective: 600px;\n    }\n\n    .cool>li>a {\n      color: yellow;\n      text-decoration: none;\n      font-size: 20px;\n      background: rgba(0, 0, 0, 0.2);\n      padding: 10px 20px;\n      display: inline-block;\n      margin: 20px;\n      border-radius: 5px;\n    }\n\n    nav ul {\n      list-style: none;\n      margin: 0;\n      padding: 0;\n      display: flex;\n      justify-content: center;\n    }\n\n    .cool>li {\n      position: relative;\n      display: flex;\n      justify-content: center;\n    }\n\n    .dropdown {\n      opacity: 0;\n      position: absolute;\n      overflow: hidden;\n      padding: 20px;\n      top: -20px;\n      border-radius: 2px;\n      transition: all 0.5s;\n      transform: translateY(100px);\n      will-change: opacity;\n      display: none;\n    }\n\n    .trigger-enter .dropdown {\n      display: block;\n    }\n\n    .trigger-enter-active .dropdown {\n      opacity: 1;\n    }\n\n\n\n    .dropdownBackground {\n      width: 100px;\n      height: 100px;\n      position: absolute;\n      background: #fff;\n      border-radius: 4px;\n      box-shadow: 0 50px 100px rgba(50, 50, 93, .1), 0 15px 35px rgba(50, 50, 93, .15), 0 5px 15px rgba(0, 0, 0, .1);\n      transition: all 0.3s, opacity 0.1s, transform 0.2s;\n      transform-origin: 50% 0;\n      display: flex;\n      justify-content: center;\n      opacity: 0;\n    }\n\n    .dropdownBackground.open {\n      opacity: 1;\n    }\n\n    .arrow {\n      position: absolute;\n      width: 20px;\n      height: 20px;\n      display: block;\n      background: white;\n      transform: translateY(-50%) rotate(45deg);\n    }\n\n    .bio {\n      min-width: 500px;\n      display: flex;\n      justify-content: center;\n      align-items: center;\n      line-height: 1.7;\n    }\n\n    .bio img {\n      float: left;\n      margin-right: 20px;\n    }\n\n    .courses {\n      min-width: 300px;\n    }\n\n    .courses li {\n      padding: 10px 0;\n      display: block;\n      border-bottom: 1px solid rgba(0, 0, 0, 0.2);\n    }\n\n    .dropdown a {\n      text-decoration: none;\n      color: #ffc600;\n    }\n\n    a.button {\n      background: black;\n      display: block;\n      padding: 10px;\n      color: white;\n      margin-bottom: 10px;\n    }\n    /* Matches Twitter, TWITTER, twitter, tWitter, TWiTTeR... */\n\n    .button[href*=twitter] {\n      background: #019FE9;\n    }\n\n    .button[href*=facebook] {\n      background: #3B5998;\n    }\n\n    .button[href*=courses] {\n      background: #ffc600;\n    }\n  </style>\n\n  <script>\n    const nav = document.querySelector('.top');\n    const menu = document.querySelectorAll('ul.cool > li');\n    const background = document.querySelector('.dropdownBackground');\n\n    function mouseEnter() {\n      this.classList.add('trigger-enter');\n      setTimeout(() => this.classList.contains('trigger-enter') && this.classList.add('trigger-enter-active'), 150);\n      background.classList.add('open');\n\n      const dropdown = this.querySelector('.dropdown');\n      const dropdownCoords = dropdown.getBoundingClientRect();\n      const navCoords = nav.getBoundingClientRect();\n      const coords = {\n        width: dropdownCoords.width,\n        height: dropdownCoords.height,\n        left: dropdownCoords.left - navCoords.left,\n        top: dropdownCoords.top - navCoords.top\n      };\n      background.style.setProperty('width', `${coords.width}px`);\n      background.style.setProperty('height', `${coords.height}px`);\n      background.style.setProperty('transform', `translate3D(${coords.left}px,${coords.top}px,0)`);\n      // background.style.setProperty('top', `${coords.top}px`);\n      // background.style.setProperty('left', `${coords.left}px`);\n    }\n\n    function mouseLeave() {\n      this.classList.remove('trigger-enter', 'trigger-enter-active');\n      // this.classList.remove('trigger-enter');\n      // this.classList.remove('');\n      background.classList.remove('open');\n    }\n    menu.forEach(ele => ele.addEventListener('mouseenter', mouseEnter));\n    menu.forEach(ele => ele.addEventListener('mouseleave', mouseLeave));\n  </script>\n\n</body>\n\n</html>"
  },
  {
    "path": "27 - Click and Drag/README.md",
    "content": "# Day27 - Click and Drag\n\n\n## 效果图\n\n[在线效果](http://30daysofjs.michaeleinsohn.com/scroll-drag/)\n\n![](http://om1c35wrq.bkt.clouddn.com/day27.gif)\n\n## UI实现\n\n#### HTML 代码\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Click and Drag</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n  <div class=\"items\">\n    <div class=\"item item1\">01</div>\n    <div class=\"item item2\">02</div>\n    <div class=\"item item3\">03</div>\n    <div class=\"item item4\">04</div>\n    <div class=\"item item5\">05</div>\n    <div class=\"item item6\">06</div>\n    <div class=\"item item7\">07</div>\n    <div class=\"item item8\">08</div>\n    <div class=\"item item9\">09</div>\n    <div class=\"item item10\">10</div>\n    <div class=\"item item11\">11</div>\n    <div class=\"item item12\">12</div>\n    <div class=\"item item13\">13</div>\n    <div class=\"item item14\">14</div>\n    <div class=\"item item15\">15</div>\n    <div class=\"item item16\">16</div>\n    <div class=\"item item17\">17</div>\n    <div class=\"item item18\">18</div>\n    <div class=\"item item19\">19</div>\n    <div class=\"item item20\">20</div>\n    <div class=\"item item21\">21</div>\n    <div class=\"item item22\">22</div>\n    <div class=\"item item23\">23</div>\n    <div class=\"item item24\">24</div>\n    <div class=\"item item25\">25</div>\n  </div>\n</body>\n</html>\n```\n\n\n#### CSS 代码\n\n```css\nhtml {\n  box-sizing: border-box;\n  background: url('https://source.unsplash.com/NFs6dRTBgaM/2000x2000') fixed;\n  background-size: cover;\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  min-height: 100vh;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  font-family: sans-serif;\n  font-size: 20px;\n  margin: 0;\n}\n\n.items {\n  height: 800px;\n  padding: 100px;\n  width: 100%;\n  border: 1px solid white;\n  overflow-x: scroll;\n  overflow-y: hidden;\n  white-space: nowrap;\n  user-select: none;\n  cursor: pointer;\n  transition: all 0.2s;\n  transform: scale(0.98);\n  will-change: transform;\n  position: relative;\n  background: rgba(255, 255, 255, 0.1);\n  font-size: 0;\n  perspective: 500px;\n}\n\n.items.active {\n  background: rgba(255, 255, 255, 0.3);\n  cursor: grabbing;\n  cursor: -webkit-grabbing;\n  transform: scale(1);\n}\n\n.item {\n  width: 200px;\n  height: calc(100% - 40px);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 80px;\n  font-weight: 100;\n  color: rgba(0, 0, 0, 0.15);\n  box-shadow: inset 0 0 0 10px rgba(0, 0, 0, 0.15);\n}\n\n.item:nth-child(9n+1) {\n  background: dodgerblue;\n}\n\n.item:nth-child(9n+2) {\n  background: goldenrod;\n}\n\n.item:nth-child(9n+3) {\n  background: paleturquoise;\n}\n\n.item:nth-child(9n+4) {\n  background: gold;\n}\n\n.item:nth-child(9n+5) {\n  background: cadetblue;\n}\n\n.item:nth-child(9n+6) {\n  background: tomato;\n}\n\n.item:nth-child(9n+7) {\n  background: lightcoral;\n}\n\n.item:nth-child(9n+8) {\n  background: darkslateblue;\n}\n\n.item:nth-child(9n+9) {\n  background: rebeccapurple;\n}\n\n.item:nth-child(even) {\n  transform: scaleX(1.31) rotateY(40deg);\n}\n\n.item:nth-child(odd) {\n  transform: scaleX(1.31) rotateY(-40deg);\n}\n```\n\n#### HTML + CSS 代码解析\n\n- 每一个`div`就是一个`item`,在本案例中一共有25个`item`。\n\n```html\n<div class=\"item item1\">01</div>\n<div class=\"item item2\">02</div>\n<div class=\"item item3\">03</div>\n<div class=\"item item4\">04</div>\n<div class=\"item item5\">05</div>\n<div class=\"item item6\">06</div>\n<div class=\"item item7\">07</div>\n<div class=\"item item8\">08</div>\n<div class=\"item item9\">09</div>\n<div class=\"item item10\">10</div>\n<div class=\"item item11\">11</div>\n<div class=\"item item12\">12</div>\n<div class=\"item item13\">13</div>\n<div class=\"item item14\">14</div>\n<div class=\"item item15\">15</div>\n<div class=\"item item16\">16</div>\n<div class=\"item item17\">17</div>\n<div class=\"item item18\">18</div>\n<div class=\"item item19\">19</div>\n<div class=\"item item20\">20</div>\n<div class=\"item item21\">21</div>\n<div class=\"item item22\">22</div>\n<div class=\"item item23\">23</div>\n<div class=\"item item24\">24</div>\n<div class=\"item item25\">25</div>\n```\n\n- 如果大家细心观察效果图，不难发现一个规律，`n` 和 `n + 9` 的 `item`的颜色相同，所以有如下的CSS代码：\n\n```css\n.item:nth-child(9n+1) {\n  background: dodgerblue;\n}\n\n.item:nth-child(9n+2) {\n  background: goldenrod;\n}\n\n.item:nth-child(9n+3) {\n  background: paleturquoise;\n}\n\n.item:nth-child(9n+4) {\n  background: gold;\n}\n\n.item:nth-child(9n+5) {\n  background: cadetblue;\n}\n\n.item:nth-child(9n+6) {\n  background: tomato;\n}\n\n.item:nth-child(9n+7) {\n  background: lightcoral;\n}\n\n.item:nth-child(9n+8) {\n  background: darkslateblue;\n}\n\n.item:nth-child(9n+9) {\n  background: rebeccapurple;\n}\n```\n\n\n- `item`的样式CSS\n\n```css\n.item {\n  width: 200px;\n  height: calc(100% - 40px);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 80px;\n  font-weight: 100;\n  color: rgba(0, 0, 0, 0.15);\n  box-shadow: inset 0 0 0 10px rgba(0, 0, 0, 0.15);\n}\n```\n\n\n- 奇偶旋转样式\n\n```css\n\n<!--表示HTML表格中的偶数行。-->\n.item:nth-child(even) {\n  transform: scaleX(1.31) rotateY(40deg);\n}\n\n<!--表示HTML表格中的奇数行。-->\n.item:nth-child(odd) {\n  transform: scaleX(1.31) rotateY(-40deg);\n}\n```\n\n## JS逻辑代码及注释\n\n```js\n<script>\n<!--获取所有的`item`-->\n  const slider = document.querySelector('.items');\n  <!--声明几个变量用于处理接下来的逻辑-->\n  let isDown = false;\n  let startX;\n  let scrollLeft;\n\n<!--监听鼠标是否处于按下去的状态，如果是，调用后面的回调函数-->\n  slider.addEventListener('mousedown', (e) => {\n  <!--设置isDown为真-->\n    isDown = true;\n    <!--增加类active，改变当前item的样式-->\n    slider.classList.add('active');\n    <!--计算当前item的x坐标-->\n    startX = e.pageX - slider.offsetLeft;\n    <!--存储当前item的滚动出了左边窗口的值-->\n    scrollLeft = slider.scrollLeft;\n  });\n\n<!--监听鼠标是否处于离开的的状态，如果是，调用后面的回调函数-->\n  slider.addEventListener('mouseleave', () => {\n  <!--设置isDown的值为false-->\n    isDown = false;\n    <!--将当前的item的样式恢复-->\n    slider.classList.remove('active');\n  });\n\n\n<!--监听鼠标是否处于抬起来的状态，如果是，调用后面的回调函数-->\n  slider.addEventListener('mouseup', () => {\n    <!--设置isDown的值为false-->\n    isDown = false;\n    <!--将当前的item的样式恢复-->\n    slider.classList.remove('active');\n  });\n\n\n<!--监听鼠标是否处于移动的状态，如果是，调用后面的回调函数-->\n  slider.addEventListener('mousemove', (e) => {\n  <!--如果没有isDown的值为false，直接返回-->\n    if (!isDown) return;  // stop the fn from running\n    <!--如果事件可取消，则取消该事件，而不停止事件的进一步传播。-->\n    e.preventDefault();\n    <!--改变当前item的scrollLeft值-->\n    const x = e.pageX - slider.offsetLeft;\n    const walk = (x - startX) * 3;\n    slider.scrollLeft = scrollLeft - walk;\n  });\n\n</script>\n```\n\n\n\n\n\n\n"
  },
  {
    "path": "27 - Click and Drag/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Click and Drag</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n  <div class=\"items\">\n    <div class=\"item item1\">01</div>\n    <div class=\"item item2\">02</div>\n    <div class=\"item item3\">03</div>\n    <div class=\"item item4\">04</div>\n    <div class=\"item item5\">05</div>\n    <div class=\"item item6\">06</div>\n    <div class=\"item item7\">07</div>\n    <div class=\"item item8\">08</div>\n    <div class=\"item item9\">09</div>\n    <div class=\"item item10\">10</div>\n    <div class=\"item item11\">11</div>\n    <div class=\"item item12\">12</div>\n    <div class=\"item item13\">13</div>\n    <div class=\"item item14\">14</div>\n    <div class=\"item item15\">15</div>\n    <div class=\"item item16\">16</div>\n    <div class=\"item item17\">17</div>\n    <div class=\"item item18\">18</div>\n    <div class=\"item item19\">19</div>\n    <div class=\"item item20\">20</div>\n    <div class=\"item item21\">21</div>\n    <div class=\"item item22\">22</div>\n    <div class=\"item item23\">23</div>\n    <div class=\"item item24\">24</div>\n    <div class=\"item item25\">25</div>\n  </div>\n\n<script>\n  const slider = document.querySelector('.items');\n  let isDown = false;\n  let startX;\n  let scrollLeft;\n\n  slider.addEventListener('mousedown', (e) => {\n    isDown = true;\n    slider.classList.add('active');\n    startX = e.pageX - slider.offsetLeft;\n    scrollLeft = slider.scrollLeft;\n  });\n\n  slider.addEventListener('mouseleave', () => {\n    isDown = false;\n    slider.classList.remove('active');\n  });\n\n  slider.addEventListener('mouseup', () => {\n    isDown = false;\n    slider.classList.remove('active');\n  });\n\n  slider.addEventListener('mousemove', (e) => {\n    if (!isDown) return;  // stop the fn from running\n    e.preventDefault();\n    const x = e.pageX - slider.offsetLeft;\n    const walk = (x - startX) * 3;\n    slider.scrollLeft = scrollLeft - walk;\n  });\n\n</script>\n\n  </body>\n</html>\n"
  },
  {
    "path": "27 - Click and Drag/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Click and Drag</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n  <div class=\"items\">\n    <div class=\"item item1\">01</div>\n    <div class=\"item item2\">02</div>\n    <div class=\"item item3\">03</div>\n    <div class=\"item item4\">04</div>\n    <div class=\"item item5\">05</div>\n    <div class=\"item item6\">06</div>\n    <div class=\"item item7\">07</div>\n    <div class=\"item item8\">08</div>\n    <div class=\"item item9\">09</div>\n    <div class=\"item item10\">10</div>\n    <div class=\"item item11\">11</div>\n    <div class=\"item item12\">12</div>\n    <div class=\"item item13\">13</div>\n    <div class=\"item item14\">14</div>\n    <div class=\"item item15\">15</div>\n    <div class=\"item item16\">16</div>\n    <div class=\"item item17\">17</div>\n    <div class=\"item item18\">18</div>\n    <div class=\"item item19\">19</div>\n    <div class=\"item item20\">20</div>\n    <div class=\"item item21\">21</div>\n    <div class=\"item item22\">22</div>\n    <div class=\"item item23\">23</div>\n    <div class=\"item item24\">24</div>\n    <div class=\"item item25\">25</div>\n  </div>\n\n  <script>\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "27 - Click and Drag/style.css",
    "content": "html {\n  box-sizing: border-box;\n  background: url('https://source.unsplash.com/NFs6dRTBgaM/2000x2000') fixed;\n  background-size: cover;\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  min-height: 100vh;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  font-family: sans-serif;\n  font-size: 20px;\n  margin: 0;\n}\n\n.items {\n  height: 800px;\n  padding: 100px;\n  width: 100%;\n  border: 1px solid white;\n  overflow-x: scroll;\n  overflow-y: hidden;\n  white-space: nowrap;\n  user-select: none;\n  cursor: pointer;\n  transition: all 0.2s;\n  transform: scale(0.98);\n  will-change: transform;\n  position: relative;\n  background: rgba(255, 255, 255, 0.1);\n  font-size: 0;\n  perspective: 500px;\n}\n\n.items.active {\n  background: rgba(255, 255, 255, 0.3);\n  cursor: grabbing;\n  cursor: -webkit-grabbing;\n  transform: scale(1);\n}\n\n.item {\n  width: 200px;\n  height: calc(100% - 40px);\n  display: inline-flex;\n  align-items: center;\n  justify-content: center;\n  font-size: 80px;\n  font-weight: 100;\n  color: rgba(0, 0, 0, 0.15);\n  box-shadow: inset 0 0 0 10px rgba(0, 0, 0, 0.15);\n}\n\n.item:nth-child(9n+1) {\n  background: dodgerblue;\n}\n\n.item:nth-child(9n+2) {\n  background: goldenrod;\n}\n\n.item:nth-child(9n+3) {\n  background: paleturquoise;\n}\n\n.item:nth-child(9n+4) {\n  background: gold;\n}\n\n.item:nth-child(9n+5) {\n  background: cadetblue;\n}\n\n.item:nth-child(9n+6) {\n  background: tomato;\n}\n\n.item:nth-child(9n+7) {\n  background: lightcoral;\n}\n\n.item:nth-child(9n+8) {\n  background: darkslateblue;\n}\n\n.item:nth-child(9n+9) {\n  background: rebeccapurple;\n}\n\n.item:nth-child(even) {\n  transform: scaleX(1.31) rotateY(40deg);\n}\n\n.item:nth-child(odd) {\n  transform: scaleX(1.31) rotateY(-40deg);\n}"
  },
  {
    "path": "28 - Video Speed Controller/.vscode/settings.json",
    "content": "{\n    \"git.ignoreLimitWarning\": true\n}"
  },
  {
    "path": "28 - Video Speed Controller/README.md",
    "content": "# Day28 - Video Speed Controller\n\n\n## 效果图\n\n[在线效果](http://30daysofjs.michaeleinsohn.com/video-speed/)\n\n![](http://om1c35wrq.bkt.clouddn.com/day28-xiaoguotu.gif)\n\n第28天的挑战主要是拖拽右边的进度条来设置当前视频的播放速率。\n\n## HTML 源码\n\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Video Speed Scrubber</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <div class=\"wrapper\">\n    <video class=\"flex\" width=\"765\" height=\"430\" src=\"https://www.dropbox.com/s/nf6jfkwck1glsyo/12%20-%20flex-wrapping-and-columns.mp4?dl=1\"\n      loop controls></video>\n    <div class=\"speed\">\n      <div class=\"speed-bar\">1×</div>\n    </div>\n  </div>\n</body>\n\n</html>\n```\n\n由上面的代码不难看出，UI由左边的一个video和右边的一个div组成。\n\n## CSS 代码\n\n```css\nbody {\n  margin: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  min-height: 100vh;\n  background: #4C4C4C url('https://unsplash.it/1500/900?image=1021');\n  background-size: cover;\n  font-family: sans-serif;\n}\n\n.wrapper {\n  width: 850px;\n  display: flex;\n}\n\nvideo {\n  box-shadow: 0 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.speed {\n  background: #efefef;\n  flex: 1;\n  display: flex;\n  align-items: flex-start;\n  margin: 10px;\n  border-radius: 50px;\n  box-shadow: 0 0 1px 3px rgba(0, 0, 0, 0.1);\n  overflow: hidden;\n}\n\n.speed-bar {\n  width: 100%;\n  background: linear-gradient(-170deg, #2376ae 0%, #c16ecf 100%);\n  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 2px;\n  color: white;\n  height: 16.3%;\n}\n```\n\n## JS 源码\n\n```js\n<script>\n  const speed = document.querySelector('.speed');\n  const bar = speed.querySelector('.speed-bar');\n  const video = document.querySelector('.flex');\n\n  function handleMove(e) {\n      const y = e.pageY - this.offsetTop;\n      const percent = y / this.offsetHeight;\n      const min = 0.4;\n      const max = 4;\n      const height = Math.round(percent * 100) + '%';\n      const playbackRate = percent * (max - min) + min;\n      bar.style.height = height;\n      bar.textContent = playbackRate.toFixed(2) + '×';\n      video.playbackRate = playbackRate;\n    }\n\n  speed.addEventListener('mousemove', handleMove);\n\n</script>\n```\n\n**代码逻辑：**\n\n- 获取组建\n\n```js\nconst speed = document.querySelector('.speed');\nconst bar = speed.querySelector('.speed-bar');\nconst video = document.querySelector('.flex');\n```\n\n\n- 监听`speed`控件，鼠标是否处于移动状态，如果在移动，调用`handleMove`函数。\n\n```js\nspeed.addEventListener('mousemove', handleMove);\n```\n\n- 改变`speed`的状态，以及及时更新`video`的`playbackRate`属性以改变播放速率\n\n```js\nfunction handleMove(e) {\n    const y = e.pageY - this.offsetTop;\n    const percent = y / this.offsetHeight;\n    const min = 0.4;\n    const max = 4;\n    const height = Math.round(percent * 100) + '%';\n    const playbackRate = percent * (max - min) + min;\n    bar.style.height = height;\n    bar.textContent = playbackRate.toFixed(2) + '×';\n    video.playbackRate = playbackRate;\n}\n```\n\n\n"
  },
  {
    "path": "28 - Video Speed Controller/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Video Speed Scrubber</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n<body>\n\n  <div class=\"wrapper\">\n    <video class=\"flex\" width=\"765\" height=\"430\" src=\"./001项目效果演示以及知识点分析.mp4\" loop controls></video>\n    <div class=\"speed\">\n      <div class=\"speed-bar\">1×</div>\n    </div>\n  </div>\n\n<script>\n  const speed = document.querySelector('.speed');\n  const bar = speed.querySelector('.speed-bar');\n  const video = document.querySelector('.flex');\n\n  function handleMove(e) {\n      const y = e.pageY - this.offsetTop;\n      const percent = y / this.offsetHeight;\n      const min = 0.4;\n      const max = 4;\n      const height = Math.round(percent * 100) + '%';\n      const playbackRate = percent * (max - min) + min;\n      bar.style.height = height;\n      bar.textContent = playbackRate.toFixed(2) + '×';\n      video.playbackRate = playbackRate;\n    }\n\n  speed.addEventListener('mousemove', handleMove);\n\n</script>\n</body>\n</html>\n"
  },
  {
    "path": "28 - Video Speed Controller/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Video Speed Scrubber</title>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <div class=\"wrapper\">\n    <video class=\"flex\" width=\"765\" height=\"430\" src=\"https://www.dropbox.com/s/nf6jfkwck1glsyo/12%20-%20flex-wrapping-and-columns.mp4?dl=1\"\n      loop controls></video>\n    <div class=\"speed\">\n      <div class=\"speed-bar\">1×</div>\n    </div>\n  </div>\n\n  <script>\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "28 - Video Speed Controller/style.css",
    "content": "body {\n  margin: 0;\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  min-height: 100vh;\n  background: #4C4C4C url('https://unsplash.it/1500/900?image=1021');\n  background-size: cover;\n  font-family: sans-serif;\n}\n\n.wrapper {\n  width: 850px;\n  display: flex;\n}\n\nvideo {\n  box-shadow: 0 0 1px 3px rgba(0, 0, 0, 0.1);\n}\n\n.speed {\n  background: #efefef;\n  flex: 1;\n  display: flex;\n  align-items: flex-start;\n  margin: 10px;\n  border-radius: 50px;\n  box-shadow: 0 0 1px 3px rgba(0, 0, 0, 0.1);\n  overflow: hidden;\n}\n\n.speed-bar {\n  width: 100%;\n  background: linear-gradient(-170deg, #2376ae 0%, #c16ecf 100%);\n  text-shadow: 1px 1px 0 rgba(0, 0, 0, 0.2);\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  padding: 2px;\n  color: white;\n  height: 16.3%;\n}"
  },
  {
    "path": "29 - Countdown Timer/.vscode/settings.json",
    "content": "{\n    \"git.ignoreLimitWarning\": true\n}"
  },
  {
    "path": "29 - Countdown Timer/README.md",
    "content": "# Day29 - Countdown Timer\n\n## 效果图\n\n[在线效果](http://30daysofjs.michaeleinsohn.com/countdown-timer/)\n\n![](http://om1c35wrq.bkt.clouddn.com/day29--xiaoguotu.gif)\n\n第20天的挑战是，设置一个倒计时时间，接下来开始倒计时。\n\n## HTML代码\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Countdown Timer</title>\n  <link href='https://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n  <div class=\"timer\">\n    <div class=\"timer__controls\">\n      <button data-time=\"20\" class=\"timer__button\">20 Secs</button>\n      <button data-time=\"300\" class=\"timer__button\">Work 5</button>\n      <button data-time=\"900\" class=\"timer__button\">Quick 15</button>\n      <button data-time=\"1200\" class=\"timer__button\">Snack 20</button>\n      <button data-time=\"3600\" class=\"timer__button\">Lunch Break</button>\n      <form name=\"customForm\" id=\"custom\">\n        <input type=\"text\" name=\"minutes\" placeholder=\"Enter Minutes\">\n      </form>\n    </div>\n    <div class=\"display\">\n      <h1 class=\"display__time-left\"></h1>\n      <p class=\"display__end-time\"></p>\n    </div>\n  </div>\n</body>\n</html>\n```\n\n- 上面的`button`中自定义的`data-time`为倒计时时间，以秒为单位。\n- `form`为自定义倒计时时间，以分为单位。\n- class为`display__time-left`的div主要为了展示倒计时的动态。\n- class为`display__end-time`的div主要为了展示倒计时什么时候结束。\n\n## CSS 代码\n\n```css\nhtml {\n  box-sizing: border-box;\n  font-size: 10px;\n  background: #8E24AA;\n  background: linear-gradient(45deg, #42a5f5 0%, #478ed1 50%, #0d47a1 100%);\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n  text-align: center;\n  font-family: 'Inconsolata', monospace;\n}\n\n.display__time-left {\n  font-weight: 100;\n  font-size: 20rem;\n  margin: 0;\n  color: white;\n  text-shadow: 4px 4px 0 rgba(0, 0, 0, 0.05);\n}\n\n.timer {\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n}\n\n.timer__controls {\n  display: flex;\n}\n\n.timer__controls>* {\n  flex: 1;\n}\n\n.timer__controls form {\n  flex: 1;\n  display: flex;\n}\n\n.timer__controls input {\n  flex: 1;\n  border: 0;\n  padding: 2rem;\n}\n\n.timer__button {\n  background: none;\n  border: 0;\n  cursor: pointer;\n  color: white;\n  font-size: 2rem;\n  text-transform: uppercase;\n  background: rgba(0, 0, 0, 0.1);\n  border-bottom: 3px solid rgba(0, 0, 0, 0.2);\n  border-right: 1px solid rgba(0, 0, 0, 0.2);\n  padding: 1rem;\n  font-family: 'Inconsolata', monospace;\n}\n\n.timer__button:hover,\n.timer__button:focus {\n  background: rgba(0, 0, 0, 0.2);\n  outline: 0;\n}\n\n.display {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n}\n\n.display__end-time {\n  font-size: 4rem;\n  color: white;\n}\n```\n\n## JS代码实现逻辑\n\n```js\nlet countdown;\nconst timerDisplay = document.querySelector('.display__time-left');\nconst endTime = document.querySelector('.display__end-time');\nconst buttons = document.querySelectorAll('[data-time]');\n\nfunction timer(seconds) {\n  // clear any existing timers\n  clearInterval(countdown);\n\n  const now = Date.now();\n  const then = now + seconds * 1000;\n  displayTimeLeft(seconds);\n  displayEndTime(then);\n\n  countdown = setInterval(() => {\n    const secondsLeft = Math.round((then - Date.now()) / 1000);\n    // check if we should stop it!\n    if (secondsLeft < 0) {\n      clearInterval(countdown);\n      return;\n    }\n    // display it\n    displayTimeLeft(secondsLeft);\n  }, 1000);\n}\n\nfunction displayTimeLeft(seconds) {\n  const minutes = Math.floor(seconds / 60);\n  const remainderSeconds = seconds % 60;\n  const display = `${minutes}:${remainderSeconds < 10 ? '0' : '' }${remainderSeconds}`;\n  document.title = display;\n  timerDisplay.textContent = display;\n}\n\nfunction displayEndTime(timestamp) {\n  const end = new Date(timestamp);\n  const hour = end.getHours();\n  const adjustedHour = hour > 12 ? hour - 12 : hour;\n  const minutes = end.getMinutes();\n  endTime.textContent = `Be Back At ${adjustedHour}:${minutes < 10 ? '0' : ''}${minutes}`;\n}\n\nfunction startTimer() {\n  const seconds = parseInt(this.dataset.time);\n  timer(seconds);\n}\n\nbuttons.forEach(button => button.addEventListener('click', startTimer));\n\ndocument.customForm.addEventListener('submit', function (e) {\n  e.preventDefault();\n  const mins = this.minutes.value;\n  console.log(mins);\n  timer(mins * 60);\n  this.reset();\n});\n```\n**逻辑分析：**\n- 当点击button按钮时，调用`startTimer`方法。\n```js\nbuttons.forEach(button => button.addEventListener('click', startTimer));\n```\n\n- 在输入框中自定义倒计时时间时，输入`enter`时调用传入的回调函数。\n\n```js\ndocument.customForm.addEventListener('submit', function (e) {\n  e.preventDefault();\n  const mins = this.minutes.value;\n  console.log(mins);\n  timer(mins * 60);\n  this.reset();\n});\n```\n\n\n- startTimer函数代码解释\n\n```js\nfunction startTimer() {\n<!--获取当前点击的button的data-time值，并将其转换成整数-->\n  const seconds = parseInt(this.dataset.time);\n  <!--调用timer函数-->\n  timer(seconds);\n}\n```\n\n\n- displayTimeLeft代码\n\n```js\n<!--计算时分秒，并且展示-->\nfunction displayTimeLeft(seconds) {\n  const minutes = Math.floor(seconds / 60);\n  const remainderSeconds = seconds % 60;\n  const display = `${minutes}:${remainderSeconds < 10 ? '0' : '' }${remainderSeconds}`;\n  document.title = display;\n  timerDisplay.textContent = display;\n}\n```\n\n\n- displayEndTime代码\n\n```js\n<!--计算倒计时结束时间，并且展示-->\nfunction displayEndTime(timestamp) {\n  const end = new Date(timestamp);\n  const hour = end.getHours();\n  const adjustedHour = hour > 12 ? hour - 12 : hour;\n  const minutes = end.getMinutes();\n  endTime.textContent = `Be Back At ${adjustedHour}:${minutes < 10 ? '0' : ''}${minutes}`;\n}\n```\n\n- timer代码\n\n```js\nfunction timer(seconds) {\n  // clear any existing timers\n  清除正在进行的倒计时\n  clearInterval(countdown);\n\n<!--获取当前时间-->\n  const now = Date.now();\n  \n  <!--计算多少毫秒后结束倒计时-->\n  const then = now + seconds * 1000;\n  \n  <!--调用displayTimeLeft函数展示倒计时效果-->\n  displayTimeLeft(seconds);\n  <!--展示结束时间-->\n  displayEndTime(then);\n\n<!--设置定时器，更新倒计时组建-->\n  countdown = setInterval(() => {\n    const secondsLeft = Math.round((then - Date.now()) / 1000);\n    // check if we should stop it!\n    if (secondsLeft < 0) {\n      clearInterval(countdown);\n      return;\n    }\n    // display it\n    displayTimeLeft(secondsLeft);\n  }, 1000);\n}\n```\n"
  },
  {
    "path": "29 - Countdown Timer/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Countdown Timer</title>\n  <link href='https://fonts.googleapis.com/css?family=Inconsolata' rel='stylesheet' type='text/css'>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n  <div class=\"timer\">\n    <div class=\"timer__controls\">\n      <button data-time=\"20\" class=\"timer__button\">20 Secs</button>\n      <button data-time=\"300\" class=\"timer__button\">Work 5</button>\n      <button data-time=\"900\" class=\"timer__button\">Quick 15</button>\n      <button data-time=\"1200\" class=\"timer__button\">Snack 20</button>\n      <button data-time=\"3600\" class=\"timer__button\">Lunch Break</button>\n      <form name=\"customForm\" id=\"custom\">\n        <input type=\"text\" name=\"minutes\" placeholder=\"Enter Minutes\">\n      </form>\n    </div>\n    <div class=\"display\">\n      <h1 class=\"display__time-left\"></h1>\n      <p class=\"display__end-time\"></p>\n    </div>\n  </div>\n\n  <script src=\"scripts-FINISHED.js\"></script>\n</body>\n\n</html>"
  },
  {
    "path": "29 - Countdown Timer/scripts-FINISHED.js",
    "content": "let countdown;\nconst timerDisplay = document.querySelector('.display__time-left');\nconst endTime = document.querySelector('.display__end-time');\nconst buttons = document.querySelectorAll('[data-time]');\n\nfunction timer(seconds) {\n  // clear any existing timers\n  clearInterval(countdown);\n\n  const now = Date.now();\n  const then = now + seconds * 1000;\n  displayTimeLeft(seconds);\n  displayEndTime(then);\n\n  countdown = setInterval(() => {\n    const secondsLeft = Math.round((then - Date.now()) / 1000);\n    // check if we should stop it!\n    if (secondsLeft < 0) {\n      clearInterval(countdown);\n      return;\n    }\n    // display it\n    displayTimeLeft(secondsLeft);\n  }, 1000);\n}\n\nfunction displayTimeLeft(seconds) {\n  const minutes = Math.floor(seconds / 60);\n  const remainderSeconds = seconds % 60;\n  const display = `${minutes}:${remainderSeconds < 10 ? '0' : '' }${remainderSeconds}`;\n  document.title = display;\n  timerDisplay.textContent = display;\n}\n\nfunction displayEndTime(timestamp) {\n  const end = new Date(timestamp);\n  const hour = end.getHours();\n  const adjustedHour = hour > 12 ? hour - 12 : hour;\n  const minutes = end.getMinutes();\n  endTime.textContent = `Be Back At ${adjustedHour}:${minutes < 10 ? '0' : ''}${minutes}`;\n}\n\nfunction startTimer() {\n  const seconds = parseInt(this.dataset.time);\n  timer(seconds);\n}\n\nbuttons.forEach(button => button.addEventListener('click', startTimer));\ndocument.customForm.addEventListener('submit', function (e) {\n  e.preventDefault();\n  const mins = this.minutes.value;\n  console.log(mins);\n  timer(mins * 60);\n  this.reset();\n});"
  },
  {
    "path": "29 - Countdown Timer/scripts-START.js",
    "content": ""
  },
  {
    "path": "29 - Countdown Timer/style.css",
    "content": "html {\n  box-sizing: border-box;\n  font-size: 10px;\n  background: #8E24AA;\n  background: linear-gradient(45deg, #42a5f5 0%, #478ed1 50%, #0d47a1 100%);\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  margin: 0;\n  text-align: center;\n  font-family: 'Inconsolata', monospace;\n}\n\n.display__time-left {\n  font-weight: 100;\n  font-size: 20rem;\n  margin: 0;\n  color: white;\n  text-shadow: 4px 4px 0 rgba(0, 0, 0, 0.05);\n}\n\n.timer {\n  display: flex;\n  min-height: 100vh;\n  flex-direction: column;\n}\n\n.timer__controls {\n  display: flex;\n}\n\n.timer__controls>* {\n  flex: 1;\n}\n\n.timer__controls form {\n  flex: 1;\n  display: flex;\n}\n\n.timer__controls input {\n  flex: 1;\n  border: 0;\n  padding: 2rem;\n}\n\n.timer__button {\n  background: none;\n  border: 0;\n  cursor: pointer;\n  color: white;\n  font-size: 2rem;\n  text-transform: uppercase;\n  background: rgba(0, 0, 0, 0.1);\n  border-bottom: 3px solid rgba(0, 0, 0, 0.2);\n  border-right: 1px solid rgba(0, 0, 0, 0.2);\n  padding: 1rem;\n  font-family: 'Inconsolata', monospace;\n}\n\n.timer__button:hover,\n.timer__button:focus {\n  background: rgba(0, 0, 0, 0.2);\n  outline: 0;\n}\n\n.display {\n  flex: 1;\n  display: flex;\n  flex-direction: column;\n  align-items: center;\n  justify-content: center;\n}\n\n.display__end-time {\n  font-size: 4rem;\n  color: white;\n}"
  },
  {
    "path": "30 - Whack A Mole/README.md",
    "content": "# Day30 - Whack A Mole\n\n\n\n## 效果图\n\n[在线效果图]()\n\n![](http://om1c35wrq.bkt.clouddn.com/day30-xiaoguotu.gif)\n\n\n第30天挑战主要是通过JS原生实现打地鼠游戏，点击开始按钮，地鼠随机在6个地洞中出现，敲中地鼠得即增加一分。\n\n## HTML 代码\n\n```html\n<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Whack A Mole!</title>\n  <link href='https://fonts.googleapis.com/css?family=Amatic+SC:400,700' rel='stylesheet' type='text/css'>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <h1>Whack-a-mole! <span class=\"score\">0</span></h1>\n  <button onClick=\"startGame()\">Start!</button>\n\n  <div class=\"game\">\n    <div class=\"hole hole1\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole2\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole3\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole4\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole5\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole6\">\n      <div class=\"mole\"></div>\n    </div>\n  </div>\n\n</body>\n</html>\n```\n\n- 开始游戏的按钮\n\n```html\n <button onClick=\"startGame()\">Start!</button>\n```\n\n- 展示所得分数\n\n```html\n<h1>Whack-a-mole! <span class=\"score\">0</span></h1>\n```\n\n- 地洞、地鼠\n\n```html\n<div class=\"game\">\n    <div class=\"hole hole1\">\n        <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole2\">\n        <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole3\">\n        <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole4\">\n        <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole5\">\n        <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole6\">\n        <div class=\"mole\"></div>\n    </div>\n</div>\n```\n\n`hole`为地洞，地洞中有一个地鼠`mole`。\n\n## CSS 代码\n\n```css\nhtml {\n  box-sizing: border-box;\n  font-size: 10px;\n  background: #ffc600;\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  padding: 0;\n  margin: 0;\n  font-family: 'Amatic SC', cursive;\n}\n\nh1 {\n  text-align: center;\n  font-size: 10rem;\n  line-height: 1;\n  margin-bottom: 0;\n}\n\n.score {\n  background: rgba(255, 255, 255, 0.2);\n  padding: 0 3rem;\n  line-height: 1;\n  border-radius: 1rem;\n}\n\n.game {\n  width: 600px;\n  height: 400px;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 auto;\n}\n\n.hole {\n  flex: 1 0 33.33%;\n  overflow: hidden;\n  position: relative;\n}\n\n.hole:after {\n  display: block;\n  background: url(dirt.svg) bottom center no-repeat;\n  background-size: contain;\n  content: '';\n  width: 100%;\n  height: 70px;\n  position: absolute;\n  z-index: 2;\n  bottom: -30px;\n}\n\n.mole {\n  background: url('mole.svg') bottom center no-repeat;\n  background-size: 60%;\n  position: absolute;\n  top: 100%;\n  width: 100%;\n  height: 100%;\n  transition: all 0.4s;\n}\n\n.hole.up .mole {\n  top: 0;\n}\n```\n\n- `dirt.svg`为地洞的图片\n\n- `mole.svg`为地鼠的图片\n\n## JS代码\n\n```js\n  <script>\n    const holes = document.querySelectorAll('.hole');\n    const scoreBoard = document.querySelector('.score');\n    const moles = document.querySelectorAll('.mole');\n    let lastHole;\n    let timeUp = false;\n    let score = 0;\n\n    function randomTime(min, max) {\n      return Math.round(Math.random() * (max - min) + min);\n    }\n\n    function randomHole(holes) {\n      const idx = Math.floor(Math.random() * holes.length);\n      const hole = holes[idx];\n      if (hole === lastHole) {\n        console.log('Ah nah thats the same one bud');\n        return randomHole(holes);\n      }\n      lastHole = hole;\n      return hole;\n    }\n\n    function peep() {\n      const time = randomTime(200, 1000);\n      const hole = randomHole(holes);\n      hole.classList.add('up');\n      setTimeout(() => {\n        hole.classList.remove('up');\n        if (!timeUp) peep();\n      }, time);\n    }\n\n    function startGame() {\n      scoreBoard.textContent = 0;\n      timeUp = false;\n      score = 0;\n      peep();\n      setTimeout(() => timeUp = true, 10000)\n    }\n\n    function bonk(e) {\n      if (!e.isTrusted) return; // cheater!\n      score++;\n      this.parentNode.classList.remove('up');\n      scoreBoard.textContent = score;\n    }\n\n    moles.forEach(mole => mole.addEventListener('click', bonk));\n  </script>\n```\n\n- 获取所有的地洞、得分、地鼠标签\n\n```js\nconst holes = document.querySelectorAll('.hole');\nconst scoreBoard = document.querySelector('.score');\nconst moles = document.querySelectorAll('.mole');\n```\n\n- 初始化变量\n\n```js\nlet lastHole; //用于存储上一次的地洞，主要用于和下一次做对比\nlet timeUp = false; //判断时间是否结束\nlet score = 0; //记录得分\n```\n\n- 事件监听\n\n```js\n<!--敲中地鼠，调用bonk方法-->\nmoles.forEach(mole => mole.addEventListener('click', bonk));\n```\n\n- isTrusted属性\n\n```js\nfunction bonk(e) {\n     if (!e.isTrusted) return; // 判断是不是操作者操作，而非代码程序操作!\n     score++; // 得一分\n     this.parentNode.classList.remove('up'); // 隐藏地鼠\n     scoreBoard.textContent = score; //更新得分\n}\n```\n\n[Event.isTrusted](https://developer.mozilla.org/zh-TW/docs/Web/API/Event/isTrusted)\n\n`Event` 界面的 `isTrusted` 只读属性为一个布林值，若事件物件是由使用者操作而产生，則 `isTrusted` 值为 true。若事件物件是由程式码所建立、修改，或是透过 `EventTarget.dispatchEvent()` 来触发，则`isTrusted` 值为 `false`。\n\n\n- 开始游戏\n\n```js\nfunction startGame() {\n    scoreBoard.textContent = 0; //清空得分\n    timeUp = false; //将时间是否结束标志设置为false\n    score = 0; //得分归零\n    peep(); //调用peep()函数\n    setTimeout(() => timeUp = true, 10000) //设置倒计时\n}\n```\n\n- randomTime设置一个`min - max`之间的随机数\n\n[Math.round](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Math/round)\n\n```js\nMath.round( 20.49); //  20\nMath.round( 20.5);  //  21\nMath.round( 42  );  //  42\nMath.round(-20.5);  // -20\nMath.round(-20.51); // -21\n```\n\n```js\nfunction randomTime(min, max) {\n    //Math.round() 函数返回一个数字四舍五入后最接近的整数值。\n    //Math.random() 函数返回一个浮点,  伪随机数在范围[0，1)，然后您可以缩放到所需的范围。\n    return Math.round(Math.random() * (max - min) + min);\n}\n```\n\n- randomHole函数\n\n[Math.floor](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Math/floor)\n\n```js\nMath.floor( 45.95); //  45\nMath.floor( 45.05); //  45\nMath.floor(  4   ); //   4\nMath.floor(-45.05); // -46 \nMath.floor(-45.95); // -46\n```\n\n```js\n//随机选出一个地洞，用于弹出地鼠\nfunction randomHole(holes) {\n    //Math.floor() 返回小于或等于一个给定数字的最大整数。\n    const idx = Math.floor(Math.random() * holes.length);\n    const hole = holes[idx];\n    if (hole === lastHole) {\n        console.log('Ah nah thats the same one bud');\n        return randomHole(holes);\n    }\n    lastHole = hole;\n    return hole;\n}\n```\n\n\n- peep()函数\n\n```js\n// 地鼠、地洞处理函数\nfunction peep() {\n    //产生一个随机数，这个数主要用于设置当前的地鼠什么时候消失\n    const time = randomTime(200, 1000);\n    //随机产生一个 0 - 5的值，用于随机选择一个地洞\n    const hole = randomHole(holes);\n    //弹出地鼠\n    hole.classList.add('up');\n    //time时间后地鼠消失\n    setTimeout(() => {\n        hole.classList.remove('up');\n        //如果倒计时没到，继续调用peep()函数\n        if (!timeUp) \n            peep();\n    }, time);\n}\n```\n\n\n"
  },
  {
    "path": "30 - Whack A Mole/index-FINISHED.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Whack A Mole!</title>\n  <link href='https://fonts.googleapis.com/css?family=Amatic+SC:400,700' rel='stylesheet' type='text/css'>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <h1>Whack-a-mole! <span class=\"score\">0</span></h1>\n  <button onClick=\"startGame()\">Start!</button>\n\n  <div class=\"game\">\n    <div class=\"hole hole1\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole2\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole3\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole4\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole5\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole6\">\n      <div class=\"mole\"></div>\n    </div>\n  </div>\n\n  <script>\n    const holes = document.querySelectorAll('.hole');\n    const scoreBoard = document.querySelector('.score');\n    const moles = document.querySelectorAll('.mole');\n    let lastHole;\n    let timeUp = false;\n    let score = 0;\n\n    function randomTime(min, max) {\n      return Math.round(Math.random() * (max - min) + min);\n    }\n\n    function randomHole(holes) {\n      const idx = Math.floor(Math.random() * holes.length);\n      const hole = holes[idx];\n      if (hole === lastHole) {\n        console.log('Ah nah thats the same one bud');\n        return randomHole(holes);\n      }\n      lastHole = hole;\n      return hole;\n    }\n\n    function peep() {\n      const time = randomTime(200, 1000);\n      const hole = randomHole(holes);\n      hole.classList.add('up');\n      setTimeout(() => {\n        hole.classList.remove('up');\n        if (!timeUp) peep();\n      }, time);\n    }\n\n    function startGame() {\n      scoreBoard.textContent = 0;\n      timeUp = false;\n      score = 0;\n      peep();\n      setTimeout(() => timeUp = true, 10000)\n    }\n\n    function bonk(e) {\n      if (!e.isTrusted) return; // cheater!\n      score++;\n      this.parentNode.classList.remove('up');\n      scoreBoard.textContent = score;\n    }\n\n    moles.forEach(mole => mole.addEventListener('click', bonk));\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "30 - Whack A Mole/index-START.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n\n<head>\n  <meta charset=\"UTF-8\">\n  <title>Whack A Mole!</title>\n  <link href='https://fonts.googleapis.com/css?family=Amatic+SC:400,700' rel='stylesheet' type='text/css'>\n  <link rel=\"stylesheet\" href=\"style.css\">\n</head>\n\n<body>\n\n  <h1>Whack-a-mole! <span class=\"score\">0</span></h1>\n  <button onClick=\"startGame()\">Start!</button>\n\n  <div class=\"game\">\n    <div class=\"hole hole1\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole2\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole3\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole4\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole5\">\n      <div class=\"mole\"></div>\n    </div>\n    <div class=\"hole hole6\">\n      <div class=\"mole\"></div>\n    </div>\n  </div>\n\n  <script>\n    const holes = document.querySelectorAll('.hole');\n    const scoreBoard = document.querySelector('.score');\n    const moles = document.querySelectorAll('.mole');\n  </script>\n</body>\n\n</html>"
  },
  {
    "path": "30 - Whack A Mole/style.css",
    "content": "html {\n  box-sizing: border-box;\n  font-size: 10px;\n  background: #ffc600;\n}\n\n*,\n*:before,\n*:after {\n  box-sizing: inherit;\n}\n\nbody {\n  padding: 0;\n  margin: 0;\n  font-family: 'Amatic SC', cursive;\n}\n\nh1 {\n  text-align: center;\n  font-size: 10rem;\n  line-height: 1;\n  margin-bottom: 0;\n}\n\n.score {\n  background: rgba(255, 255, 255, 0.2);\n  padding: 0 3rem;\n  line-height: 1;\n  border-radius: 1rem;\n}\n\n.game {\n  width: 600px;\n  height: 400px;\n  display: flex;\n  flex-wrap: wrap;\n  margin: 0 auto;\n}\n\n.hole {\n  flex: 1 0 33.33%;\n  overflow: hidden;\n  position: relative;\n}\n\n.hole:after {\n  display: block;\n  background: url(dirt.svg) bottom center no-repeat;\n  background-size: contain;\n  content: '';\n  width: 100%;\n  height: 70px;\n  position: absolute;\n  z-index: 2;\n  bottom: -30px;\n}\n\n.mole {\n  background: url('mole.svg') bottom center no-repeat;\n  background-size: 60%;\n  position: absolute;\n  top: 100%;\n  width: 100%;\n  height: 100%;\n  transition: all 0.4s;\n}\n\n.hole.up .mole {\n  top: 0;\n}"
  },
  {
    "path": "31 - Canvas CountClock/Countdown.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n    <meta charset=\"UTF-8\">\n    <title>canvas倒计时特效</title>\n    <style>\n        #canvas{\n            /*border:1px solid #444;*/\n            display: block;\n            margin:50px auto;\n        }\n    </style>\n</head>\n<body style=\"min-height:100vh\">\n    <canvas id=\"canvas\" style=\"min-height:100vh\">\n        <p>抱歉，您的浏览器暂不支持canvas属性！</p>\n    </canvas>\n    <script src=\"digit.js\"></script>\n    <script src=\"Countdown.js\"></script>\n</body>\n</html>"
  },
  {
    "path": "31 - Canvas CountClock/Countdown.js",
    "content": "/*\n* @Author: Administrator\n* @Date:   2018-12-22 15:41:18\n* @Last Modified by:   Administrator\n* @Last Modified time: 2018-12-23 20:19:12\n*/\n // 主干程序放在了页面加载函数中，封装各个函数供主干程序调用，将都用到的变量提出作为全局变量。\n        //定义的是全局变量，不仅在页面加载函数中能用，在自定义函数中也能使用。\n        // let WINDOW_WIDTH=1090;\n        // let WINDOW_HEIGHT=768;\n        // let RADIUS=8;\n        // let MARGIN_LEFT=60;\n        // let MARGIN_TOP=200;\n        // const endTime = new Date(2018,11,22,23,00,00);  //设置倒计时的截止时间\n        \n        let endTime = new Date();\n        endTime.setTime(endTime.getTime() + 3600*1000); //倒计时截止时间是当前时间之后的一个小时。        \n        let curShowTimeSeconds=0; //用于显示当前距离截止时间的毫秒数。\n\n        let balls=[];//用于存放小球，将新增加的小球添加进去。\n        //预设小球的颜色值，生成的小球来随机取其中的某一个值。\n        const colors=[\"#33B545\",\"#009CC\",\"#AA66CC\",\"#9933CC\",\"#99CC00\",\"#669900\",\"#FFBB33\",\"#FF8800\",\"#FF4444\",\"#CC0000\"];\n        //当页面尺寸大小改变时，进行页面刷新。\n        // let innerWidth  = window.innerWidth;\n        // let innerHeight = window.innerHeight;\n        // function resizeFresh(){\n        //     if(innerWidth!=window.innerWidth|| innerHeight!=window.innerHeight){\n        //         location.reload();\n        //     }\n        // }\n        //当页面尺寸大小改变时，进行页面刷新。\n        window.onresize = function(){\n            location.reload();\n        }\n        //页面加载函数\n        window.onload=function(){\n            console.log(\"ddd\");\n            WINDOW_HEIGHT = document.body.clientHeight;\n            WINDOW_WIDTH  = document.body.clientWidth;\n            MARGIN_LEFT   = Math.round(WINDOW_WIDTH/10);  //除去时钟显示部分的五分之四，剩下的五分之一再左右进行两等分。\n            RADIUS        = Math.round(WINDOW_WIDTH*(4/5)/108)-1;//时钟的显示部分占据整个网页可视区域的（4/5），时钟的第一个数字是从x=0开始的，根据每一个数字的宽度进行计算得到是108，\n            MARGIN_TOP    = Math.round(WINDOW_HEIGHT/5);\n\n            let canvas   = document.querySelector(\"#canvas\");\n            let context  = canvas.getContext(\"2d\");\n            canvas.width = WINDOW_WIDTH;\n            canvas.height= WINDOW_HEIGHT;\n\n            curShowTimeSeconds = getCurrentShowTimeSeconds();\n            setInterval(()=>{\n                render(context);\n                update();\n                console.log(balls.length);\n            },50);\n        }\n\n        //通过这个函数得到当前距离截止时间显示的毫秒数。\n        function getCurrentShowTimeSeconds(){\n            var curTime = new Date();\n            var ret = endTime.getTime()-curTime.getTime();  //截止时间减去当前时间得到的毫秒数。\n            ret = Math.round(ret/1000);//将毫秒数转换为秒数。\n            return ret >= 0 ? ret : 0;  \n        }\n\n         // 更新函数：1.更新时间；2.更新动画，3.生成小球\n        function update(){\n            //得到下一次距离截止时间的秒数。\n            let nextShowTimeSeconds = getCurrentShowTimeSeconds();//注意getCurrentShowTimeSeconds这里掉了括号“（）”也就是说没有执行函数。\n            let nextHours = parseInt( nextShowTimeSeconds / 3600);\n            let nextMinutes = parseInt((nextShowTimeSeconds-nextHours*3600)/60);\n            let nextSeconds = parseInt(nextShowTimeSeconds%60);\n\n            let curHours   = parseInt(curShowTimeSeconds/3600);  \n            let curMinutes = parseInt((curShowTimeSeconds-curHours*3600)/60); \n            let curSeconds = parseInt(curShowTimeSeconds%60); \n            //如果时间发生改变，将改变后的时间赋予给当前时间；根据改变的是哪个数字，来给该数字添加小球。\n            if(nextSeconds!=curSeconds){\n                curShowTimeSeconds=nextShowTimeSeconds;\n                //时针的十位数改变时，在相应数字的位置处添加小球。\n                if(parseInt(curHours/10)!=parseInt(nextHours/10)){\n                    addBalls(MARGIN_LEFT, MARGIN_TOP, parseInt(curHours/10));\n                }\n                if(parseInt(curHours%10)!=parseInt(nextHours%10)){\n                    addBalls(MARGIN_LEFT+15*(RADIUS+1), MARGIN_TOP, parseInt(curHours%10));\n                }\n\n                if(parseInt(curMinutes/10)!=parseInt(nextMinutes/10)){\n                    addBalls(MARGIN_LEFT+39*(RADIUS+1), MARGIN_TOP, parseInt(curMinutes/10));\n                }\n                if(parseInt(curMinutes%10)!=parseInt(nextMinutes%10)){\n                    addBalls(MARGIN_LEFT+54*(RADIUS+1), MARGIN_TOP, parseInt(curMinutes%10));\n                }\n\n                if(parseInt(curSeconds/10)!=parseInt(nextSeconds/10)){\n                    addBalls(MARGIN_LEFT+78*(RADIUS+1), MARGIN_TOP, parseInt(curSeconds/10));\n                }\n                if(parseInt(curSeconds%10)!=parseInt(nextSeconds%10)){\n                    addBalls(MARGIN_LEFT+93*(RADIUS+1), MARGIN_TOP, parseInt(curSeconds%10));\n                }\n             \n            }\n            updateBalls();//更新每个小球的运动状态。\n        }\n\n        //小球运动状态的更新设置。\n        function updateBalls(){\n            for(let i=0;i<balls.length;i++){\n                balls[i].x += balls[i].vx;\n                balls[i].y += balls[i].vy;\n                balls[i].vy += balls[i].g;\n\n                // 碰撞检测\n                if(balls[i].y  >= WINDOW_HEIGHT-RADIUS){\n                   balls[i].y  =  WINDOW_HEIGHT-RADIUS;//小球落到底部的位置 \n                   balls[i].vy = -balls[i].vy*0.75;  //速度反向，进行反弹,0.5表示摩擦系数，每次反弹速度都减半。\n                }\n            }\n            // 将滚出画布的小球进行删除处理。\n            balls=balls.filter((item)=>{\n                return item.x+RADIUS>0 && item.x - RADIUS < WINDOW_WIDTH;\n            });\n\n            // 将滚出画布的小球进行删除处理。\n            // let cnt=0;\n            // for(let i=0;i<balls.length;i++){\n            //     if(balls[i].x+RADIUS>0&&balls[i].x - RADIUS < WINDOW_WIDTH){ //小球的右边缘只要大于0，左边缘小于画布宽度就就符合，可以用左右临近或出去一半画布的小球进行分析。\n            //         balls[cnt++]=balls[i];//遍历整个balls数组，将符合要求的小球放在以cnt计数的数组中\n            //     }\n            // }\n            // //经过上面的上面的这一组循环之后，可以知道cnt之前包括cnt的数据项都是符合的小球，后面的便是不符的，进行一项一项删除。\n            // while(balls.length>cnt){\n            //     balls.pop();\n            // }\n\n        }\n\n        //遍历循环每一个数的点阵屏，在点阵屏上数字为“1”处生成小球函数，将生成的小球放到数组ball中。\n        function addBalls(x,y,num){\n            for(let i=0;i<digit[num].length;i++){\n                for(let j=0;j<digit[num][i].length;j++){\n                    if(digit[num][i][j]===1){\n                        let aball={\n                            x     : x+(RADIUS+1)+j*2*(RADIUS+1),//定义生成小球球心x点的坐标\n                            y     : y+(RADIUS+1)+i*2*(RADIUS+1),//定义生成小球球心y点的坐标\n                            vx    : Math.pow(-1,Math.ceil(Math.random()*10))*6,//用0-1之间的随机数乘以10,然后向上取整得到奇数或偶数，在由-1的奇偶次幂得到正负4.\n                            vy    : -6,\n                            g     : 1.5+Math.random(),\n                            color : colors[Math.floor(Math.random()*colors.length)]\n                        };\n                        balls.push(aball);//每产生一个小球就放到全局容器balls中。\n                    }\n                }\n            }\n        }\n\n        //将上下文环境进行渲染\n        function render(cxt){\n            cxt.clearRect(0,0,WINDOW_WIDTH,WINDOW_HEIGHT);  //绘图之前进行刷新操作\n            //会根据curShowTimeSeconds计算出时分秒进行更新数字。\n            let hours   = parseInt(curShowTimeSeconds/3600);  //求有多少个小时\n            let minutes = parseInt((curShowTimeSeconds-hours*3600)/60); //求有多少分钟\n            let seconds = parseInt(curShowTimeSeconds%60);  //所有的秒数对分钟求余得到秒数\n\n            renderdigit(MARGIN_LEFT,               MARGIN_TOP, parseInt(hours/10),  cxt);   //时钟的十位数\n            renderdigit(MARGIN_LEFT+15*(RADIUS+1), MARGIN_TOP, parseInt(hours%10),  cxt);   //时钟的个位数\n            renderdigit(MARGIN_LEFT+30*(RADIUS+1), MARGIN_TOP, 10,                  cxt);   //冒号\":\"的粒子化\n            renderdigit(MARGIN_LEFT+39*(RADIUS+1), MARGIN_TOP, parseInt(minutes/10),cxt);   //分钟十位数粒子化\n            renderdigit(MARGIN_LEFT+54*(RADIUS+1), MARGIN_TOP, parseInt(minutes%10),cxt);   //分钟个位数粒子化\n            renderdigit(MARGIN_LEFT+69*(RADIUS+1), MARGIN_TOP, 10,                  cxt);   //冒号粒子化\n            renderdigit(MARGIN_LEFT+78*(RADIUS+1), MARGIN_TOP, parseInt(seconds/10),cxt);   //秒针十位数粒子化\n            renderdigit(MARGIN_LEFT+93*(RADIUS+1), MARGIN_TOP, parseInt(seconds%10),cxt);   //秒针个位数粒子化\n            \n            //绘制balls容器中的每一个小球。\n            for(let i=0;i<balls.length;i++){\n                cxt.fillStyle = balls[i].color;\n                cxt.beginPath();\n                cxt.arc(balls[i].x, balls[i].y, RADIUS, 0, 2*Math.PI);\n                cxt.closePath();\n                cxt.fill();\n            }\n        }\n\n        //将每一位数字进行粒子化\n        function renderdigit(x,y,num,cxt){\n            cxt.fillStyle=\"rgb(0,102,153)\";\n            for(let i=0;i<digit[num].length;i++){\n                for(let j=0;j<digit[num][i].length;j++){\n                    if(digit[num][i][j]===1){\n                        cxt.beginPath();\n                        cxt.arc(x+(RADIUS+1)+j*2*(RADIUS+1),y+(RADIUS+1)+i*2*(RADIUS+1),RADIUS,0,2*Math.PI);\n                        cxt.closePath();\n\n                        cxt.fill();\n                    }\n                }\n            }\n        }\n// random() 方法可返回介于 0 ~ 1 之间的一个随机数。\n//Math.pow(base, exponent) 函数返回基数（base）的指数（exponent）次幂。\n\n// 屏幕自适应的处理\n// WINDOW_HEIGHT = document.body.clientHeight;\n// WINDOW_WIDTH  = document.body.clientWidth;\n// MARGIN_LEFT   = Math.round(WINDOW_WIDTH/10);  //除去时钟显示部分的五分之四，剩下的五分之一再左右进行两等分。\n// RADIUS        = Math.round(WINDOW_WIDTH*(4/5)/108)-1;//时钟的显示部分占据整个网页可视区域的（4/5），时钟的第一个数字是从x=0开始的，根据每一个数字的宽度进行计算得到是108，\n// MARGIN_TOP    = Math.round(WINDOW_HEIGHT/5);\n\n// Date.setTime(millisec),setTime() 方法以毫秒设置 Date 对象。\n// 要设置的日期和时间据 GMT 时间 1970 年 1 月 1 日午夜之间的毫秒数\n// \n// window.onresize = function(){\n// onresize 事件会在窗口或框架被调整大小时发生。\n// location.reload() 方法用于重新加载当前文档。"
  },
  {
    "path": "31 - Canvas CountClock/digit.js",
    "content": "// 粒子圆的数字采用的是类似点阵屏的显示方式，\n// 数字采用的是10*7的点阵屏(本质是二维数组),符号“：”采用的是10*4的点阵屏，\n// 整体上是三维数组，digit[0]是数字0的点阵，digit[1]是数字1的点阵，以此类推进行遍历可以取得对应数字的点阵\n\ndigit =\n    [\n        [\n            [0,0,1,1,1,0,0],\n            [0,1,1,0,1,1,0],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [0,1,1,0,1,1,0],\n            [0,0,1,1,1,0,0]\n        ],//0\n        [\n            [0,0,0,1,1,0,0],\n            [0,1,1,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [1,1,1,1,1,1,1]\n        ],//1\n        [\n            [0,1,1,1,1,1,0],\n            [1,1,0,0,0,1,1],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,1,1,0],\n            [0,0,0,1,1,0,0],\n            [0,0,1,1,0,0,0],\n            [0,1,1,0,0,0,0],\n            [1,1,0,0,0,0,0],\n            [1,1,0,0,0,1,1],\n            [1,1,1,1,1,1,1]\n        ],//2\n        [\n            [1,1,1,1,1,1,1],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,1,1,0],\n            [0,0,0,1,1,0,0],\n            [0,0,1,1,1,0,0],\n            [0,0,0,0,1,1,0],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [0,1,1,1,1,1,0]\n        ],//3\n        [\n            [0,0,0,0,1,1,0],\n            [0,0,0,1,1,1,0],\n            [0,0,1,1,1,1,0],\n            [0,1,1,0,1,1,0],\n            [1,1,0,0,1,1,0],\n            [1,1,1,1,1,1,1],\n            [0,0,0,0,1,1,0],\n            [0,0,0,0,1,1,0],\n            [0,0,0,0,1,1,0],\n            [0,0,0,1,1,1,1]\n        ],//4\n        [\n            [1,1,1,1,1,1,1],\n            [1,1,0,0,0,0,0],\n            [1,1,0,0,0,0,0],\n            [1,1,1,1,1,1,0],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [0,1,1,1,1,1,0]\n        ],//5\n        [\n            [0,0,0,0,1,1,0],\n            [0,0,1,1,0,0,0],\n            [0,1,1,0,0,0,0],\n            [1,1,0,0,0,0,0],\n            [1,1,0,1,1,1,0],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [0,1,1,1,1,1,0]\n        ],//6\n        [\n            [1,1,1,1,1,1,1],\n            [1,1,0,0,0,1,1],\n            [0,0,0,0,1,1,0],\n            [0,0,0,0,1,1,0],\n            [0,0,0,1,1,0,0],\n            [0,0,0,1,1,0,0],\n            [0,0,1,1,0,0,0],\n            [0,0,1,1,0,0,0],\n            [0,0,1,1,0,0,0],\n            [0,0,1,1,0,0,0]\n        ],//7\n        [\n            [0,1,1,1,1,1,0],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [0,1,1,1,1,1,0],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [0,1,1,1,1,1,0]\n        ],//8\n        [\n            [0,1,1,1,1,1,0],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [1,1,0,0,0,1,1],\n            [0,1,1,1,0,1,1],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,0,1,1],\n            [0,0,0,0,1,1,0],\n            [0,0,0,1,1,0,0],\n            [0,1,1,0,0,0,0]\n        ],//9\n        [\n            [0,0,0,0],\n            [0,0,0,0],\n            [0,1,1,0],\n            [0,1,1,0],\n            [0,0,0,0],\n            [0,0,0,0],\n            [0,1,1,0],\n            [0,1,1,0],\n            [0,0,0,0],\n            [0,0,0,0]\n        ]//:\n    ];"
  },
  {
    "path": "31 - Canvas CountClock/readme.md",
    "content": "### 效果展示\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/31%20-%20Canvas%20CountClock/image/GIF.gif)\n### 代码部分\n由页面加载函数和6个自定义的功能函数完成。\n- 页面加载函数\n```JavaScript\n        window.onload=function(){\n            console.log(\"ddd\");\n            WINDOW_HEIGHT = document.body.clientHeight;\n            WINDOW_WIDTH  = document.body.clientWidth;\n            MARGIN_LEFT   = Math.round(WINDOW_WIDTH/10);  //除去时钟显示部分的五分之四，剩下的五分之一再左右进行两等分。\n            RADIUS        = Math.round(WINDOW_WIDTH*(4/5)/108)-1;//时钟的显示部分占据整个网页可视区域的（4/5），时钟的第一个数字是从x=0开始的，\t\t\t\t\t\t\t\t\t根据每一个数字的宽度进行计算得到是108，\n            MARGIN_TOP    = Math.round(WINDOW_HEIGHT/5);\n\n            let canvas   = document.querySelector(\"#canvas\");\n            let context  = canvas.getContext(\"2d\");\n            canvas.width = WINDOW_WIDTH;\n            canvas.height= WINDOW_HEIGHT;\n\n            curShowTimeSeconds = getCurrentShowTimeSeconds();\n            setInterval(()=>{\n                render(context);\n                update();\n                console.log(balls.length);\n            },50);\n        }\n```\n- 通过这个函数得到当前距离截止时间显示的毫秒数。\n```javascript\nfunction getCurrentShowTimeSeconds(){\n    var curTime = new Date();\n    var ret = endTime.getTime()-curTime.getTime();  //截止时间减去当前时间得到的毫秒数。\n    ret = Math.round(ret/1000);//将毫秒数转换为秒数。\n    return ret >= 0 ? ret : 0;  \n}\n```\n- 更新函数：1.更新时间；2.更新动画，3.生成小球\n```javascript\n  function update(){\n            //得到下一次距离截止时间的秒数。\n            let nextShowTimeSeconds = getCurrentShowTimeSeconds();//注意getCurrentShowTimeSeconds这里掉了括号“（）”也就是说没有执行函数。\n            let nextHours = parseInt( nextShowTimeSeconds / 3600);\n            let nextMinutes = parseInt((nextShowTimeSeconds-nextHours*3600)/60);\n            let nextSeconds = parseInt(nextShowTimeSeconds%60);\n\n            let curHours   = parseInt(curShowTimeSeconds/3600);  \n            let curMinutes = parseInt((curShowTimeSeconds-curHours*3600)/60); \n            let curSeconds = parseInt(curShowTimeSeconds%60); \n            //如果时间发生改变，将改变后的时间赋予给当前时间；根据改变的是哪个数字，来给该数字添加小球。\n            if(nextSeconds!=curSeconds){\n                curShowTimeSeconds=nextShowTimeSeconds;\n                //时针的十位数改变时，在相应数字的位置处添加小球。\n                if(parseInt(curHours/10)!=parseInt(nextHours/10)){\n                    addBalls(MARGIN_LEFT, MARGIN_TOP, parseInt(curHours/10));\n                }\n                if(parseInt(curHours%10)!=parseInt(nextHours%10)){\n                    addBalls(MARGIN_LEFT+15*(RADIUS+1), MARGIN_TOP, parseInt(curHours%10));\n                }\n\n                if(parseInt(curMinutes/10)!=parseInt(nextMinutes/10)){\n                    addBalls(MARGIN_LEFT+39*(RADIUS+1), MARGIN_TOP, parseInt(curMinutes/10));\n                }\n                if(parseInt(curMinutes%10)!=parseInt(nextMinutes%10)){\n                    addBalls(MARGIN_LEFT+54*(RADIUS+1), MARGIN_TOP, parseInt(curMinutes%10));\n                }\n\n                if(parseInt(curSeconds/10)!=parseInt(nextSeconds/10)){\n                    addBalls(MARGIN_LEFT+78*(RADIUS+1), MARGIN_TOP, parseInt(curSeconds/10));\n                }\n                if(parseInt(curSeconds%10)!=parseInt(nextSeconds%10)){\n                    addBalls(MARGIN_LEFT+93*(RADIUS+1), MARGIN_TOP, parseInt(curSeconds%10));\n                }\n             \n            }\n            updateBalls();//更新每个小球的运动状态。\n        }\n```\n- //小球运动状态的更新设置。\n```javascript\n        //小球运动状态的更新设置。\n        function updateBalls(){\n            for(let i=0;i<balls.length;i++){\n                balls[i].x += balls[i].vx;\n                balls[i].y += balls[i].vy;\n                balls[i].vy += balls[i].g;\n\n                // 碰撞检测\n                if(balls[i].y  >= WINDOW_HEIGHT-RADIUS){\n                   balls[i].y  =  WINDOW_HEIGHT-RADIUS;//小球落到底部的位置 \n                   balls[i].vy = -balls[i].vy*0.75;  //速度反向，进行反弹,0.5表示摩擦系数，每次反弹速度都减半。\n                }\n            }\n            // 将滚出画布的小球进行删除处理。\n            balls=balls.filter((item)=>{\n                return item.x+RADIUS>0 && item.x - RADIUS < WINDOW_WIDTH;\n            });\n\n            // 将滚出画布的小球进行删除处理。\n            // let cnt=0;\n            // for(let i=0;i<balls.length;i++){\n            //     if(balls[i].x+RADIUS>0&&balls[i].x - RADIUS < WINDOW_WIDTH){ //小球的右边缘只要大于0，左边缘小于画布宽度就就符合，可以用左右临近或出去一半画布的小球进行分析。\n            //         balls[cnt++]=balls[i];//遍历整个balls数组，将符合要求的小球放在以cnt计数的数组中\n            //     }\n            // }\n            // //经过上面的上面的这一组循环之后，可以知道cnt之前包括cnt的数据项都是符合的小球，后面的便是不符的，进行一项一项删除。\n            // while(balls.length>cnt){\n            //     balls.pop();\n            // }\n\n        }\n```\n-  //遍历循环每一个数的点阵屏，在点阵屏上数字为“1”处生成小球函数，将生成的小球放到数组ball中。\n```javascript\n   function addBalls(x,y,num){\n            for(let i=0;i<digit[num].length;i++){\n                for(let j=0;j<digit[num][i].length;j++){\n                    if(digit[num][i][j]===1){\n                        let aball={\n                            x     : x+(RADIUS+1)+j*2*(RADIUS+1),//定义生成小球球心x点的坐标\n                            y     : y+(RADIUS+1)+i*2*(RADIUS+1),//定义生成小球球心y点的坐标\n                            vx    : Math.pow(-1,Math.ceil(Math.random()*10))*6,//用0-1之间的随机数乘以10,然后向上取整得到奇数或偶数，在由-1的奇偶次幂得到正负4.\n                            vy    : -6,\n                            g     : 1.5+Math.random(),\n                            color : colors[Math.floor(Math.random()*colors.length)]\n                        };\n                        balls.push(aball);//每产生一个小球就放到全局容器balls中。\n                    }\n                }\n            }\n        }\n```\n- 将上下文环境进行渲染\n```javascript\n function render(cxt){\n            cxt.clearRect(0,0,WINDOW_WIDTH,WINDOW_HEIGHT);  //绘图之前进行刷新操作\n            //会根据curShowTimeSeconds计算出时分秒进行更新数字。\n            let hours   = parseInt(curShowTimeSeconds/3600);  //求有多少个小时\n            let minutes = parseInt((curShowTimeSeconds-hours*3600)/60); //求有多少分钟\n            let seconds = parseInt(curShowTimeSeconds%60);  //所有的秒数对分钟求余得到秒数\n\n            renderdigit(MARGIN_LEFT,               MARGIN_TOP, parseInt(hours/10),  cxt);   //时钟的十位数\n            renderdigit(MARGIN_LEFT+15*(RADIUS+1), MARGIN_TOP, parseInt(hours%10),  cxt);   //时钟的个位数\n            renderdigit(MARGIN_LEFT+30*(RADIUS+1), MARGIN_TOP, 10,                  cxt);   //冒号\":\"的粒子化\n            renderdigit(MARGIN_LEFT+39*(RADIUS+1), MARGIN_TOP, parseInt(minutes/10),cxt);   //分钟十位数粒子化\n            renderdigit(MARGIN_LEFT+54*(RADIUS+1), MARGIN_TOP, parseInt(minutes%10),cxt);   //分钟个位数粒子化\n            renderdigit(MARGIN_LEFT+69*(RADIUS+1), MARGIN_TOP, 10,                  cxt);   //冒号粒子化\n            renderdigit(MARGIN_LEFT+78*(RADIUS+1), MARGIN_TOP, parseInt(seconds/10),cxt);   //秒针十位数粒子化\n            renderdigit(MARGIN_LEFT+93*(RADIUS+1), MARGIN_TOP, parseInt(seconds%10),cxt);   //秒针个位数粒子化\n            \n            //绘制balls容器中的每一个小球。\n            for(let i=0;i<balls.length;i++){\n                cxt.fillStyle = balls[i].color;\n                cxt.beginPath();\n                cxt.arc(balls[i].x, balls[i].y, RADIUS, 0, 2*Math.PI);\n                cxt.closePath();\n                cxt.fill();\n            }\n        }\n```\n> 由下面的数字点阵图可以知道，每一个点阵屏都是十行7列，也就是说每一个数字显示所占据的宽度是7*2*(RADIUS+1)=14*(RADIUS+1),为了让数字之间留有间隙，不让数字紧紧挨着，所以每一块点阵屏（显示一个数字）的宽度设置为15*(RADIUS+1)，冒号的宽度是4*2*(RADIUS+1)=8*(RADIUS+1)，同样为了布局美观采用9*(RADIUS+1)。  \n- 数字点阵的图示  \n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/31%20-%20Canvas%20CountClock/image/digit.PNG)  \n\t\n> `cxt.arc(x+(RADIUS+1)+j*2*(RADIUS+1),y+(RADIUS+1)+i*2*(RADIUS+1),RADIUS,0,2*Math.PI); `参数里面是每一个小圆点的圆心的坐标位置，从坐标x,y开始，每一个小方格的距离是2*(RADIUS+1)。\n\n\n- 将每一位数字进行粒子化\n```javascript\n        function renderdigit(x,y,num,cxt){\n            cxt.fillStyle=\"rgb(0,102,153)\";\n            for(let i=0;i<digit[num].length;i++){\n                for(let j=0;j<digit[num][i].length;j++){\n                    if(digit[num][i][j]===1){\n                        cxt.beginPath();\n                        cxt.arc(x+(RADIUS+1)+j*2*(RADIUS+1),y+(RADIUS+1)+i*2*(RADIUS+1),RADIUS,0,2*Math.PI);\n                        cxt.closePath();\n\n                        cxt.fill();\n                    }\n                }\n            }\n        }\n```\n\n```JavaScript\n//将每一位数字进行粒子化\nfunction renderdigit(x,y,num,cxt){\n    cxt.fillStyle=\"rgb(0,102,153)\";\n    for(let i=0;i<digit[num].length;i++){\n\tfor(let j=0;j<digit[num][i].length;j++){\n\t    if(digit[num][i][j]===1){\n\t\tcxt.beginPath();\n\t\tcxt.arc(x+(RADIUS+1)+j*2*(RADIUS+1),y+(RADIUS+1)+i*2*(RADIUS+1),RADIUS,0,2*Math.PI);\n\t\tcxt.closePath();\n\n\t\tcxt.fill();\n\t    }\n\t}\n    }\n}\n```\n> 代码分析：renderdigit(x,y,num,cxt)：参数x,y表示当前这位数字的位置， num是要进行粒子化的数字，cxt是画布上下文。  \n这里的digit[num]是点阵中具体哪一块，刚好点阵我们设置规则是：点阵的0项块对应数字“0”以此类推，可以用要渲染哪一个数字来到点阵中取得相应的那一项。  \n\n### 补充知识点\n- random() 方法可返回介于 0 ~ 1 之间的一个随机数。  \n- Math.pow(base, exponent) 函数返回基数（base）的指数（exponent）次幂。  \n\n- 屏幕自适应的处理  \nWINDOW_HEIGHT = document.body.clientHeight;  \nWINDOW_WIDTH  = document.body.clientWidth;  \nMARGIN_LEFT   = Math.round(WINDOW_WIDTH/10);  //除去时钟显示部分的五分之四，剩下的五分之一再左右进行两等分。  \nRADIUS        = Math.round(WINDOW_WIDTH*(4/5)/108)-1;//时钟的显示部分占据整个网页可视区域的（4/5），时钟的第一个数字是从x=0开始的，根据每一个数字的宽度进行计算得到是108，  \nMARGIN_TOP    = Math.round(WINDOW_HEIGHT/5);  \n\n- Date.setTime(millisec),setTime() 方法以毫秒设置 Date 对象。  \n要设置的日期和时间据 GMT 时间 1970 年 1 月 1 日午夜之间的毫秒数  \n\n- window.onresize = function(){  }\n- onresize 事件会在窗口或框架被调整大小时发生。  \n- location.reload() 方法用于重新加载当前文档。  \n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2018 SUNNERCMS\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "[纯CSS/CSS3制作的动画小demo](https://github.com/SUNNERCMS/CSS-CSS3-Animation-effects)  \n# JavaScript 30天每日效果图  \n\n在Github上看到了[wesbos](https://twitter.com/wesbos)的一个Javascript30天挑战的[repo](https://github.com/wesbos/JavaScript30)，旨在使用纯JS来进行练习，不允许使用任何其他的库和框架，该挑战共30天，我会在这里复现这30天遇到的挑战与问题，将其教程化、视频化分享给大家。实例在我学习过程中有所补充，具体实例多于30个。\n\n[在线查看JS30所有项目效果图](http://30daysofjs.michaeleinsohn.com/)\n\n## Day01 - JavaScript Drum Kit\n![](http://oslwafb71.bkt.clouddn.com/small0.jpg)\n\n## Day02 - JS and CSS Clock\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/02%20-%20JS%20and%20CSS%20Clock/image/GIF.gif)  \n\n## Day03 - CSS Variables\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/03%20-%20CSS%20Variables/GIF.gif)\n\n## Day04 - Array Cardio Day 1\n![](http://oslwafb71.bkt.clouddn.com/small3.jpg)\n\n## Day05 - Flex Panel Gallery\n![](http://oslwafb71.bkt.clouddn.com/small4.jpg)\n\n## Day06 - Quick match\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/06%20-%20Fetch%E3%80%81filter%E3%80%81%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E5%AE%9E%E7%8E%B0%E5%BF%AB%E9%80%9F%E5%8F%A4%E8%AF%97%E5%8C%B9%E9%85%8D/DemoGIF.gif)\n\n## Day07 - Array Cardio Day 2\n![](http://oslwafb71.bkt.clouddn.com/small6.jpg)\n\n## Day08 - Fun with HTML5 Canvas\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/08%20-%20HTML5%20Canvas%20%E5%AE%9E%E7%8E%B0%E5%BD%A9%E8%99%B9%E7%94%BB%E7%AC%94%E7%BB%98%E7%94%BB%E6%9D%BF/GIF.gif)\n\n## Day09 - Dev Tools Domination\n![](http://oslwafb71.bkt.clouddn.com/small8.jpg)\n\n## Day10 - Hold Shift and Check Checkboxes\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/10%20-%20JS%20%E5%AE%9E%E7%8E%B0%20Checkbox%20%E4%B8%AD%E6%8C%89%E4%BD%8F%20Shift%20%E7%9A%84%E5%A4%9A%E9%80%89%E5%8A%9F%E8%83%BD/GIF.gif)\n\n## Day11 - Custom Video Player\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/11%20-%20%E8%87%AA%E5%AE%9A%E4%B9%89%E8%A7%86%E9%A2%91%E6%92%AD%E6%94%BE%E5%99%A8/GIF.gif)\n\n## Day12 - Key Sequence Detection\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/12%20-%20%E9%94%AE%E7%9B%98%E8%BE%93%E5%85%A5%E5%BA%8F%E5%88%97%E7%9A%84%E9%AA%8C%E8%AF%81%E6%8C%87%E5%8D%97/GIF.gif)  \n\n## Day13 - Slide in on Scroll\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/13%20-%20%E5%9B%BE%E7%89%87%E9%9A%8F%E5%B1%8F%E5%B9%95%E6%BB%9A%E5%8A%A8%E8%80%8C%E6%BB%91%E5%85%A5%E6%BB%91%E5%87%BA%E7%9A%84%E6%95%88%E6%9E%9C/demoshow/GIF.gif)\n\n## Day14 - JavaScript References VS Copying\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/14%20-%20JavaScript%20%E5%BC%95%E7%94%A8%E5%92%8C%E5%80%BC%E6%8B%B7%E8%B4%9D/showpicture.PNG)\n\n## Day15 - LocalStorage\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/15%20-%20LocalStorage/GIF.gif)\n\n## Day16 - Mouse Move Shadow\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/16%20-%20%E7%A7%BB%E5%8A%A8%E9%BC%A0%E6%A0%87%E8%AE%A9%E5%AD%97%E4%BD%93%E5%91%88%E7%8E%B0%E5%BD%A9%E8%99%B9%E6%95%88%E6%9E%9C/show.PNG)\n\n## Day17 - Sort Without Articles\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/17%20-%20%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F/show.PNG)\n\n## Day18 - Adding Up Times with Reduce\n\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/18%20-%20Day18%20-%20Reduce%E3%80%81Map%E6%B7%B7%E5%90%88%E4%BD%BF%E7%94%A8%E8%AE%A1%E7%AE%97%E6%97%B6%E5%88%86%E7%A7%92/show.PNG)\n\n## Day19 - Webcam Fun\n![](http://oslwafb71.bkt.clouddn.com/small18.jpg)\n\n## Day20 - Speech Detection\n![](http://oslwafb71.bkt.clouddn.com/small19.jpg)\n\n## Day21 - Geolocation\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/21%20-%20Geolocation/show.PNG)\n\n## Day22 - Follow Along Link Highlighter\n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/22%20-%20Follow%20Along%20Link%20Highlighter/SHOWGIF.gif)\n\n## Day23 - Speech Synthesis\n![](http://oslwafb71.bkt.clouddn.com/small22.jpg)\n\n## Day24 - Sticky Nav\n![](http://oslwafb71.bkt.clouddn.com/small23.jpg)\n\n## Day25 - Event Capture, Propagation, Bubbling and Once\n![](http://oslwafb71.bkt.clouddn.com/small24.jpg)\n\n## Day26 - Stripe Follow Along Nav\n![](http://oslwafb71.bkt.clouddn.com/small25.jpg)\n\n## Day27 - Click and Drag\n![](http://oslwafb71.bkt.clouddn.com/small26.jpg)\n\n## Day28 - Video Speed Controller\n![](http://oslwafb71.bkt.clouddn.com/small27.jpg)\n\n## Day29 - Countdown Timer\n![](http://oslwafb71.bkt.clouddn.com/small28.jpg)\n\n## Day30 - Whack A Mole\n![](http://oslwafb71.bkt.clouddn.com/small29.jpg)\n\n## Day31 - Canvas CountdownClock  \n![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/31%20-%20Canvas%20CountClock/image/GIF.gif)\n\n本人博客：https://sunzhaoxiang.com   \n欢迎关注微信公众号：一线开发\n"
  }
]