Repository: SUNNERCMS/30daysJavascript Branch: master Commit: 445ddc452c5c Files: 112 Total size: 344.0 KB Directory structure: gitextract_hmq1lhif/ ├── .gitattributes ├── 01 - JavaScript Drum Kit/ │ ├── README.md │ ├── index-FINISHED.html │ ├── index-START.html │ └── style.css ├── 02 - JS and CSS Clock/ │ ├── README.md │ ├── click.css │ ├── clock.js │ ├── clock1.js │ ├── clock2.js │ ├── index.html │ └── style.css ├── 03 - CSS Variables/ │ ├── README.md │ ├── index.html │ ├── style.css │ └── variables.js ├── 04 - Array Cardio Day 1/ │ ├── README.md │ ├── index-FINISHED.html │ └── index-START.html ├── 05 - Flex Panel Gallery/ │ ├── README.md │ ├── animation.html │ ├── index-FINISHED.html │ └── index-START.html ├── 06 - Fetch、filter、正则表达式实现快速古诗匹配/ │ ├── .vscode/ │ │ └── launch.json │ ├── README.md │ └── index.html ├── 07 - Array Cardio Day 2/ │ ├── README.md │ └── index.html ├── 08 - HTML5 Canvas 实现彩虹画笔绘画板/ │ ├── README.md │ └── index.html ├── 09 - Console 调试各种姿势指南/ │ ├── README.md │ └── index-FINISHED.html ├── 10 - JS 实现 Checkbox 中按住 Shift 的多选功能/ │ ├── README.md │ ├── index-FINISHED.html │ ├── index-START.html │ └── sunzhaoxiang.html ├── 11 - 自定义视频播放器/ │ ├── README.md │ ├── index.html │ └── style.css ├── 12 - 键盘输入序列的验证指南/ │ ├── README.md │ └── index.html ├── 13 - 图片随屏幕滚动而滑入滑出的效果/ │ ├── README.md │ ├── index.html │ ├── index.js │ └── style.css ├── 14 - JavaScript 引用和值拷贝/ │ ├── README.md │ ├── index-FINISHED.html │ └── index-START.html ├── 15 - LocalStorage/ │ ├── README.md │ ├── demo.html │ ├── index.html │ └── style.css ├── 16 - 移动鼠标让字体呈现彩虹效果/ │ ├── README.md │ └── index.html ├── 17 - 数组排序/ │ ├── README.md │ └── index.html ├── 18 - Day18 - Reduce、Map混合使用计算时分秒/ │ ├── README.md │ └── index.html ├── 19 - Webcam Fun/ │ ├── README.md │ ├── index.html │ ├── package.json │ ├── scripts.js │ └── style.css ├── 20 - Speech Detection/ │ ├── README.md │ ├── index-FINISHED.html │ ├── index-START.html │ └── package.json ├── 21 - Geolocation/ │ ├── README.md │ ├── index.html │ └── package.json ├── 22 - Follow Along Link Highlighter/ │ ├── README.md │ ├── index.html │ └── style.css ├── 23 - Speech Synthesis/ │ ├── 23 - Speech Synthesis/ │ │ ├── README.md │ │ ├── index.html │ │ └── style.css │ └── speak-easy-synthesis/ │ ├── index.html │ ├── manifest.webapp │ ├── script.js │ └── style.css ├── 24 - Sticky Nav/ │ ├── README.md │ ├── index.html │ └── style.css ├── 25 - Event Capture, Propagation, Bubbling and Once/ │ ├── README.md │ └── index.html ├── 26 - Stripe Follow Along Nav/ │ ├── README.md │ └── index.html ├── 27 - Click and Drag/ │ ├── README.md │ ├── index-FINISHED.html │ ├── index-START.html │ └── style.css ├── 28 - Video Speed Controller/ │ ├── .vscode/ │ │ └── settings.json │ ├── README.md │ ├── index-FINISHED.html │ ├── index-START.html │ └── style.css ├── 29 - Countdown Timer/ │ ├── .vscode/ │ │ └── settings.json │ ├── README.md │ ├── index.html │ ├── scripts-FINISHED.js │ ├── scripts-START.js │ └── style.css ├── 30 - Whack A Mole/ │ ├── README.md │ ├── index-FINISHED.html │ ├── index-START.html │ └── style.css ├── 31 - Canvas CountClock/ │ ├── Countdown.html │ ├── Countdown.js │ ├── digit.js │ └── readme.md ├── LICENSE └── README.md ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitattributes ================================================ *.css linguist-language=javascript *.html linguist-language=javascript *.java linguist-language=javascript ================================================ FILE: 01 - JavaScript Drum Kit/README.md ================================================ # Day1 JavaScript Drum Kit 中文指南 ## 简介 第一天的练习是用JS制作一个爵士鼓的页面,通过敲击键盘上不同的字母,会发出不同的声音,并且页面上会伴随着敲击的动画。 效果如下: ![](http://om1c35wrq.bkt.clouddn.com/01%20-%20JavaScript%20Drum%20Kit%202.gif) 想要实现以上效果,大致思路和解决方案如下: - 检测到键盘上什么键被按下--监听`keydown`事件 - 在按键被按下的时候,播放音效--`audio.play()` - 在按键被按下的同时,播放动画--`Element.classList.add('className')` - 在动画结束后,移除动画,不然之后再点击不会有任何效果--`Element.classList.remove('className')` ## 基础语法 ### 一些 ES6 语法 1. ``const`` :声明一个只读的常量,标识符的值只能赋值一次。 2. \`字符串 \${ 变量、属性名 } \`:模板字面量(Template literals)中用于表示模板字符串的标识。特点是字符串首尾用反引号(\`),内部的模板部分用 ${ } 括起来表示,具体请看[MDN文档]( https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/template_strings)。简单例子如下: ````javascript var a = 1; var b = 2; //不用模板的写法 console.log("三是" + (a + b) + "不是" + (2 * a + b)); //"三是3不是4" //使用模板字符串的写法 console.log(`三是${a + b}不是${2 * a + b}`); //"三是3不是4" ```` ### ``forEach`` 与箭头函数 使用 ``document.querySelector`` 获取一组符合 CSS 选择符的元素快照,类型为 NodeList(此对象是对于文档的实时运行的动态查询),对其进行遍历时可采用 ``forEach`` 方法。 ```javascript // Code from http://es6-features.org/#StatementBodies // ES6 nums.forEach(v => { if (v % 5 === 0) fives.push(v); }) // ES5 nums.forEach(function (v) { if (v % 5 === 0) five.push(v); }) ``` ## 页面基础布局 ```html JS Drum Kit
A clap
S hihat
D kick
F openhat
G boom
H ride
J snare
K tom
L tink
``` - 标签定义键盘文本 说到技术概念上的特殊样式时,就要提到 标签。正如你已经猜到的,它用来表示文本是从键盘上键入的。 浏览器通常用等宽字体来显示该标签中包含的文本。 标签经常用在于计算机相关的文档和手册中。例如: ```text 键入 quit 来退出程序,或者键入 menu 来返回主菜单。 ``` - 使用 data-* 属性来嵌入自定义数据 页面里通过data-key将页面展示的内容和audio关联起来。使用方法如下介绍: ```html
  • Owl
  • Salmon
  • Tarantula
``` ① data-* 属性用于存储页面或应用程序的私有自定义数据。 ② data-* 属性赋予我们在所有 HTML 元素上嵌入自定义 data 属性的能力。 ③ 属性名不应该包含任何大写字母,并且在前缀 "data-" 之后必须有至少一个字符 ④ 属性值可以是任意字符串 **语法:** ```text ``` **属性值:** |值|描述| |:---------:|:---------:| |somevalue|规定属性的值(以字符串)。| - 按键键码对应表https://www.cnblogs.com/yiven/p/7118056.html ## 主要CSS代码 ```css html { font-size: 10px; background: url(http://i.imgur.com/b9r5sEL.jpg) bottom center; background-size: cover; } body, html { margin: 0; padding: 0; font-family: sans-serif; } .keys { display: flex; flex: 1; min-height: 100vh; align-items: center; justify-content: center; } .key { border: .4rem solid black; border-radius: .5rem; margin: 1rem; font-size: 1.5rem; padding: 1rem .5rem; transition: all .07s ease; width: 10rem; text-align: center; color: white; background: rgba(0, 0, 0, 0.4); text-shadow: 0 0 .5rem black; } .playing { transform: scale(1.1); border-color: #ffc600; box-shadow: 0 0 1rem #ffc600; } kbd { display: block; font-size: 4rem; } .sound { font-size: 1.2rem; text-transform: uppercase; letter-spacing: .1rem; color: #ffc600; } ``` 主要属性有以下几个: - `background-size:cover;`属性规定背景图像的尺寸。把背景图像扩展至足够大,以使背景图像完全覆盖背景区域。背景图像的某些部分也许无法显示在背景定位区域中。 - `html`中有一个样式为`font-size: 10px;`,在本案例中,`1rem`就是10px,rem是以html中的`font-size`为参照物,`1.2rem`就是`12px`。 - `transform: scale(1.1);`--该属性在键盘被点击时将该元素缩放至原来的1.1倍。 - `.key{border: .4rem solid black;} .playing{border-color: #ffc600;}`--这两条属性在按键点击的时候改变边框颜色。 - `.key{text-shadow: 0 0 .5rem black;} .playing{box-shadow: 0 0 1rem #ffc600;}`--这两条属性在按键点击的时候改变阴影的效果 - `transition: all .07s ease;`--定义以上动画在0.07秒内完成。 我们注意到我们定义了`.palying`类,在按键按下的时侯为该元素添加`playing`类,在结束后移除`playing`类。 ## JS代码 ### 按键监听&音效播放&添加动画 ```js function playSound(e) { const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`); const key = document.querySelector(`div[data-key="${e.keyCode}"]`); if (!audio) return; key.classList.add('playing'); audio.currentTime = 0; audio.play(); } /** * 监听页面的keydown事件,触发playAudio函数。 */ window.addEventListener('keydown', playSound); ``` * 监听页面的keydown事件,触发playAudio函数。 * 通过KeyCode检测我们按下的键盘按钮是哪个按钮。 * A -> 65 * B -> 66 * C -> 67 * D -> 68 * E -> 69 * F -> 70 * G -> 71 * H -> 72 * I -> 73 * J -> 74 * K -> 75 * L -> 76 * M -> 77 * N -> 78 * O -> 79 * P -> 80 * Q -> 81 * R -> 82 * S -> 83 * T -> 84 * U -> 85 * V -> 86 * W -> 87 * X -> 88 * Y -> 89 * Z -> 90 * 在这里我们用到了ES6的模板字符串,`${e.keyCode}`,可以动态的将按键的`Keycode`传过去,以使`audio`动态的获取每一个按键绑定的`audio`。需要注意的是模板字符串一定要使用"`"(Esc下面那个键)包裹,而不是双引号。 * 我们注意到`audio.play();`前面一行是`audio.currentTime = 0;`,这是因为,如果没有在播放音效前将该音乐重置,会发生以下情况,当我连续点击某一按键的时候,只有第一次点击会响,第二次第三次连续的点击可能没声音。所以在每一次点击之前重置音效是很有必要的。 * `key.classList.add('playing');`可以在按键点击的同时为该元素添加playing类,展示小动画。 * `if(!audio) return; if(!key) return;`因为并不是每一个按键都有音效,当用户点击了非绑定音效按键,及时退出函数是很好的习惯。 ### 动画结束后移除动画 ```js function removeTransition(e) { if (e.propertyName !== 'transform') return; e.target.classList.remove('playing'); } const keys = Array.from(document.querySelectorAll('.key')); keys.forEach(key => key.addEventListener('transitionend',stopTransition)); ``` - 监听每一个按键元素的`transitionend`事件,当按键元素的动画结束后会触发`removeTransition`函数。 - 首先在`removeTransition`函数中可以输出事件e的内容,会输出该动画每一步具体的变化,发现其中会有`propertyName`属性,可以通过判断`propertyName`等于其中的一个值(例如'transform'),等于该值就移除`playing`类,也即移除动画。 - 在定位元素的时候,可以使用`this`也可以使用`e.target`,可以简单这么理解,`this`值的是谁出发了这次事件,也就是`key`,就等同于事件的目标(e.target). ## 解决难点 ### 如何将键盘按键与页面按钮对应起来? 连接的帮手是 ``keydown`` 事件中的 `keyCode` 属性,`keyCode` 属性的值和 ASCII 编码值相同(对应小写字母)。在[这个网站]( http://keycode.info/ )可以用按键盘来查看对应的键码。 我们能获取到的初始页面中,按钮 `div` 和音频 `audio` 标签中都添加了一个属性 `data-key` 用于存储对应的键码,这样做的目的是,添加键盘事件监听后,触发键盘事件时即可获取事件的 `keyCode` 属性值,以此为线索,操作对应的按钮及音频。 ````javascript const audio = document.querySelector(`audio[data-key="${e.keyCode}"]`); const key = document.querySelector(`div[data-key="${e.keyCode}"]`); ```` ### 如何保证按键被按住不放时,可以马上响起连续鼓点声? 每次播放音频之前,设置播放时间戳为 0: ````javascript var audio = document.getElementById("video"); audio.currentTime = 0; audio.play(); ```` ### 如何使页面按钮恢复原状? 利用一个叫 [`transitionened`](https://developer.mozilla.org/zh-CN/docs/Web/Events/transitionend) 的事件,它在 CSS transition 结束后会被触发。我们就可以利用这个事件,在每次打鼓的效果(尺寸变大、颜色变化)完成之后,去除相应样式。 在这个页面中,发生 `transition` 的样式属性不止一个(`box-shadow`, `transform`, `border-color`),所以需要添加一个判断语句,使每发生一次按键事件时,只去除一次样式。 ````javascript funciton remove(event) { if (event.propertyName !== 'border-left-color') return; this.classList.remove('playing'); // event.target.classList.remove('playing'); } ```` ================================================ FILE: 01 - JavaScript Drum Kit/index-FINISHED.html ================================================ JS Drum Kit
A clap
S hihat
D kick
F openhat
G boom
H ride
J snare
K tom
L tink
================================================ FILE: 01 - JavaScript Drum Kit/index-START.html ================================================ JS Drum Kit
A clap
S hihat
D kick
F openhat
G boom
H ride
J snare
K tom
L tink
================================================ FILE: 01 - JavaScript Drum Kit/style.css ================================================ html { font-size: 10px; background-size: cover; background-image: url(http://i.imgur.com/b9r5sEL.jpg); } body, html { margin: 0; padding: 0; font-family: sans-serif;//非称线--字体的开始和结束之处没有特殊修饰 } .keys { display: flex; flex: 1; min-height: 100vh; align-items: center; justify-content: center; } .key { border: .4rem solid black; border-radius: .5rem; margin: 1rem; font-size: 1.5rem; padding: 1rem .5rem; transition: all .07s ease; width: 10rem; text-align: center; color: white; background: rgba(0, 0, 0, 0.4); text-shadow: 0 0 .5rem black; } .playing { transform: scale(1.1); border-color: #ffc600; box-shadow: 0 0 1rem #ffc600; } kbd { display: block; font-size: 4rem; } .sound { font-size: 1.2rem; text-transform: uppercase; letter-spacing: .1rem; color: #ffc600; } ================================================ FILE: 02 - JS and CSS Clock/README.md ================================================ # Day02 - JavaScript + CSS Clock ## 简介 第二天的练习是用JS+CSS模拟时钟效果。 效果如下: ![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/02%20-%20JS%20and%20CSS%20Clock/image/GIF.gif) 实现以上模拟时钟的效果,大致思路和解决方案如下: * 分别获取到当前时间的时、分、秒。 * 通过时分秒对一圈360度,进行映射,确定每一个指针所需旋转的角度。 * 通过CSS的`transform:rotate(deg)`,来实时的调整指针在键盘中的位置。 ### 文件说明: (1)image:用来存放背景图片 (2)click.js:最终版JS逻辑 (3)click.css:最终版样式表 ## 页面布局 ```html RealTimeClock
``` ## CSS样式 ```css /*时分秒指针初始化是垂直的,指针移动没有设置过渡效果和过渡时间, 就是根据角度来定位置*/ html{ /*font-size:625%,默认字体大小都是16px,16*62.5=100px,1rem=100px*/ font-size:625%; background: #018DED url(./image/picture4.jpg) bottom center ; background-size: cover; } html,body{ margin:0px; padding:0px; display: flex; min-height: 100vh; justify-content:center; align-items:center; } /*采用的是标准盒模型,即是纯宽高*/ .clock{ position:relative; width:3rem; height:3rem; border:0.2rem solid white; margin:0.5rem auto; padding:0.2rem; background: rgba(0,0,0,0.4); border-radius:50%; box-shadow:0 0 2px 4px rgba(0,0,0,0.1), 0 0 10px 3px rgba(0,0,0,0.2), 0 0 1px 2px #EFEFEF inset, 0 0 30px black inset; } .clock-face{ position:relative; width:100%; /*这里的100%是300px,是clock的宽*/ height:100%; } /*时钟表表盘中心圆点*/ .clock-face::after{ content:''; display: block; width:.1rem; height:.1rem; background-color: #a8c5d1; position: absolute; left:50%; top:50%; transform:translate(-50%,-50%); border-radius:50%; } /*指针通用样式,在sass中可以封装成一个mixin*/ .hand{ background: #fff; position:absolute; bottom:50%; left:50%; /*transform:translateX(-50%); 虽说这样可以使指针居中线,但是translate的平移是相对于自身center位置的,那么这样居中处理后,下面的旋转仍旧按的是平移之前的right位置为原点,虽说三个指针通过translateY看似处于一条中线上,实际旋转时仍然是按照各自的right位置进行旋转*/ transform:rotate(0deg); transform-origin:50% 100%; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 8px rgba(0, 0, 0, 0.4), 2px 5px 1px rgba(0, 0, 0, 0.5); } /*时针样式*/ .hour-hand{ height:40%; width:0.1rem; margin-left:-0.05rem; /*使时针向左移动自身的一半来居中*/ border-bottom-left-radius: .05rem; border-top-left-radius:.05rem; } .min-hand { height: 45%; width: .05rem; margin-left:-0.025rem; } .second-hand { height: 50%; width: .02rem; margin-left:-0.01rem; border-bottom-left-radius: .02rem; border-top-left-radius: .02rem; background-color: red; } /*日期,时间,星期几的样式*/ .dateblock{ width: 5rem; position: relative; font-size:.7rem; font-family:serif; font-weight:bold; text-align: center; color:white; } ``` **涉及到的特性:** - `transform-oragin` 调整指针的初始位置以及旋转的轴点:[transform-oragin](https://developer.mozilla.org/en-US/docs/Web/CSS/transform-origin) ```css transform:rotate(0deg); transform-origin:50% 100%; //这里旋转点是bottom ``` - `transform: rotate()` ## JS代码 ```js //时分表每次移动没有过渡效果,仅仅根据角度来定旋转的位置, //不用考虑354->0度的一个回旋BUG,若过渡时间过短会出现闪动。 //左边时钟表盘部分 function left(){ const secondHand = document.querySelector(".second-hand"); const minHand = document.querySelector(".min-hand"); const hourHand = document.querySelector(".hour-hand"); function updata(){ const now = new Date(); //秒针的旋转计算 const seconds = now.getSeconds(); const secondsDegrees = seconds*6; secondHand.style.transform = `rotate(${secondsDegrees}deg)`; //分针旋转的计算 const mins = now.getMinutes(); const minsDegrees = (mins*6)+(seconds/60)*6; minHand.style.transform = `rotate(${minsDegrees}deg)`; //时针旋转的计算 const hours = now.getHours(); const hoursDegrees = (hours-12)*30+(mins/60)*30; hourHand.style.transform = `rotate(${hoursDegrees}deg)`; } setInterval(updata,1000); updata(); } //右边电子日历部分 function right(){ const DATE = document.querySelector(".date"); const WEEK = document.querySelector(".week"); const TIME = document.querySelector(".time"); function Adate(){ const now = new Date(); const weekList = ["星期一","星期二","星期三","星期四","星期五","星期六","星期日"]; const str = now.getFullYear()+"-"+now.getMonth()+"-"+now.getDay(); DATE.innerHTML = str; WEEK.innerHTML = weekList[now.getDay()]; } Adate(); setInterval(Adate,24*3600); // 分钟,秒,不足两位时,用0进行凑位。 function zero(arg){ if(arg>=10){ return arg; }else{ return "0"+arg; } } // 显示当前时间的函数 function Atime(){ const now = new Date(); const str = now.getHours()+":"+zero(now.getMinutes())+":"+zero(now.getSeconds()); TIME.innerHTML=str; } Atime(); setInterval(Atime,1000); } left(); right(); ``` - 获取秒针、分钟、小时节点 ```js const secondHand = document.querySelector(".second-hand"); const minHand = document.querySelector(".min-hand"); const hourHand = document.querySelector(".hour-hand"); ``` - 获取当前时间秒、分、小时 ```js const now = new Date(); const seconds = now.getSeconds(); const mins = now.getMinutes(); const hours = now.getHours(); ``` - 计算秒、分、小时角度 ```js const secondsDegrees = seconds*6; const minsDegrees = (mins*6)+(seconds/60)*6; const hoursDegrees = (hours-12)*30+(mins/60)*30; ``` - 根据角度设置样式 ```js secondHand.style.transform = `rotate(${secondsDegrees}deg)`; minHand.style.transform = `rotate(${minsDegrees}deg)`; hourHand.style.transform = `rotate(${hoursDegrees}deg)`; ``` - 设置定时器,每秒调用一次`setDate`函数 ```js setInterval(updata,1000); ``` ================================================ FILE: 02 - JS and CSS Clock/click.css ================================================ /* * @Author: Administrator * @Date: 2018-11-27 20:14:35 * @Last Modified by: Administrator * @Last Modified time: 2018-11-30 11:41:15 */ /*时分秒指针初始化是垂直的,指针移动没有设置过渡效果和过渡时间, 就是根据角度来定位置*/ html{ /*font-size:625%,默认字体大小都是16px,16*62.5=100px,1rem=100px*/ font-size:625%; background: #018DED url(./image/picture4.jpg) bottom center ; background-size: cover; } html,body{ margin:0px; padding:0px; display: flex; min-height: 100vh; justify-content:center; align-items:center; } /*采用的是标准盒模型,即是纯宽高*/ .clock{ position:relative; width:3rem; height:3rem; border:0.2rem solid white; margin:0.5rem auto; padding:0.2rem; background: rgba(0,0,0,0.4); border-radius:50%; box-shadow:0 0 2px 4px rgba(0,0,0,0.1), 0 0 10px 3px rgba(0,0,0,0.2), 0 0 1px 2px #EFEFEF inset, 0 0 30px black inset; } .clock-face{ position:relative; width:100%; /*这里的100%是300px,是clock的宽*/ height:100%; } /*时钟表表盘中心圆点*/ .clock-face::after{ content:''; display: block; width:.1rem; height:.1rem; background-color: #a8c5d1; position: absolute; left:50%; top:50%; transform:translate(-50%,-50%); border-radius:50%; } /*指针通用样式,在sass中可以封装成一个mixin*/ .hand{ background: #fff; position:absolute; bottom:50%; left:50%; /*transform:translateX(-50%); 虽说这样可以使指针居中线,但是translate的平移是相对于自身center位置的,那么这样居中处理后,下面的旋转仍旧按的是平移之前的right位置为原点,虽说三个指针通过translateY看似处于一条中线上,实际旋转时仍然是按照各自的right位置进行旋转*/ transform:rotate(0deg); transform-origin:50% 100%; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 8px rgba(0, 0, 0, 0.4), 2px 5px 1px rgba(0, 0, 0, 0.5); } /*时针样式*/ .hour-hand{ height:40%; width:0.1rem; margin-left:-0.05rem; /*使时针向左移动自身的一半来居中*/ border-bottom-left-radius: .05rem; border-top-left-radius:.05rem; } .min-hand { height: 45%; width: .05rem; margin-left:-0.025rem; } .second-hand { height: 50%; width: .02rem; margin-left:-0.01rem; border-bottom-left-radius: .02rem; border-top-left-radius: .02rem; background-color: red; } /*日期,时间,星期几的样式*/ .dateblock{ width: 5rem; position: relative; font-size:.7rem; font-family:serif; font-weight:bold; text-align: center; color:white; } ================================================ FILE: 02 - JS and CSS Clock/clock.js ================================================ /* * @Author: Administrator * @Date: 2018-11-27 20:15:18 * @Last Modified by: Administrator * @Last Modified time: 2018-11-30 11:08:26 */ //时分表每次移动没有过渡效果,仅仅根据角度来定旋转的位置, //不用考虑354->0度的一个回旋BUG,若过渡时间过短会出现闪动。 function left(){ const secondHand = document.querySelector(".second-hand"); const minHand = document.querySelector(".min-hand"); const hourHand = document.querySelector(".hour-hand"); function updata(){ const now = new Date(); //秒针的旋转计算 const seconds = now.getSeconds(); const secondsDegrees = seconds*6; secondHand.style.transform = `rotate(${secondsDegrees}deg)`; //分针旋转的计算 const mins = now.getMinutes(); const minsDegrees = (mins*6)+(seconds/60)*6; minHand.style.transform = `rotate(${minsDegrees}deg)`; //时针旋转的计算 const hours = now.getHours(); const hoursDegrees = (hours-12)*30+(mins/60)*30; hourHand.style.transform = `rotate(${hoursDegrees}deg)`; } setInterval(updata,1000); updata(); } function right(){ const DATE = document.querySelector(".date"); const WEEK = document.querySelector(".week"); const TIME = document.querySelector(".time"); function Adate(){ const now = new Date(); const weekList = ["星期一","星期二","星期三","星期四","星期五","星期六","星期日"]; const str = now.getFullYear()+"-"+now.getMonth()+"-"+now.getDay(); DATE.innerHTML = str; WEEK.innerHTML = weekList[now.getDay()]; } Adate(); setInterval(Adate,24*3600); // 分钟,秒,不足两位时,用0进行凑位。 function zero(arg){ if(arg>=10){ return arg; }else{ return "0"+arg; } } // 显示当前时间的函数 function Atime(){ const now = new Date(); const str = now.getHours()+":"+zero(now.getMinutes())+":"+zero(now.getSeconds()); TIME.innerHTML=str; } Atime(); setInterval(Atime,1000); } left(); right(); ================================================ FILE: 02 - JS and CSS Clock/clock1.js ================================================ /* * @Author: Administrator * @Date: 2018-11-29 09:54:44 * @Last Modified by: Administrator * @Last Modified time: 2018-11-30 11:34:30 */ const secondHand = document.querySelector('.second-hand'); const minsHand = document.querySelector('.min-hand'); const hourHand = document.querySelector('.hour-hand'); let secondDeg = 0; let minDeg = 0; let hourDeg = 0; //初始化函数,用来标定当前时间下时分秒的位置,遵循规则:小单位移动角度的效果累加到大单位上。 function initDate() { const date = new Date(); const second = date.getSeconds(); secondDeg = second*6; //一秒转动的角度为6° const min = date.getMinutes(); minDeg = (min + second / 60)*6; //秒转换为分钟,一分钟转动的角度也是6° const hour = date.getHours(); hourDeg = ((hour-12)+(min/60)+(second/3600)) * 30; //转化为小时,一小时转动的角度的30° secondHand.style.transform = `rotate(${ secondDeg }deg)`; minsHand.style.transform = `rotate(${ minDeg }deg)`; hourHand.style.transform = `rotate(${ hourDeg }deg)`; } //跟新函数:每隔一秒计算一次,也即是每次增加1s,(1/60)分钟,(1/3600)小时 function updateDate() { secondDeg += 1*6; minDeg += (1 / 60)*6; hourDeg += (1/3600)*30; secondHand.style.transform = `rotate(${ secondDeg }deg)`; minsHand.style.transform = `rotate(${ minDeg }deg)`; hourHand.style.transform = `rotate(${ hourDeg }deg)`; } initDate(); setInterval(updateDate, 1000); //通过初始化时分秒的位置,然后进行角度的累加 ================================================ FILE: 02 - JS and CSS Clock/clock2.js ================================================ /* * @Author: Administrator * @Date: 2018-11-30 10:48:55 * @Last Modified by: Administrator * @Last Modified time: 2018-11-30 11:34:08 */ /* * @Author: Administrator * @Date: 2018-11-27 20:15:18 * @Last Modified by: Administrator * @Last Modified time: 2018-11-29 13:53:54 */ //每隔一秒,计算一次时分秒的角度进行定位。 function left(){ const secondHand = document.querySelector(".second-hand"); const minHand = document.querySelector(".min-hand"); const hourHand = document.querySelector(".hour-hand"); function updata(){ const now = new Date(); //秒针的旋转计算,以及闪动消除 const seconds = now.getSeconds(); const secondsDegrees = seconds*6; if (secondsDegrees === 0) { secondHand.style.transition = 'all 0s'; } else { secondHand.style.transition = 'all 0.05s'; } secondHand.style.transform = `rotate(${secondsDegrees}deg)`; //分针旋转的计算 const mins = now.getMinutes(); const minsDegrees = (mins*6)+(seconds/60)*6; if (minsDegrees === 0) { minHand.style.transition = 'all 0s'; } else { minHand.style.transition = 'all 0.05s'; } minHand.style.transform = `rotate(${minsDegrees}deg)`; //时针旋转的计算 const hours = now.getHours(); const hoursDegrees = (hours-12)*30+(mins/60)*30; hourHand.style.transform = `rotate(${hoursDegrees}deg)`; } setInterval(updata,1000); updata(); } function right(){ const DATE = document.querySelector(".date"); const WEEK = document.querySelector(".week"); const TIME = document.querySelector(".time"); function Adate(){ const now = new Date(); const weekList = ["星期一","星期二","星期三","星期四","星期五","星期六","星期日"]; const str = now.getFullYear()+"-"+now.getMonth()+"-"+now.getDay(); DATE.innerHTML = str; WEEK.innerHTML = weekList[now.getDay()]; } Adate(); setInterval(Adate,24*3600); // 分钟,秒,不足两位时,用0进行凑位。 function zero(arg){ if(arg>=10){ return arg; }else{ return "0"+arg; } } // 显示当前时间的函数 function Atime(){ const now = new Date(); const str = now.getHours()+":"+zero(now.getMinutes())+":"+zero(now.getSeconds()); TIME.innerHTML=str; } Atime(); setInterval(Atime,1000); } left(); right(); ================================================ FILE: 02 - JS and CSS Clock/index.html ================================================ RealTimeClock
================================================ FILE: 02 - JS and CSS Clock/style.css ================================================ /* * @Author: Administrator * @Date: 2018-11-27 20:14:35 * @Last Modified by: Administrator * @Last Modified time: 2018-11-29 12:41:57 */ html{ /*font-size:625%,默认字体大小都是16px,16*62.5=100px,1rem=100px*/ font-size:625%; background: #018DED url(http://unsplash.it/1500/1000?image=881&blur=50) bottom center ; background-size: cover; } html,body{ margin:0px; padding:0px; display: flex; min-height: 100vh; justify-content:center; align-items:center; } /*采用的是标准盒模型,即是纯宽高*/ .clock{ position:relative; width:3rem; height:3rem; border:0.2rem solid white; margin:0.5rem auto; padding:0.2rem; background: rgba(0,0,0,0.4); border-radius:50%; box-shadow:0 0 2px 4px rgba(0,0,0,0.1), 0 0 10px 3px rgba(0,0,0,0.2), 0 0 1px 2px #EFEFEF inset, 0 0 30px black inset; } .clock-face{ position:relative; width:100%; /*这里的100%是300px,是clock的宽*/ height:100%; /*transform: translateY(-30px);*/ } /*时钟表表盘中心圆点*/ .clock-face::after{ content:''; display: block; width:.1rem; height:.1rem; background-color: #a8c5d1; position: absolute; left:50%; top:50%; transform:translate(-50%,-50%); border-radius:50%; } /*指针通用样式,在sass中可以封装成一个mixin*/ .hand{ background: #fff; position:absolute; top:50%; right:50%; /*transform:translateY(-50%); 虽说这样可以使指针居中线,但是translate的平移是相对于自身center位置的,那么这样居中处理后,下面的旋转仍旧按的是平移之前的right位置为原点,虽说三个指针通过translateY看似处于一条中线上,实际旋转时仍然是按照各自的right位置进行旋转*/ /*transform:rotate(90deg);*/ transform-origin:100% 50%; box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1), 0 0 8px rgba(0, 0, 0, 0.4), 2px 5px 1px rgba(0, 0, 0, 0.5); transition-timing-function:linear; } /*时针样式*/ .hour-hand{ width:40%; height:0.1rem; margin-top:-0.05rem; border-bottom-left-radius: .05rem; border-top-left-radius:.05rem; transition:all 3s; } .min-hand { width: 45%; height: .05rem; margin-top:-0.025rem; transition: all .1s; } .second-hand { width: 50%; height: .02rem; margin-top:-0.01rem; border-bottom-left-radius: .02rem; border-top-left-radius: .02rem; transition: all .05s; background-color: red; } /*日期,时间,星期几的样式*/ .dateblock{ width: 5rem; position: relative; font-size:.7rem; font-family:serif; font-weight:bold; text-align: center; color:white; } ================================================ FILE: 03 - CSS Variables/README.md ================================================ # Day03 - CSS 变量 ## 实现效果 ![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/03%20-%20CSS%20Variables/GIF.gif) ## HTML源码 ```html CSS Variables

Update CSS Variables with JS

演示图片 ``` ## CSS源码 ```css :root{ --spacing:10px; --blur:10px; --base:#ffc600; --fontsize:10px; } html,body{ text-align:center; background: #193549; margin:0; padding:0; min-height: 100vh; font-size:calc(3*var(--fontsize)); font-family:'helvetica neue', sans-serif; /*Helvetica是一种被广泛使用的的西文字体(铅字体),用于印刷行业,Helvetica是苹果电脑的默认字体,微软常用的Arial字体也来自于它*/ font-weight:900;/*设置字体粗细:100---900,400=normal,700=bolder*/ color:white; } .h1{ color:var(--base); } .controls{ font-weight:100; margin-bottom:calc(5*var(--fontsize)); } .controls .wrap{ display: inline-block; margin:5px auto; } .controls .wrap label{ margin-left:20px; } .controls .wrap input{ position:relative; top:3px; width:calc(10*var(--fontsize)); } .controls .wrap #base{ top:-3px; } img{ width:calc(60*var(--fontsize)); height:calc(40*var(--fontsize)); padding:var(--spacing); filter:blur(var(--blur)); background-color:var(--base);/*背景颜色填充内容、内边距、边框,作为打底色*/ } ``` ## JS源码 ```js function spacingchange(){ var spacing=document.querySelector("#spacing"); document.body.style.setProperty('--spacing', spacing.value+'px'); // var img=document.querySelector("#img"); // img.style.padding=spacing.value+'px';一个需要改变的元素可以将其取出,改变它的对应项,但有100个需要改变的元素时,改变根变量值最有效。 } function blurchange(){ var blur=document.querySelector("#blur"); document.body.style.setProperty("--blur",blur.value+'px'); } function basechange(){ var base=document.querySelector("#base"); document.body.style.setProperty("--base",base.value); } ``` ## 过程指南 ### CSS 部分准备 1. 声明全局(`:root`)的 CSS 变量 2. 将变量应用到页面中对应元素 `` 3. 处理标题的 CSS 值 ### JS 实时更新 CSS 值 1. 监听input的change改变函数,然后触发各自的事件处理函数 2. 每个事件中,先取出该元素,然后设置CSS的原生变量值,进而下面的所有样式中凡是用到这个变量的值都跟着改变。 改进:利用字符串模板+遍历添加事件的方法,给每一个input元素添加事件处理函数。 ## 基础知识 1. NodeList 和 Array 的区别 可以打开 __proto__ 查看它的方法,其中有 `forEach()`、`item()`、`keys()` 等。而 Array 的 prototype 中有 `map()`、`pop()` 等数组才有的方法。 3. HTML5 中的自定义数据属性 `dataset` HTML5 中可以为元素添加非标准的自定义属性,只需要加上 `data-` 前缀,可以随便添加和命名。添加之后,可以通过元素的 `dataset` 属性来访问这些值,`dataset` 的值是 DOMStringMap 的一个实例化对象,其中包含之前所设定的自定义属性的“名-值”对。 4. [CSS variable](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Using_CSS_variables) 这是一个 CSS3 的新特性,[IE 和 Edge 目前都还不支持](http://caniuse.com/#feat=css-variables)。命名写法是 `--变量名`,在引用这个变量时写法是 `var(--变量名)`。具体实例见下一条代码。 5. `:root` 伪类 这个伪元素匹配的是文档的根元素,也就是 `` 标签。 所以常用于声明全局的 CSS 变量: ```css :root { --color: #fff; } ``` 在使用时: ```css img { background: var(--base); } ``` 5. CSS 滤镜 [filter](https://developer.mozilla.org/zh-CN/docs/Web/CSS/filter) CSS 的滤镜提供了一些图形特效,比如高斯模糊(blur)、锐化、变色等。它带有一些预设的函数,在使用时加上参数调用这些函数即可。[在 Chrome、Firefox 中都支持。](http://caniuse.com/#search=filter) 6. ``HTML5中type属性之range range 输入类型用于应该包含指定范围值的输入字段。 range 类型显示为滑块。 您也可以设置可接受数字的限制: `` |属性|值|描述| |:---------:|:---------:|:---------:| |max|number|规定允许的最大值。| |min|number|规定允许的最小值。| |step|number|规定合法数字间隔(如果 step="3",则合法数字是 -3,0,3,6,以此类推)。| |value|number|规定默认值。| 7. ``HTML5中type属性之color color输入类型用于规定颜色。 该输入类型允许您从拾色器中选取颜色。 实例: `Color: ` 8. font-weight 属性设置文本的粗细。 > normal 默认值。定义标准的字符。 bold 定义粗体字符。 bolder 定义更粗的字符。 lighter 定义更细的字符。 100, 200, 300, 400, 500, 600, 700, 800, 900:定义由粗到细的字符。400 等同于 normal,而 700 等同于 bold。 inherit 规定应该从父元素继承字体的粗细。 9. background-color 属性为元素设置一种纯色。 这种颜色会填充元素的内容、内边距和边框区域,但不包括外边距。如果边框有透明部分(如虚线边框dashed),会透过这些透明部分显示出背景色。 ## 解决难点 1. **如何处理参数值(一个有 px 、另一个没有)** 运用 `dataset` 储存后缀,有 px 后缀的标签中设置 ``: ```html ``` JS 中通过 `dataset.sizing` 来获取后缀值: ```javascript const suffix = this.dataset.sizing || ''; ``` 此时 suffix 获取到的值,针对颜色为空,而针对长度类的则为 'px'。 2. **如何用 JavaScript 改变 CSS 属性值?** 在 JavaScript 中 `document.documentElement` 即代表文档根元素。所以要改变全局的 CSS 变量,可以这样写: ```js document.documentElement.style.setProperty('--base', '#fff'); ``` ================================================ FILE: 03 - CSS Variables/index.html ================================================ CSS Variables

Update CSS Variables with JS

演示图片 ================================================ FILE: 03 - CSS Variables/style.css ================================================ /* * @Author: Administrator * @Date: 2018-11-30 20:09:15 * @Last Modified by: Administrator * @Last Modified time: 2018-12-03 21:57:37 */ :root{ --spacing:10px; --blur:10px; --base:#ffc600; --fontsize:10px; } html,body{ text-align:center; background: #193549; margin:0; padding:0; min-height: 100vh; font-size:calc(3*var(--fontsize)); font-family:'helvetica neue', sans-serif; /*Helvetica是一种被广泛使用的的西文字体(铅字体),用于印刷行业,Helvetica是苹果电脑的默认字体,微软常用的Arial字体也来自于它*/ font-weight:900;/*设置字体粗细:100---900,400=normal,700=bolder*/ color:white; } .h1{ color:var(--base); } .controls{ font-weight:100; margin-bottom:calc(5*var(--fontsize)); } .controls .wrap{ display: inline-block; margin:5px auto; } .controls .wrap label{ margin-left:20px; } .controls .wrap input{ position:relative; top:3px; width:calc(10*var(--fontsize)); } .controls .wrap #base{ top:-3px; } img{ width:calc(60*var(--fontsize)); height:calc(40*var(--fontsize)); padding:var(--spacing); filter:blur(var(--blur)); background-color:var(--base);/*背景颜色填充内容、内边距、边框,作为打底色*/ } ================================================ FILE: 03 - CSS Variables/variables.js ================================================ /* * @Author: Administrator * @Date: 2018-11-30 20:09:45 * @Last Modified by: Administrator * @Last Modified time: 2018-12-03 22:17:32 */ function spacingchange(){ var spacing=document.querySelector("#spacing"); document.body.style.setProperty('--spacing', spacing.value+'px'); // var img=document.querySelector("#img"); // img.style.padding=spacing.value+'px';一个需要改变的元素可以将其取出,改变它的对应项,但有100个需要改变的元素时,改变根变量值最有效。 } function blurchange(){ var blur=document.querySelector("#blur"); document.body.style.setProperty("--blur",blur.value+'px'); // document.documentElement.style.setProperty("--blur",blur.value+'px'); // 在 JavaScript 中 document.documentElement 即代表文档根元素。所以要改变全局的 CSS 变量,可以这样写 } function basechange(){ var base=document.querySelector("#base"); document.body.style.setProperty("--base",base.value); } ================================================ FILE: 04 - Array Cardio Day 1/README.md ================================================ # Day04 - Array Cardio 指南一 ## 实现效果 这一部分主要是熟悉 Array 的几个基本方法,其中有两个(filter、map)是 ES6 定义的迭代方法,这些迭代方法都有一个特点,就是对数组的每一项都运行给定函数,根据使用的迭代方法的不同,有不同的返回结果。 文档给出了一个初始操作的 `inventor` 数组,基于这个数组可以练习一下`Array`的各个方法,请用`Google Chrome`浏览器打开 `HTML` 后在`Console`面板中查看输出结果。 ## 炫酷的调试技巧 在 Console 中我们常用到的可能是 `console.log()` ,但它还有一个很炫的输出,按照表格来输出,效果如下: ```js console.table(thing) ``` ![console.table()](http://om1c35wrq.bkt.clouddn.com/day4-000.png) ## 原始数据 本节中我们将围绕如下数据进行相关操作以便快速掌握数组的相关方法的使用。 ```js const inventors = [{ first: 'Albert', last: 'Einstein', year: 1879, passed: 1955 }, { first: 'Isaac', last: 'Newton', year: 1643, passed: 1727 }, { first: 'Galileo', last: 'Galilei', year: 1564, passed: 1642 }, { first: 'Marie', last: 'Curie', year: 1867, passed: 1934 }, { first: 'Johannes', last: 'Kepler', year: 1571, passed: 1630 }, { first: 'Nicolaus', last: 'Copernicus', year: 1473, passed: 1543 }, { first: 'Max', last: 'Planck', year: 1858, passed: 1947 }, { first: 'Katherine', last: 'Blodgett', year: 1898, passed: 1979 }, { first: 'Ada', last: 'Lovelace', year: 1815, passed: 1852 }, { first: 'Sarah E.', last: 'Goode', year: 1855, passed: 1905 }, { first: 'Lise', last: 'Meitner', year: 1878, passed: 1968 }, { first: 'Hanna', last: 'Hammarström', year: 1829, passed: 1909 } ]; 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' ]; const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car', 'truck', 'pogostick' ]; ``` ## 筛选 16 世纪出生的发明家 [filter](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/filter) 过滤操作,有点像 SQL 里面的 select 语句。筛出运行结果是 true 的组成数组返回。 ````js const __fifteen = inventors.filter(function(inventor) { if (inventor.year >= 1500 && inventor.year < 1600 ) { return true; } else { return false; } }); console.table(__fifteen); ```` 前面几篇已经提到过箭头函数,这里可以简化一下,用箭头函数来写,而且由于 if 语句的存在并不是必要的,可以写成下面这样: ````js const fifteen = inventors.filter(inventor =>(inventor.year >= 1500 && inventor.year < 1600)); console.table(fifteen); ```` 控制台效果图: ![](http://om1c35wrq.bkt.clouddn.com/day4-001.png) ## 展示他们的姓和名 [map](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/map) map 形象的理解就是,把数组中的每个元素进行处理后,返回一个新的数组。 例子如下: ````js // Array.prototype.map() // 2. 展示他们的姓和名 const fullNames = inventors.map(inventor => `${inventor.first} ${inventor.last}`); console.log(fullNames); ```` 控制台效果图: ![](http://om1c35wrq.bkt.clouddn.com/day4-002.png) ## 把他们按照年龄从大到小进行排序 [sort](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 默认情况下,`Array.prototype.sort()` 会将数组以字符串的形式进行升序排列(10 会排在 2 之前),但 sort 也可以接受一个函数作为参数。所以需要对数字大小排序时需要自己设定一个比较函数,例子如下: ````js // Array.prototype.sort() // 3. 把他们按照年龄从大到小进行排序 const ordered = inventors.sort((a, b) => { if(a.year > b.year) { return 1; } else { return -1; } }); console.table(ordered); ```` 上面的代码可以简写成: ```js const ordered = inventors.sort((a, b) => a.year > b.year ? 1 : -1); console.table(ordered); ``` 控制台效果图: ![](http://om1c35wrq.bkt.clouddn.com/day4-003.png) ## 计算所有的发明家加起来一共活了多少岁 [reduce](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) ```js // Array.prototype.reduce() // 4. 计算所有的发明家加起来一共活了多少岁 const totalYears = inventors.reduce((total, inventor) => { return total + (inventor.passed - inventor.year); }, 0); console.log(totalYears); ``` 控制台效果图: ![](http://om1c35wrq.bkt.clouddn.com/day4-004.png) ## 按照他们活了多久来进行排序 ```js // 5. 按照他们活了多久来进行排序 const oldest = inventors.sort((a, b) => { const lastInventor = a.passed - a.year; const nextInventor = b.passed - b.year; return lastInventor > nextInventor ? -1 : 1; }); console.table(oldest); ``` 控制台效果图: ![](http://om1c35wrq.bkt.clouddn.com/day4-005.png) ## `map、filter`结合使用筛选出网页中含有`CSS`标题的数据名称 ```js const category = document.querySelectorAll('.subject-list h2 a'); const links = Array.from(category); const CSS_BOOK = links .map(link => link.textContent) .filter(streetName => streetName.includes('CSS')); ``` 由 `querySelectorAll()` 获取到的是一个 `NodeList` ,它并非是 Array 类型的数据,所以并不具有 map 和 filter 这样的方法,所以如果要进行筛选操作则需要把它转化成 Array 类型,使用下面示例之中的 `Array.from()` 来转化。 Google Chrome浏览球操作如下: - 打开`https://book.douban.com/tag/web`网页。 - 在控制台按如下图操作即可 ![](http://om1c35wrq.bkt.clouddn.com/day4-006.png) ## 按照姓氏来对发明家进行排序 ```js const alpha = people.sort((lastOne, nextOne) => { const [aLast, aFirst] = lastOne.split(', '); const [bLast, bFirst] = nextOne.split(', '); return aLast > bLast ? 1 : -1; }); console.log(alpha); ``` 控制台效果图: ![](http://om1c35wrq.bkt.clouddn.com/day4-007.png) ## 统计给出数组中各个物品的数量 [reduce](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 这是一个归并数组的方法,它接受一个函数作为参数(这个函数可以理解成累加器),它会遍历数组的所有项,然后构建一个最终的返回值,这个值就是这个累加器的第一个参数。第二个参数中的`0`是`previousValue`的初始值,例子如下: ````js [0,1,2,3,4].reduce((previousValue, currentValue, index, array) => { return previousValue + currentValue; },0); ```` 而此处我们需要统计一个给定数组中各个项的值,恰好可以用到这个方法,在累加器之中,将统计信息存入一个新的对象,最后返回统计值。 ```js const data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car', 'truck', 'pogostick' ]; const transportation = data.reduce( (obj, item) => { if (!obj[item]) { obj[item] = 0; } obj[item]++; return obj; }, {}); console.log(transportation); ``` ```JS var data = ['car', 'car', 'truck', 'truck', 'bike', 'walk', 'car', 'van', 'bike', 'walk', 'car', 'van', 'car','truck', 'pogostick' ]; data.reduce((acc,cur)=>{ cur in acc ? acc[cur]++ : acc[cur]=1; return acc; },{}); ``` ![](http://om1c35wrq.bkt.clouddn.com/day4-008.png) ================================================ FILE: 04 - Array Cardio Day 1/index-FINISHED.html ================================================ Array Cardio 💪

打开Google Chrome浏览器,查看JavaScript 控制台。 💁

================================================ FILE: 04 - Array Cardio Day 1/index-START.html ================================================ Array Cardio 💪

打开Google Chrome浏览器,查看JavaScript 控制台。 💁

================================================ FILE: 05 - Flex Panel Gallery/README.md ================================================ # Day05 - Flex 实现可伸缩的图片墙 中文指南 ## 实现效果 点击任意一张图片,图片展开,同时从图片上下两方分别移入文字。点击已经展开的图片后,图片被压缩,同时该图片上下两端的文字被挤走。 ![](http://om1c35wrq.bkt.clouddn.com/day5_00.gif) ## HTML源码 ```html

Hey

Let's

Dance

Give

Take

Receive

Experience

It

Today

Give

All

You can

Life

In

Motion

``` 初始文档的 DOM 结构:以 `.panels` 为父 `div` 之下,有 5 个类名为 `.panel` 的 `div`,这 5 个各含有 3 个子 `p` 标签。而相应的 CSS 样式中,动画时间等特性已经设定好,只需要完成不同状态下的页面布局以及事件监听即可。 ## CSS 源码 ```css ``` CSS 在这个过程中占了重点,运用 `flex` 可以使各个元素按一定比例占据页面。在调试的时候,可以把边框显示出来方便查看效果。(`border: 1px solid #f00;`) 1. 将 `.panels` 设置为 `display:flex` 2. 设定每个子 `panel` 的 `flex` 值为 `1` 3. 针对每个子 `panel`,设为 `display:flex`,设置其 flex 主轴方向 4. 控制 `.panle` 的子元素 `

` 中的文字垂直、水平居中(单独看每个 panel,其中的文字也可以用 flex 的思路来使其三等分后居中) 1. 设置为 `display:flex` 2. 设置 `flex` 值 2. 设置其子元素的布局方式:垂直水平居中(沿主轴、侧轴居中) 4. 设定点击图片后文字移动的样式 5. 设定点击图片展开后的图片的 `flex` 值 **重要:不了解CSS和Flex的童鞋必看。** - [CSS参考手册](http://www.css88.com/book/css/properties/flex/flex.htm) - [选择器(Selectors)](https://developer.mozilla.org/zh-CN/docs/Web/Guide/CSS/Getting_started/Selectors) - [CSS选择器笔记](http://www.ruanyifeng.com/blog/2009/03/css_selectors.html) - [flex布局完全入门教程](http://bbs.kongyixueyuan.com/topic/10/flex布局完全入门教程) ## JS源码 ```js ``` 1. 获取所有类名为 `panel` 的元素 2. 为其添加 `click` 事件监听,编写触发事件调用的函数(给触发的 DOM 元素添加/去掉样式,实现拉伸/压缩的效果) 3. 为其添加 `transitionend` 事件监听,编写调用的函数(添加/去掉样式,实现文字的飞入/飞出效果) ================================================ FILE: 05 - Flex Panel Gallery/animation.html ================================================ flexbox gallery

HEY

LET'S

DANCE

GIVE

TAKE

RECEIVE

WXPERE

IT

TODAY

GIVE

ALL

YOUCAN

UP

IN

FIGHT

================================================ FILE: 05 - Flex Panel Gallery/index-FINISHED.html ================================================ /*这里用了过渡的思路:transition,而我自己做的用了animation+flex-grow的变化以及@media*/ Flex Panels ```css ``` ```html

Hey

Let's

Dance

Give

Take

Receive

Experience

It

Today

Give

All

You can

Life

In

Motion

``` ```js ``` ================================================ FILE: 05 - Flex Panel Gallery/index-START.html ================================================ Flex Panels 💪

Hey

Let's

Dance

Give

Take

Receive

Experience

It

Today

Give

All

You can

Life

In

Motion

================================================ FILE: 06 - Fetch、filter、正则表达式实现快速古诗匹配/.vscode/launch.json ================================================ { "version": "0.2.0", "configurations": [ { "type": "chrome", "request": "launch", "name": "Launch Chrome against localhost", "url": "http://localhost:8080", "webRoot": "${workspaceRoot}" }, { "type": "chrome", "request": "attach", "name": "Attach to Chrome", "port": 9222, "webRoot": "${workspaceRoot}" } ] } ================================================ FILE: 06 - Fetch、filter、正则表达式实现快速古诗匹配/README.md ================================================ # Day06 - Fetch、filter、正则表达式实现快速古诗匹配 ## 效果图 在输入框中搜索`字或者某个词`快速匹配含有这个字或者是词的诗句。 ![](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) ## 涉及特性 - flex布局 - `nth-child`奇偶匹配 - Fetch获取数据以及异常处理 - Array - `filter()` - `push()` - `...` - JavaScript RegExp 对象 - 字面量语法 - 创建 RegExp 对象的语法 - 修饰符`i`、`g` - `test()` - `replace()` - 防抖处理 - 匹配文本的高亮显示 - "瓦楞纸效果"的实现:transform:perspective(xxxpx) rotateX(xdeg) ## 实现步骤 - UI布局 - 通过Fetch下载数据 - 数据处理并保存 - 事件监听 - 数据匹配操作 - 新数据替换展示 ## 布局篇 - HTML代码 ```html
  • 输入诗人名字
  • 输入关键字,找一首诗
``` - CSS代码 ```css ``` - CSS布局相关参考文档 - [CSS参考手册](http://www.css88.com/book/css/properties/flex/flex.htm) - [CSS选择器笔记](http://www.ruanyifeng.com/blog/2009/03/css_selectors.html) - [flex布局完全入门教程](http://bbs.kongyixueyuan.com/topic/10/flex布局完全入门教程) - [使用HTML5里的classList操作CSS类](http://www.webhek.com/post/html5-classlist-api.html) - [position](http://zh.learnlayout.com/position.html) ## 通过Fetch下载数据解析并且保存 ```js //通过fetch来获取后台数据,并进行json化以及请求异常处理。 fetch(url) .then(response=>{ if(response.ok){ return response.json(); }else{ return Promise.reject({ status:response.status, statusText:response.statusText }); } }) .then(data => { poetrys.push(...data); //concat会创建一个新数组,push会修改原来的数组,所以可以直接拿过来用。 // console.log(poetrys); }) .catch(e=>{console.log("status:",e.status); console.log("statusText:",e.statusText); }); ``` [Fetch详细使用文档,详见本人博客](https://blog.csdn.net/qq_39207948/article/details/85050687) ## 事件监听 ```js const search = document.querySelector(".search"); const suggestions = document.querySelector(".suggestions"); search.addEventListener("change",debounce(findMatches,500)); //当输入框中文本改变时会触发事件处理函数 search.addEventListener("keyup",debounce(findMatches,500)); //当按键up时会触发事件,最好有防抖操作。 ``` 获取`search`和`suggestions'`节点分别对`change`、`keyup`事件进行监听,当输入框中的内容发生变化或者键盘弹起时触发`debounce`函数进行防抖处理。 ## 数据匹配操作 - RegExp使用基础 [RegExp参考文档](http://www.w3school.com.cn/jsref/jsref_obj_regexp.asp) - 项目源码分析 ```js //关键字匹配函数,在里面又调用了内容加载函数 function findMatches(){ //有搜索内容时,进行关键字匹配,没有的话显示两行提示 if(this.value){ let regexp = new RegExp(this.value,"gi"); let matched= poetrys.filter(item=>{ //根据标题、作者名、文本中的是否有关键字,将该数组项取出 return regexp.test(item.title)||regexp.test(item.detail_author)||regexp.test(item.detail_text); }); if(matched.length > 0){ //如果匹配到数组项,将匹配到的内容加载出来 createDom(matched); }else{ //如果没有匹配项,那么显示提示信息。 suggestions.innerHTML=''; suggestions.innerHTML=`
  • 抱歉,没有查找到匹配项!
  • ` } }else{ suggestions.innerHTML=`
  • 输入诗人名字
  • 输入关键字,找一首诗
  • `; } } //将匹配到的内容加载出来 function createDom(matched){ let frag = document.createDocumentFragment(); //用文本片段的形式进行一次性添加,减少回流重绘。 matched.forEach(item=>{ let li = document.createElement("li"); let p= document.createElement("p"); let regexp = new RegExp(search.value,"gi"); //将匹配到的关键词用带样式的形式进行替换。 let detailText = item.detail_text.replace(regexp,`${search.value}`); let title = item.title.replace(regexp,`${search.value}`); let detailAuthor = item.detail_author[0].replace(regexp,`${search.value}`); li.innerHTML = detailText; p.innerHTML = title + "-" +detailAuthor; // p.setAttribute("style","text-align:right;margin:0px"); li.appendChild(p); frag.appendChild(li); }); suggestions.innerHTML=''; suggestions.appendChild(frag); } ``` ================================================ FILE: 06 - Fetch、filter、正则表达式实现快速古诗匹配/index.html ================================================ Fetch,filter,Regexp实现快速古诗匹配
    • 输入诗人名字
    • 输入关键字,找一首诗
    ================================================ FILE: 07 - Array Cardio Day 2/README.md ================================================ # Day07 - Array Cardio 中文指南二 第七天的练习是接着之前[Day04 - Array Cardio 中文指南一](http://bbs.kongyixueyuan.com/topic/40/day04-array-cardio-%E6%8C%87%E5%8D%97%E4%B8%80)的练习,继续熟练数组的方法,依旧没有页面显示效果,所以请打开浏览器的Console面板进行调试运行。 ![](http://om1c35wrq.bkt.clouddn.com/dya7%20-%20000.png) ![](http://om1c35wrq.bkt.clouddn.com/day7%20-%20001.png) ## 任务表 网站给了两个数组,分别为`people`数组和`comments`数组,如下: ```JavaScript const people = [ { name: 'Wes', year: 1988 }, { name: 'Kait', year: 1986 }, { name: 'Irv', year: 1970 }, { name: 'Lux', year: 2015 } ]; const comments = [ { text: 'Love this!', id: 523423 }, { text: 'Super good', id: 823423 }, { text: 'You are the best', id: 2039842 }, { text: 'Ramen is my fav food ever', id: 123523 }, { text: 'Nice Nice Nice!', id: 542328 } ]; ``` **在此两数组的基础上实现一下几个操作:** 1. 是否至少有一人年满`19`周岁? 2. 是否每一个人都年满`19`周岁? 3. 是否存在`id=823423`的评论? 4. 找到`id=823423`的评论的序列号(下标)。 5. 删除`id=823423`的评论。 ## 是否至少有一人年满19周岁? ### `Array.prototype.some()` > [some参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/some) - CASE ```js let isBiggerThan10 = (element, index, array) => { return element > 10; } [2, 5, 8, 1, 4].some(isBiggerThan10); // false [12, 5, 8, 1, 4].some(isBiggerThan10); // true ``` - Syntax ```js arr.some(callback[, thisArg]) ``` - Parameters - element:当前在操作的对象。 - index:当前操作对象的索引。 - array:在操作的数组指针。 - Return value 返回`true`或者`false`,返回true,说明数组中有满足条件的数据存在,返回false,说明数组里面没有满足条件的数组存在。 ### 项目源码 - 版本一: ```js const isAdult = people.some(function(person){ const currentYear = new Date().getFullYear(); if(currentYear - person.year >= 19){ return true; } }); console.log(isAdult); ``` - 版本二: ```JavaScript const isAdult = people.some((person) => { const currentYear = new Date().getFullYear(); if(currentYear - person.year >= 19){ return true; } }); console.log(isAdult); ``` - 版本三: ```JavaScript const isAdult = people.some(person => (new Date().getFullYear() - person.year) >= 19 ); console.log(isAdult); ``` ## 是否每一个人都年满`19`周岁? ### `Array.prototype.every()` > [every参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/every) - CASE ```js let isBigEnough = (element, index, array) => { return element >= 10; } [12, 5, 8, 130, 44].every(isBigEnough); // false [12, 54, 18, 130, 44].every(isBigEnough); // true ``` - Syntax ```js arr.every(callback) ``` - Parameters - Parameters - element:当前在操作的对象。 - index:当前操作对象的索引。 - array:在操作的数组指针。 - Return value 返回`true`或者`false`,返回true,代表数组中所有数据都满足条件,否则,至少有一条数据不满足条件。 ### 项目源码 ```JavaScript const everyAdult = people.every(person => (new Date().getFullYear() - person.year) >= 19); console.log({everyAdult}); ``` ## 是否存在`id=823423`的评论? ### `Array.prototype.find(callback)` > [find参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/find) - CASE ```js let isBigEnough = (element) => { return element >= 15; } [12, 5, 8, 130, 44].find(isBigEnough); // 130 ``` - Syntax ```js arr.find(callback) ``` - Parameters - element:当前在操作的对象。 - index:当前操作对象的索引。 - array:在操作的数组指针。 - Return value 如果有满足条件对象,返回该对象,否则返回`undefined `。 ### 项目源码 ```JavaScript const findComment = comments.find(comment => comment.id === 823423); console.log(findComment); } ``` ## 找到`id=823423`的评论的序列号(下标) ### `Array.prototype.findIndex()` > [findIndex参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex) - CASE ```js let isBigEnough = (element) => { return element >= 15; } [12, 5, 8, 130, 44].findIndex(isBigEnough); // index of 4th element in the Array is returned, // so this will result in '3' ``` - Syntax arr.findIndex(callback) - Parameters - element:当前在操作的对象。 - index:当前操作对象的索引。 - array:在操作的数组指针。 - Return value 返回满足条件的当前对象在数组中的索引,如果找不到满足条件的对象,返回`-1`。 ### 项目源码 ```JavaScript const findCommentIndex = comments.findIndex(comment => comment.id === 823423); console.log(findCommentIndex); ``` ## 删除`id=823423`的评论 > [splice参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/splice) > [slice参考文档](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/slice) ### `Array.prototype.splice()` - CASE 在索引2的位置移除0个元素,并且插入"drum" ```js var myFish = ['angel', 'clown', 'mandarin', 'sturgeon']; var removed = myFish.splice(2, 0, 'drum'); // myFish 是 ["angel", "clown", "drum", "mandarin", "sturgeon"] // removed is [], 没有元素被移除。 ``` 从索引3开始移除1个元素。 ```js var myFish = ['angel', 'clown', 'drum', 'mandarin', 'sturgeon']; var removed = myFish.splice(3, 1); // 移除的原色是 ["mandarin"] // myFish 为 ["angel", "clown", "drum", "sturgeon"] ``` 从索引2移除一个元素,并且插入"trumpet" ```js var myFish = ['angel', 'clown', 'drum', 'sturgeon']; var removed = myFish.splice(2, 1, 'trumpet'); // myFish 为 ["angel", "clown", "trumpet", "sturgeon"] // 移除的元素为 ["drum"] ``` 从索引0开始移除2个元素,并且插入"parrot", "anemone" 和 "blue"。 ```js var myFish = ['angel', 'clown', 'trumpet', 'sturgeon']; var removed = myFish.splice(0, 2, 'parrot', 'anemone', 'blue'); // myFish为 ["parrot", "anemone", "blue", "trumpet", "sturgeon"] // 移除的元素是 ["angel", "clown"] ``` 从索引2开始移除所有元素 ```js var myFish = ['angel', 'clown', 'mandarin', 'sturgeon']; var removed = myFish.splice(2); // myFish 为 ["angel", "clown"] // 移除的原色为 ["mandarin", "sturgeon"] ``` - Syntax ```js array.splice(start) array.splice(start, deleteCount) array.splice(start, deleteCount, item1, item2, ...) ``` **array.splice(start):** 从索引`start`开始移除后面所有的元素。 **array.splice(start, deleteCount):** 从索引`start`元素删除`deleteCount`个元素。 **array.splice(start, deleteCount, item1, item2, ...):**从`start`索引开始,删除`deleteCount`个元素,然后插入`item1`,`item2`,... ### `Array.prototype.slice()` - CASE ```js var a = ['zero', 'one', 'two', 'three']; var sliced = a.slice(1, 3); console.log(a); // ['zero', 'one', 'two', 'three'] console.log(sliced); // ['one', 'two'] ``` - Syntax ```js arr.slice() arr.slice(begin) arr.slice(begin, end) ``` **arr.slice()**等价于**arr.slice(0,arr.length)** **arr.slice(begin)**等价于**arr.slice(begin,arr.length)** `arr.slice(begin, end)`:创建一个新数组,将索引`begin`-`end`(不包含end)的元素放到新数组中并返回新数组,原数组不被修改。 ### 项目源码 - 删除`id=823423`的评论 ``` const comments = [ { text: 'Love this!', id: 523423 }, { text: 'Super good', id: 823423 }, { text: 'You are the best', id: 2039842 }, { text: 'Ramen is my fav food ever', id: 123523 }, { text: 'Nice Nice Nice!', id: 542328 } ]; const findCommentIndex = comments.findIndex(comment => comment.id === 823423); // delete the comment with the ID of 823423 //comments.splice(findCommentIndex,1); const newComments = [ ...comments.slice(0,findCommentIndex), ...comments.slice(findCommentIndex+1) ]; ``` `splice`会修改原数组,`slice`不会改变原数组的值。 ================================================ FILE: 07 - Array Cardio Day 2/index.html ================================================ Array Cardio 💪💪

    Psst: have a look at the JavaScript Console 💁

    ================================================ FILE: 08 - HTML5 Canvas 实现彩虹画笔绘画板/README.md ================================================ # Day08 - HTML5 Canvas 实现彩虹画笔绘画板指南 ## 项目效果 ![](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) > 用 HTML5 中的 Canvas 的路径绘制实现一个绘画板,可供鼠标画画,颜色呈彩虹色渐变,画笔大小同样呈渐变效果。这部分不涉及 CSS 内容,全部由 JS 来实现。 ## 涉及特性 Canvas: - 模板骨架 - 基本属性 - `getContext()` - `strokeStyle` - `lineCap` - `lineJoin` - 路径绘制 - `beginPath()` - `lineTo()` - `moveTo()` 鼠标事件处理: - `mousemove` - `mousedown` - `mouseup` - `mouseout` ## 过程指南 1. 获取 HTML 中的 `` 元素,并设定宽度和高度 2. `.getContext('2d')` 获取上下文,下面以 ctx 表示 3. 设定 ctx 基本属性 - 描边和线条颜色 - 线条宽度 - 线条末端形状 4. 绘画效果 1. 设定一个用于标记绘画状态的变量 2. 鼠标事件监听,不同类型的事件将标记变量设为不同值 3. 编写发生绘制时触发的函数,设定绘制路径起点、终点 5. 线条彩虹渐变效果(运用 hsl 的 `h` 值的变化,累加) 6. 线条粗细渐变效果(设定一个范围,当超出这个范围时,线条粗细进行逆向改变,利用[撞墙反弹程序](https://blog.csdn.net/qq_39207948/article/details/85252068) ## Canvas相关知识 [Canvas_API](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API) ### 代码部分 ```JS //全局变量和初始值设置部分 let drawflag=false; //用于区分鼠标点击事件和鼠标移动事件。 let beginX=0; //设置为全局变量,初始点要传到移动处理事件中。 let beginY=0; let hue=0; //hsl的色调初始值 let context=''; let lineWidth=60; let direction=true; //定义变量增加方向 // 页面加载函数,在DOM结构解析完成后运行 window.onload=function(){ let canvas = document.querySelector("#tutorial"); canvas.width = window.innerWidth; canvas.height = window.innerHeight; context=canvas.getContext("2d"); canvas.addEventListener("mousedown",beginlocation); canvas.addEventListener("mousemove",drawing); canvas.addEventListener("mouseup",()=>drawflag=false); canvas.addEventListener("mouseout",()=>drawflag=false); } ``` - 页面加载函数中用的两个事件处理函数 ```JS //设定初始点坐标,并开启绘图flag function beginlocation(e){ beginX=e.offsetX; beginY=e.offsetY; drawflag=true; } //绘图函数:实际上一段一段的直线连接而成,鼠标每移动一点就将该时刻的坐标转换成下一次的起始坐标,而鼠标移动后的位置作为该段直线结束的坐标。 function drawing(e){ if(drawflag){ let moveX=e.offsetX; let moveY=e.offsetY; //色相值改变 if(hue<=360){ //hue要设置初始值 hue++; }else{ hue=0; } context.strokeStyle=`hsl(${hue},100%,50%)`; //“撞墙反弹程序” if(lineWidth>100||lineWidth<10){ direction = !direction; } if(direction){ lineWidth++; }else{ lineWidth--; } context.lineWidth=lineWidth; context.lineCap="round"; context.lineJoin="round"; context.beginPath(); context.moveTo(beginX,beginY); context.lineTo(moveX,moveY); context.closePath(); [beginX,beginY]=[moveX,moveY]; //es6解构赋值 context.stroke(); }else{ return; } } ``` - canvas 元素 ```js ``` `canvas` 看起来和 `img` 元素很相像,唯一的不同就是它并没有 `src` 和`alt` 属性。实际上,`canvas` 标签只有两个属性——`width`和`height`。这些都是可选的,并且同样利用 `DOM properties` 来设置。当没有设置宽度和高度的时候,`canvas`会初始化宽度为`300`像素和高度为`150`像素。该元素可以使用CSS来定义大小,但在绘制时图像会伸缩以适应它的框架尺寸:如果CSS的尺寸与初始画布的比例不一致,它会出现扭曲。 - 渲染上下文(The rendering context) ```js var canvas = document.getElementById('tutorial'); var ctx = canvas.getContext('2d'); ``` `canvas`元素创造了一个固定大小的画布,它公开了一个或多个渲染上下文,其可以用来绘制和处理要展示的内容。 `canvas`起初是空白的。为了展示,首先脚本需要找到渲染上下文,然后在它的上面绘制。`canvas`元素有一个叫做 `getContext()` 的方法,这个方法是用来获得渲染上下文和它的绘画功能。`getContext()`只有一个参数,上下文的格式。对于2D图像而言,基本教程,你可以使用[CanvasRenderingContext2D](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D) - 检查支持性 替换内容是用于在不支持 `canvas` 标签的浏览器中展示的。通过简单的测试`getContext()`方法的存在,脚本可以检查编程支持性。 ```js var canvas = document.getElementById('tutorial'); if (canvas.getContext){ //支持 var ctx = canvas.getContext('2d'); // drawing code here } else { //不支持 // canvas-unsupported code here } ``` ### Canvas的简单实例 - [canvas 倒计时特效](https://blog.csdn.net/qq_39207948/article/details/85252925) - [canvas 躁动的小球](https://blog.csdn.net/qq_39207948/article/details/85252947) - [canvas 单个小球运动实验](https://blog.csdn.net/qq_39207948/article/details/85252849) ### 涉及知识点 [Canvas](https://developer.mozilla.org/zh-CN/docs/Web/API/Canvas_API) #### canvas宽高设置 ```js canvas.width = window.innerWidth; canvas.height = window.innerHeight; ``` #### 属性 - `lineCap`:笔触的形状,有 round | butt | square 圆、平、方三种。 - `lineJoin`:线条相交的样式,有 round | bevel | miter 圆交、斜交、斜接三种。 - `lineWidth`:线条的宽度 - `strokeStyle`:线条描边的颜色 #### 方法 - `beginPath()`:新建一条路径 - `stroke()`:绘制轮廓 - `moveTo()`:(此次)绘制操作的起点 - `lineTo()`:路径的终点 ### 彩虹渐变颜色——HSL 在这个挑战中,涉及到改变线条的颜色,如何实现彩虹的渐变效果?我们需要利用 HSL 色彩模式,首先可以去这个网站 [http://mothereffinghsl.com](http://mothereffinghsl.com/) 感受一下 HSL 不同色彩值对应的效果。 - H(hue) 代表色调,取值为 0~360,专业术语叫色相 - S 是饱和度,可以理解为掺杂进去的灰度值,取值为 0~1 - L 则是亮度,取值也是 0~1,或者百分比。 这之中 H 值从 0 到 360 的变化代表了色相的角度的值域变化,利用这一点就可以实现绘制时线条颜色的渐变了,只需要在它的值超过 360 时恢复到 0 重新累加即可。 ```js //色相值改变 if(hue<=360){ //hue要设置初始值 hue++; }else{ hue=0; } context.strokeStyle=`hsl(${hue},100%,50%)`; ``` 除此之外,如果想实现黑白水墨的颜色,可以将颜色设置为黑色,通过透明度的改变来实现深浅不一的颜色。 ### 控制笔触大小 ```js //“撞墙反弹程序” if(lineWidth>100||lineWidth<10){ direction = !direction; } if(direction){ lineWidth++; }else{ lineWidth--; } context.lineWidth=lineWidth; ``` 上面的代码中,根据线条的宽度的变化来控制`direction`的值,根据`direction`的值来控制线宽是增加还是减少。 ### 控制线条路径 ```js context.beginPath(); context.moveTo(beginX,beginY); context.lineTo(moveX,moveY); context.closePath(); [beginX,beginY]=[moveX,moveY]; //es6解构赋值 ``` ### 事件监听代码逻辑分析 ```js canvas.addEventListener("mousedown",beginlocation); canvas.addEventListener("mousemove",drawing); canvas.addEventListener("mouseup",()=>drawflag=false); canvas.addEventListener("mouseout",()=>drawflag=false); ``` #### 需要整理知识点 - 1、鼠标事件有哪些,具体使用方法。 - 2、获取窗口的高度与宽度(不包含工具条与滚动条): var w=window.innerWidth; var h=window.innerHeight;浏览器中地址导航栏下面中的部分 和clientWidth以及clientHeight的区别。 clientX和offsetX的区别 clientX检索与窗口客户区域有关的鼠标光标的X坐标, offsetX 检索与触发事件的对象相关的鼠标位置的水平坐标 因为canvas的宽高均设置为了window.innerHtml和window.innerWidth,那么当点击鼠标时,实际上得到的是相对于canvas元素的位置,也即是e.offsetX/Y,这里offset中的set是小写。 - 3、lineCap 属性设置或返回线条末端线帽的样式。 butt 默认。向线条的每个末端添加平直的边缘。 round 向线条的每个末端添加圆形线帽。 square 向线条的每个末端添加正方形线帽。 - 4、lineJoin 属性设置或返回所创建边角的类型,当两条线交汇时。 bevel 创建斜角。 round 创建圆角。 miter 默认。创建尖角。 ================================================ FILE: 08 - HTML5 Canvas 实现彩虹画笔绘画板/index.html ================================================ Canvas Rainbowbrush

    抱歉!您的浏览器暂不支持Canvas标签属性!

    ================================================ FILE: 09 - Console 调试各种姿势指南/README.md ================================================ # Day09 - Console 调试各种姿势指南 ## 项目效果 [控制台打印结果,请猛戳我!!!](https://blog.csdn.net/qq_39207948/article/details/85261675) ## 各种调试正确姿势 ### `.log` 的更多用法 这个是最常用的,但它还有一些更多功能:比如参数支持类似 C 语言的字符串替换模式。 - `%s` 字符串 - `%d` 整数 - `%f` 浮点值 - `%o` Object - `%c` 设定输出的样式,在之后的文字将按照第二个参数里的值进行显示 ```js console.log("I am a String: %s ", "log"); //log console.log("I am a int number: %d ", 1); //1 console.log("I am a float number: %d ", 1.23); //1.23 let dog = {name: "Lucky",age: "5"}; console.log("%o",dog); console.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"); ``` ![](http://om1c35wrq.bkt.clouddn.com/day9-log-method.png) ### 清空 console 面板输出内容 要清空已经打印输出的内容,有两种方式,一种是 JavaScript 语句: `console.clear()`。另一个是快捷键 `Ctrl + L`。 ![](http://om1c35wrq.bkt.clouddn.com/day9-2-2-2.gif) ### 不同样式的输出 除了常规的 `log` 之外,还有一些其他已设定好的样式,区别在于图标或者颜色不一样: ```js // warning! console.warn("用于输出警示信息"); // Error :| console.error("用于输出错误信息"); // Info console.info("用于输出提示性信息"); //debug console.debug("用于输出调试信息"); ``` ![](http://om1c35wrq.bkt.clouddn.com/day9-warn-info.png) ### 打印DOM节点 获取 DOM 元素之后,可以直接打印输出。 ```js const p = document.querySelector('p'); console.log(p); console.dir(p); ``` * .log 输出这个 DOM 的 HTML 标签。 * .dir 则会输出这个 DOM 元素的属性列表。 ![](http://om1c35wrq.bkt.clouddn.com/day9-dir-p.png) ### 断点调试 `console.asset(arg1,arg2)`方法接受一个表达式作为参数,如果参数返回值是`false`,则会输出第二个参数中的内容。 ```js const p = document.querySelector('p'); console.assert(p.classList.contains('ouch'), 'That is wrong!'); ``` ![](http://om1c35wrq.bkt.clouddn.com/day9-assert.png) ### 打印表格 `console.table()`方法,可以将数组、对象以表格的形式打印输出,如果只输出其中的某一列,可以加上第二个参数,如下所示: ```Javascript console.table(dogs); console.table(dogs, ["age"]); ``` ![](http://om1c35wrq.bkt.clouddn.com/day9-table.png) ### 分组打印 ```Javascript const dogs = [{ name: 'Snickers', age: 2 }, { name: 'hugo', age: 8 }]; dogs.forEach(dog => { console.group(`${dog.name}`); // console.groupCollapsed(`${dog.name}`); // 列表默认叠起状态 console.log(`${dog.name}`); console.log(`${dog.age}`); console.log(`${dog.name} 有 ${dog.age} 岁了`); console.groupEnd(); }); ``` `group()`方法中可以传入这个分组的名称。`group()/groupCollapsed() `与 `groupEnd()` 之间的内容会自动分组,区别在于是否能自动折叠。 ![](http://om1c35wrq.bkt.clouddn.com/day9-group-1.png) ![](http://om1c35wrq.bkt.clouddn.com/day9-group-2.png) ### console.count() 计数 通过`console.count()`可以对输出的对象进行计数。但需要注意的是这里的计数对象仅限于由 `count()` 输出的内容,并非所有 `console` 中的输出。 ![](http://om1c35wrq.bkt.clouddn.com/day9-count11.png) ### `time` 计时 用 `time("name")` 和 `timeEnd("name")` 分别控制开始点和结束点,它们两的参数表示当前计时的名称,可以自定义但需要保持相同。所以如果想看异步获取数据花了多场时间,可以这样写: ````js console.time('fetch my data'); fetch("https://api.github.com/users/soyaine") .then(data => data.json()) .then(data => { console.timeEnd('fetch my data'); console.log(data); }); ```` ![](http://om1c35wrq.bkt.clouddn.com/day9-time.png) 如果 timeEnd 中的名称如果和上面不一样,得到的数据是系统当前时间换算后的毫秒值。 ================================================ FILE: 09 - Console 调试各种姿势指南/index-FINISHED.html ================================================ Console Tricks!

    全栈部落的小伙伴,请点击我!

    ================================================ FILE: 10 - JS 实现 Checkbox 中按住 Shift 的多选功能/README.md ================================================ # Day10 - JS 实现 Checkbox 中按住 Shift 的多选功能 ## 项目效果 ![](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) 初始文档中提供了一组 checkbox 类型的 input 元素,选中某个复选框时,其

    标签中的文字会显示删除线。最终效果是,提供按下 Shift 键后进行多选操作的功能。readme.md文档中的说明对应的是xunzhoaxinag.html。 ## 操作方法 1. 选中 A 项 2. 按下 Shift 3. 再选中 B 项 4. A-B 之间的所有项都被选中或者取消 ## 实现方法 ### 方法一 Wes Bos 在文档里提供了一种解决办法:用一个变量,来标记这个范围。 变量初始值为 `false`,当按下 Shift 键且同时选中了某个元素的时候,遍历所有项,遍历过程中,若遇到 A 或 B,则将标记值取反。同时,将所有标记为 `true` 的项设置为选中。 ```js let startChecked; // 处理方法一:用变量 inBetween 对需要选中的元素进行标记 function handleCheck0(e) { let inBetween = false; if(e.shiftKey && this.checked){ boxs.forEach(input => { console.log(input); if(input === startChecked || input ===this) { inBetween = !inBetween; } if(inBetween) { console.log("on"); input.checked = true; } }); } startChecked = this; } ``` > 上面会出现一个问题,初次加载页面时,按住 Shift 再点击某一项,此项之后的元素都会被选中。此外,对于取消选中,无法批量操作。所以我参照了 Stack Overflow 的一个答案: How can I shift-select multiple checkboxes like GMail? 改进得到第二种解决方案。 ### 方法二 方法一中的 inBetween 仅仅表示此项是否在被选中的范围中,此处会赋给它更多的意义,用它来表示此项是选中还是未选中,而范围划定则由数组来解决。 首先将获取到的 `` 组转化为数组,针对每次操作,获取 A 和 B,利用 `indexOf()` 来获得 A 和 B 在数组中的索引值,由此即可确定范围,并能通过 `slice()` 来直接截取 A-B 的所有 DOM 元素,并进行状态改变的操作,而变量 onOff 表示 A-B 范围内的状态,true 表示选中,false 表示取消选中。 ```js const boxs = document.querySelectorAll('.inbox input[type="checkbox"]'); const boxArr = Array.from(boxs); boxArr.forEach(box => box.addEventListener('click', handleCheck1)); // 处理方法二:利用数组索引获取需要选中的范围 let lastinput;//用来保存上一次的点击元素 let onoff; //用来保存上一次的点击状态,已提供给截取范围内CheckBox的状态 function handleCheck1(e){ if(e.shiftKey){ //若果按下shift按键再点击时进入该程序,主要用来处理索引值 let cur=boxArr.indexOf(this); //用来获取当前点击元素是第几个input let last=boxArr.indexOf(lastinput); //用来获取上一次点击元素是第几个input boxArr.slice(Math.min(cur,last),Math.max(cur,last)+1) //slice返回一个子数组 .forEach(item=>item.checked = onoff); //将截取出来的各项状态设置的和上下点击元素的状态一致 } lastinput = this; //用来存放第一次或者上一次的点击元素(将当前点击元素作为上次元素,然后在有点击时和下次又组成一个范围) onoff = lastinput.checked ? true : false; //识别第一次或者上一次点击元素的状态值 } } ``` > 学习用全局变量来存放(由当前状态,转换而成的)上次状态。 设置两个变量(全局变量)来分别保存上一次的点击元素和其状态,接下里选中范围的元素状态以此为依据。 在shift被按下时,需要得到上次点击元素的索引,和当前点击元素(this)的索引, 然后将该段内input元素的状态全部统一于上次点击后的状态。 然后将当前点击元素作为上次元素,进行一个接龙。 ### 涉及知识点 - 1.shiftKey:检测 SHIFT 键是否被按住。 事件属性可返回一个布尔值,指示当事件发生时,“SHIFT”键是否被按下并保持住。 语法:event.shiftKey=true|false|1|0,这里的event用this无效,用e.target无效。 - 2.document.querySelectorAll('.inbox input[type="checkbox"]') 通过[]和属性名称来选取指定元素 ================================================ FILE: 10 - JS 实现 Checkbox 中按住 Shift 的多选功能/index-FINISHED.html ================================================ Document

    This is an inbox layout.

    Check one item

    Hold down your Shift key

    Check a lower item

    Everything inbetween should also be set to checked

    Try do it with out any libraries

    Just regular JavaScript

    Good Luck!

    Don't forget to tweet your result!

    ================================================ FILE: 10 - JS 实现 Checkbox 中按住 Shift 的多选功能/index-START.html ================================================ Document

    This is an inbox layout.

    Check one item

    Hold down your Shift key

    Check a lower item

    Everything inbetween should also be set to checked

    Try do it with out any libraries

    Just regular JavaScript

    Good Luck!

    Don't forget to tweet your result!

    ================================================ FILE: 10 - JS 实现 Checkbox 中按住 Shift 的多选功能/sunzhaoxiang.html ================================================ checkbox的shift多选功能

    无双看过吗?

    从零到壹全栈部落

    输出是最好的学习方式

    赠人玫瑰,手留余香

    祥哥的说,CSDN博客

    全栈部落

    你会玩魔方吗?

    你会留下联系方式吗?

    你会选择下载吗?

    ================================================ FILE: 11 - 自定义视频播放器/README.md ================================================ # Day11 - 自定义视频播放器 ## 效果展示 ![](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) 第十一天是要做一个自定义的视频播放器,在具有基本样式的前提下,实现视频的播放,暂停,进度条拖拽,音量加减,播放速度加减,快进快退的功能。 ## 实现思路 1. 首先需要分别将变量绑定至页面上的元素 2. 分别实现播放,暂停,声音加减,播放速度加减,拖拽快进,点击快进等函数 3. 事件绑定,将页面元素绑定相应触发事件 ## CSS部分解读 - 视频播放器控制台隐藏弹出的设置 ```CSS /*控制台的样式设置*/ .player__controls { display:flex; /*弹性布局,子元素生效*/ position: absolute; bottom:0; width: 100%; transform: translateY(100%) translateY(-5px); transition:all .3s; flex-wrap:wrap; /*运行flex的子元素进行灵活的换行布局*/ background:rgba(0,0,0,0.1); } .player:hover .player__controls { transform: translateY(0); /*这里将控制台显示出来*/ } /*当有鼠标悬停视频播放器时,控制台弹出,此时设置进度条高度为15px*/ .player:hover .progress { height:15px; } ``` > 隐藏露头功能(天猫界面趴着那只猫,只露了个眼睛,悬停会跳出来,就可以据此来模拟实现) transform: translateY(100%) translateY(-5px); 这里的控制台有一定的高度,translateY(100%),是为了将控制台通过位移结合overflow:hidden进行完全隐藏, 然后translateY(-5px),是为了将控制台顶部高度为5px的控制条,向上移动然后将其显示出来。 - input的range类型:[滑动条的自定义样式设置](https://blog.csdn.net/qq_39207948/article/details/85880391) ## 变量绑定 HTML 元素中,`video` 标签是我们的视频,而下面的 `player__controls` 就是我们自己的控制面板 ```html
    ``` 开始之前我们先把所有需要用到的元素节点先取到: ```javascript // 获取视频播放器中的各个元素 const player = document.querySelector('.player'); const video = player.querySelector('.viewer'); const progress = player.querySelector('.progress'); const progressBar = player.querySelector('.progress__filled'); const toggle = player.querySelector('.toggle'); //播放/停止图标按钮 const ranges = Array.from(player.querySelectorAll('.player__slider')); //音量和播放速度按钮 const skipButtons = Array.from(player.querySelectorAll('[data-skip]')); //快进快退按钮 ``` ### 源代码功能函数的实现: #### N0.1 点击视频播放器或者暂停/播放按钮控制视频的停-播,并且暂停/播放按钮图标随着改变 ```HTML video.addEventListener("click",togglePlay); //视频播放器监听点击事件,控制停-播 toggle.addEventListener("click",togglePlay); //toggle按钮监听点击事件,控制停-播 ``` ```javascript // 使用video的两个方法使得动画暂停和运行,而判断的依据就是video.paused属性。 function togglePlay(){ video.paused ? video.play() : video.pause(); } ``` 接下来需要实现,toggle图标随播放状态而改变 ```HTML video.addEventListener("play",updateButton); //根据监听video执行暂停和播放的方法来改变按钮标志 video.addEventListener("pause",updateButton); //根据监听video执行暂停和播放的方法来改变按钮标志 ``` ```JS //改变toggle的图标 function updateButton(){ toggle.textContent = video.paused ? "►":"II" ; } ``` #### NO.2 音量和播放速度滑动条功能的实现 ```HTML ```JS const ranges = Array.from(player.querySelectorAll('.player__slider')); ranges[0].addEventListener("change",handle1); ranges[1].addEventListener("change",handle2); // 音量改变函数 function handle1(){ console.log("yinliang:",this.value); video.volume=this.value; } //播放速度改变函数 function handle2(){ console.log("shudu:",this.value); video.playbackRate=this.value; } ``` > 该视频播放器有两个滑动条,前者控制音量的大小,后者控制播放速度,由于input类型相同,但是绑定的事件处理函数又不相同,可以利用name值做到一个函数来处理不同的回调函数功能(设定name值和对象的属性值一致),具体代码如下: ```JS ranges.forEach(item=>item.addEventListener('change',rangeHandle)); // 音量和播放速度控制函数 function rangeHandle(){ video[this.name]=this.value; //这里动态的进行了对象的属性值设置 } ``` > 其中需要注意的是,他们分别有一个 volume 和 playbackRate 的 name 属性,我们起这两个名字是因为他们是 video 对象里对应音量和播放速度的两个属性名。这样起名并不是必须的,但可以让我们后面 js 的操作更精简。 因为我们上面说过,input 的 name 值和 video 对象中的属性名是一样的,可以看到在 handleRangeUpdate 函数中我们利用了 this.name 的写法来代表属性,,这里的 this 一样是 addEventListener 的调用者,即 range。 #### NO.3 视频快退和快进 ```HTML ``` ```JS const skipButtons = Array.from(player.querySelectorAll('[data-skip]')); //快进快退按钮,将伪数组转换为真正的数组 skipButtons.forEach(item=>item.addEventListener("click",skip));//给快进快退按钮添加点击事件监听函数 // 视频快退和快进控制函数 function skip(){ video.currentTime+=(+this.dataset.skip); } ``` > video 有一个属性叫 currentTime,可以用来设置视频当前的时间。我们只要修改这个属性就可以了 要注意的是,这里就不能用 this 来访问 video 对象了,因为在这里面,this 指向的是遍历得到的每一个 button,而我们是要修改 video 的 currentTime 属性。 data-** 这样的属性以前提到过了,在 JavaScript 中需要通过 .dataset.** 来访问。因为我们获取到的是字符串,所以要通过"+"来转换成数值。 #### NO.4 进度条随着视频播放而改变,进度条拖动控制视频播放功能的实现, - 进度条随着视频播放而改变 我们的进度条需要能在鼠标点击和拖动的时候改变视频播放的进度。我们先实现进度条随着视频播放更新进度的功能。 进度条显示进度的原理很简单,progress__filled 这个元素是一个 flex 定位的元素,我们改变其 flex-basis 的百分比值就可以调节它所占父元素的宽度。flex-basis 值代表 flex 元素在主轴方向上的初始尺寸。progressBar.style.flexBasis对其进行设值便可以动态改变进度条的填充长度。 ```html
    ``` ```js const progress = player.querySelector('.progress'); const progressBar = player.querySelector('.progress__filled'); video.addEventListener("timeupdate",progressFilled); //进度条跟随视频播放改变,那么需要实时自动来执行这个函数。 function progressFilled(){ progressBar.style.flexBasis=((video.currentTime/video.duration)*100).toFixed(2)+"%"; } ``` > 现在只要运行 progressFilled 这个函数就能够更新对应的进度条,但我们需要的是自动执行这个操作。也许你会想到利用 setInterval 设置一个定时器,其实 video 元素给我们提供了更好的方法—— timeupdate 事件。这个事件会在媒体文件的 currentTime 属性改变的时触发. - 进度条控制视频播放功能的实现(包括点击改变和拖动改变) 实现思路:根据进度条填充部分占据整个进度条的百分比,然后结合整个video的duration,便可得出当前的video.currentTime ```JS progress.addEventListener("click",progressClickHandle); //点击改变进度条位置来跟新视频播放进度 // 拖动事件拆分为:mousedown,mousemove,mouseup progress.addEventListener("mousedown",()=>mousedownFlag=true); //鼠标在进度条上按下不放将标志位置为true,然后拖动即触发mousemove事件 progress.addEventListener("mousemove",progressMoveHandle); //拖动改变进度条位置来跟新视频播放进度 progress.addEventListener("mouseup",()=>mousedownFlag=false); //鼠标抬起后,标志位为false,即便触发mousemove,也不会更新视频进度 //点击进度条来改变视频播放(手动更新视频播放进度的函数,点击和拖动都要使用这个函数) function progressClickHandle(e){ video.currentTime = (e.offsetX/progress.offsetWidth)*video.duration; } // 鼠标按下拖动进度条时,更新视频播放进度,从而触发timeupdate事件来校正进度条长度。 let mousedownFlag = false; function progressMoveHandle(e){ if(mousedownFlag){ progressClickHandle(e); } } ``` ### 涉及知识点 1.title 属性规定关于元素的额外信息。   这些信息通常会在鼠标移到元素上时显示一段工具提示文本(tooltip text)。 2.:focus 伪类在元素获得焦点时向元素添加特殊的样式。 3.flex-wrap 属性规定flex容器是单行或者多行,同时横轴的方向决定了新行堆叠的方向。   注释:IE 浏览器不支持此属性。 4.光标cursor的常见样式pointer|e-resize 5.flex-basis属性用于设置或检索弹性盒伸缩基准值 6.[flex属性的全面总结](https://blog.csdn.net/qq_39207948/article/details/85956861) 7.video 对象有一个叫 paused 的属性来判断视频是否在播放   .play() 方法可以播放视频,.pause() 方法暂停播放 8.textContent 属性设置或返回指定节点的文本内容,以及它的所有后代。[结合demo理解记忆](https://blog.csdn.net/qq_39207948/article/details/86099905)   如果您设置了 textContent 属性,会删除所有子节点,并被替换为包含指定字符串的一个单独的文本节点。 9.playbackRate 属性设置或返回音频/视频的当前播放速度。 只有 Google Chrome 和 Safari 支持 playbackRate 属性。   1.0 正常速度   0.5 半速(更慢)   2.0 倍速(更快)   -1.0 向后,正常速度   -0.5 向后,半速 10.video.playbackRate:设置或返回音频/视频播放的速度   video.volume:设置或返回音频/视频的音量 11. 鼠标事件进行整理,具体都有哪些方法和属性。 12. currentTime 属性设置或返回音频/视频播放的当前位置(以秒计)。   当设置该属性时,播放会跳跃到指定的位置。 13.duration 返回当前音频/视频的长度(以秒计) 14. timeupdate事件,当目前的播放位置已更改时触发。可用来实时更新进度条部分。 ================================================ FILE: 11 - 自定义视频播放器/index.html ================================================ 自定义视频播放器
    ================================================ FILE: 11 - 自定义视频播放器/style.css ================================================ * @Author: Administrator * @Date: 2018-12-29 15:53:10 * @Last Modified by: Administrator * @Last Modified time: 2019-01-05 19:52:35 */ html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } body { padding: 0; display:flex; background:#7A419B; min-height:100vh; background: linear-gradient(135deg, #7c1599 0%,#921099 48%,#7e4ae8 100%); background-size:cover; align-items: center; justify-content: center; } /*整个视频播放器的样式设置*/ .player { max-width:750px; border:5px solid rgba(0,0,0,0.2); box-shadow:0 0 20px rgba(0,0,0,0.2); position: relative; font-size: 0; overflow: hidden; } .player__video { width: 100%; } /*按钮选项的通用配置,去除浏览器默认配置*/ .player__button { background:none; border:0; line-height:1; color:white; text-align: center; outline:0; padding: 0; cursor:pointer; max-width:50px; } .player__button:focus { border-color: #ffc600; } /*滑动条的设置*/ .player__slider { width:10px; height:30px; } /*控制台的样式设置*/ .player__controls { display:flex; /*弹性布局,子元素生效*/ position: absolute; bottom:0; width: 100%; transform: translateY(100%) translateY(-5px); transition:all .3s; flex-wrap:wrap; /*运行flex的子元素进行灵活的换行布局*/ background:rgba(0,0,0,0.1); } /*设置控制台鼠标悬停的效果*/ .player:hover .player__controls { transform: translateY(0); } /*当有鼠标悬停视频播放器时,控制台弹出,此时设置进度条高度为15px*/ .player:hover .progress { height:15px; } .player__controls > * { flex:1; } /*进度条样式设置*/ .progress { flex:10; position: relative; display:flex; flex-basis:100%; /*初始长度占据100%*/ height:5px; transition:height 0.3s; /*当鼠标位于视频播放器,进度条由5px变成15PX*/ background:rgba(0,0,0,0.5); cursor:e-resize; /*定义鼠标的样式是左右箭头滑动样式*/ } /*进度条填充部分*/ .progress__filled { width:50%; background:#ffc600; /*橘黄色的进度条填充部分*/ flex:0; flex-basis:0%; /*transition: flex-basis 0.22s linear;*/ } /* unholy css to style input type="range" */ /*主要是滑动条的自定义样式设置*/ /*去除系统默认的滑动条样式,主要针对基于webkit的浏览器*/ input[type=range] { -webkit-appearance: none; background:transparent; width: 100%; margin: 0 5px; } /*原始的控件获取到焦点时,会显示包裹整个控件的边框,所以还需要把边框取消。*/ input[type=range]:focus { outline: none; } /*开始自定义滑动条控件的样式*/ /*自定义滑动控件的轨道*/ input[type=range]::-webkit-slider-runnable-track { width: 100%; height: 8.4px; cursor: pointer; box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0); background: rgba(255,255,255,0.8); /*滑动条轨道背景颜色*/ border-radius: 3.3px; border: 0.2px solid rgba(1, 1, 1, 0); } /*自定义滑动控件的滑块*/ input[type=range]::-webkit-slider-thumb { -webkit-appearance: none; height: 15px; width: 15px; box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0); border-radius: 50px; background: #ffc600; /*和进度条的填充部分一个颜色*/ cursor: pointer; margin-top: -3.5px; box-shadow:0 0 2px rgba(0,0,0,0.2); } /*自定义滑动条获得焦点时的背景颜色*/ input[type=range]:focus::-webkit-slider-runnable-track { background: #bada55; } /*兼容Firefox浏览器*/ input[type=range]::-moz-range-track { width: 100%; height: 8.4px; cursor: pointer; box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0); background: #ffffff; border-radius: 3.3px; border: 0.2px solid rgba(1, 1, 1, 0); } input[type=range]::-moz-range-thumb { box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0); height: 15px; width: 15px; border-radius: 50px; background: #ffc600; cursor: pointer; } /*flex-basis 属性用于设置或检索弹性盒伸缩基准值 一个长度单位或者一个百分比,规定灵活项目的初始长度。 auto 默认值。长度等于灵活项目的长度。如果该项目未指定长度,则长度将根据内容决定。 ================================================ FILE: 12 - 键盘输入序列的验证指南/README.md ================================================ ### Day12 - 键盘输入序列的验证指南 #### 项目效果 ![](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) 文档里提供了一个 `script` 标签,供我们从 [Cornify.com](https://www.cornify.com/) 加载一个 JS 文件,调用其中的 `cornify_add()` 方法时,会在页面中追加 `p` 标签,并在 DOM 中插入一个图标。Cornify 的具体效果如上所示。 #### 实现功能: - 可以在输入区输入内容,且内容长度为5,在匹配区中显示输入内容,长度为4,当匹配到暗码字段时,会调用`cornify_add()`,在页面中添加一个独角兽或者彩虹特效。 #### 解决思路 1. 指定可激发特效的字符串 2. 监测字符串变化 3. 事件监听 4. 正则表达式判断字符串输入 5. 处理输入,在符合条件时,调用 `cornify_add()` #### 代码分析 1、获取元素和事件监听 ``` let input=document.querySelector(".text"); let show= document.querySelector(".show"); input.addEventListener('keyup',debounce(handle,300)); ``` 2、对键盘输入事件进行防抖处理,避免过度调用回调函数 ``` // 防抖处理 function debounce(func,wait){ let timeflag; return function(){ clearTimeout(timeflag); //清除300ms之内之前触发的定时器。 let arg=arguments; let _this = this; timeflag = setTimeout(func.bind(_this,arg),wait); } } ``` > 防抖的主要思路:用一个函数对回调函数和等待时间进行包装,这个包装函数需要在等待时间到达后返回这个回调函数,这里可以使用setTimeout函数实现,需要注意的是需要设置一个时间变量来保存定时器,在wait等待时间之内再次触发监听事件,说明上一个的定时器还存在,需要将它清除。这里的防抖代码解决了this指向和event队象的问题,如果对这块不了解,详解见https://github.com/mqyqingfeng/Blog/issues/22 3、回调函数部分 ``` //回调函数 function handle(){ input.value=input.value.slice(-5); show.textContent=input.value.slice(-4); let regexp = /love/gi; if(regexp.test(show.textContent)){ cornify_add();//这个方法是由通过script引入的cornify.com中的cornify.js提供的。 } } ``` ================================================ FILE: 12 - 键盘输入序列的验证指南/index.html ================================================ key sequence detection

    暗码:love

    匹配显示区:

    ================================================ FILE: 13 - 图片随屏幕滚动而滑入滑出的效果/README.md ================================================ ### Day13 - 图片随屏幕滚动而滑入滑出的效果 #### 项目效果 ![](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) 实现页面内伴随着鼠标滚动,到每个图片时图片出现,并伴随着动画出现。 #### 实现思路 - 1、判断图片的滑出和滑入的位置(JS实现) - 2、根据位置来决定图片的过渡效果(CSS实现) ### 代码解析 #### JS部分 ```JS let imgs=Array.from(document.querySelectorAll("img"));//转换为真正的数组 window.addEventListener("scroll",throttle(handle,100)); // 节流函数(定时器实现方式) function throttle(func,wait){ let timeflag; return function(){ let content=this; let args=arguments; if(!timeflag){ timeflag=setTimeout(function(){ timeflag=null; func.apply(content,args) },wait); } } } // 事件处理函数,每2秒执行一次,停止后也会执行一次。(无首有尾) // 判断图片位置的函数,遍历每一个图片,判断每一个图片是否出现在视图中,来决定是否加类。 function handle(){ imgs.forEach(img=>{ let imgHalfBoolean=(window.scrollY+window.innerHeight)>img.offsetTop+img.height/2;//图片划过一半的判断条件 let imgFullBoolean=window.scrollYimg.offsetTop+img.height/2;//图片划过一半的判断条件 let imgFullBoolean=window.scrollY Document

    Slide In On Scroll

    Consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariaturlores sunt esse magni, ut, dignissimos.

    Lorem ipsum cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    Adipisicing elit. Tempore tempora rerum..

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Tempore tempora rerum, est autem cupiditate, corporis a qui libero ipsum delectus quidem dolor at nulla, adipisci veniam in reiciendis aut asperiores omnis blanditiis quod quas laborum nam! Fuga ad tempora in aspernatur pariatur fugit quibusdam dolores sunt esse magni, ut, dignissimos.

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Voluptates, deserunt facilis et iste corrupti omnis tenetur est. Iste ut est dicta dolor itaque adipisci, dolorum minima, veritatis earum provident error molestias. Ratione magni illo sint vel velit ut excepturi consectetur suscipit, earum modi accusamus voluptatem nostrum, praesentium numquam, reiciendis voluptas sit id quisquam. Consequatur in quis reprehenderit modi perspiciatis necessitatibus saepe, quidem, suscipit iure natus dignissimos ipsam, eligendi deleniti accusantium, rerum quibusdam fugit perferendis et optio recusandae sed ratione. Culpa, dolorum reprehenderit harum ab voluptas fuga, nisi eligendi natus maiores illum quas quos et aperiam aut doloremque optio maxime fugiat doloribus. Eum dolorum expedita quam, nesciunt

    at provident praesentium atque quas rerum optio dignissimos repudiandae ullam illum quibusdam. Vel ad error quibusdam, illo ex totam placeat. Quos excepturi fuga, molestiae ea quisquam minus, ratione dicta consectetur officia omnis, doloribus voluptatibus? Veniam ipsum veritatis architecto, provident quas consequatur doloremque quam quidem earum expedita, ad delectus voluptatum, omnis praesentium nostrum qui aspernatur ea eaque adipisci et cumque ab? Ea voluptatum dolore itaque odio. Eius minima distinctio harum, officia ab nihil exercitationem. Tempora rem nemo nam temporibus molestias facilis minus ipsam quam doloribus consequatur debitis nesciunt tempore officiis aperiam quisquam, molestiae voluptates cum, fuga culpa. Distinctio accusamus quibusdam, tempore perspiciatis dolorum optio facere consequatur quidem ullam beatae architecto, ipsam sequi officiis dignissimos amet impedit natus necessitatibus tenetur repellendus dolor rem! Dicta dolorem, iure, facilis illo ex nihil ipsa amet officia, optio temporibus eum autem odit repellendus nisi. Possimus modi, corrupti error debitis doloribus dicta libero earum, sequi porro ut excepturi nostrum ea voluptatem nihil culpa? Ullam expedita eligendi obcaecati reiciendis velit provident omnis quas qui in corrupti est dolore facere ad hic, animi soluta assumenda consequuntur reprehenderit! Voluptate dolor nihil veniam laborum voluptas nisi pariatur sed optio accusantium quam consectetur, corrupti, sequi et consequuntur, excepturi doloremque. Tempore quis velit corporis neque fugit non sequi eaque rem hic. Facere, inventore, aspernatur. Accusantium modi atque, asperiores qui nobis soluta cumque suscipit excepturi possimus doloremque odit saepe perferendis temporibus molestiae nostrum voluptatum quis id sint quidem nesciunt culpa. Rerum labore dolor beatae blanditiis praesentium explicabo velit optio esse aperiam similique, voluptatem cum, maiores ipsa tempore. Reiciendis sed culpa atque inventore, nam ullam enim expedita consectetur id velit iusto alias vitae explicabo nemo neque odio reprehenderit soluta sint eaque. Aperiam, qui ut tenetur, voluptate doloremque officiis dicta quaerat voluptatem rerum natus magni. Eum amet autem dolor ullam.

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis

    laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio maiores adipisci quibusdam repudiandae dolor vero placeat esse sit! Quibusdam saepe aperiam explicabo placeat optio, consequuntur nihil voluptatibus expedita quia vero perferendis, deserunt et incidunt eveniet temporibus doloremque possimus facilis. Possimus labore, officia dolore! Eaque ratione saepe, alias harum laboriosam deserunt laudantium blanditiis eum explicabo placeat reiciendis labore iste sint. Consectetur expedita dignissimos, non quos distinctio, eos rerum facilis eligendi. Asperiores laudantium, rerum ratione consequatur, culpa consectetur possimus atque ab tempore illum non dolor nesciunt. Neque, rerum. A vel non incidunt, quod doloremque dignissimos necessitatibus aliquid laboriosam architecto at cupiditate commodi expedita in, quae blanditiis. Deserunt labore sequi, repellat laboriosam est, doloremque culpa reiciendis tempore excepturi. Enim nostrum fugit itaque vel corporis ullam sed tenetur ipsa qui rem quam error sint, libero. Laboriosam rem, ratione. Autem blanditiis laborum neque repudiandae quam, cumque, voluptate veritatis itaque, placeat veniam ad nisi. Expedita, laborum reprehenderit ratione soluta velit natus, odit mollitia. Corporis rerum minima fugiat in nostrum. Assumenda natus cupiditate hic quidem ex, quas, amet ipsum esse dolore facilis beatae maxime qui inventore, iste? Maiores dignissimos dolore culpa debitis voluptatem harum, excepturi enim reiciendis, tempora ab ipsam illum aspernatur quasi qui porro saepe iure sunt eligendi tenetur quaerat ducimus quas sequi omnis aperiam suscipit! Molestiae obcaecati officiis quo, ratione eveniet, provident pariatur. Veniam quasi expedita distinctio, itaque molestiae sequi, dolorum nisi repellendus quia facilis iusto dignissimos nam? Tenetur fugit quos autem nihil, perspiciatis expedita enim tempore, alias ab maiores quis necessitatibus distinctio molestias eum, quidem. Delectus impedit quidem laborum, fugit vel neque quo, ipsam, quasi aspernatur quas odio nihil? Veniam amet reiciendis blanditiis quis reprehenderit repudiandae neque, ab ducimus, odit excepturi voluptate saepe ipsam. Voluptatem eum error voluptas porro officiis, amet! Molestias, fugit, ut! Tempore non magnam, amet, facere ducimus accusantium eos veritatis neque.

    ================================================ FILE: 13 - 图片随屏幕滚动而滑入滑出的效果/index.js ================================================ let imgs=Array.from(document.querySelectorAll("img"));//转换为真正的数组 // imgs.forEach(item=>item.addEventListener("scroll",handle)); window.addEventListener("scroll",throttle(handle,100)); // 节流函数(定时器实现方式) function throttle(func,wait){ let timeflag; return function(){ let content=this; let args=arguments; if(!timeflag){ timeflag=setTimeout(function(){ timeflag=null; func.apply(content,args) },wait); } } } // 事件处理函数,每2秒执行一次,停止后也会执行一次。(无首有尾) // 判断图片位置的函数,遍历每一个图片,判断每一个图片是否出现在视图中,来决定是否加类。 function handle(){ imgs.forEach(img=>{ let imgHalfBoolean=(window.scrollY+window.innerHeight)>img.offsetTop+img.height/2;//图片划过一半的判断条件 let imgFullBoolean=window.scrollY 值的复制:是给另外一个变量创建了一个存储空间,二者彼此独立,修改数据互不影响。 由此可见,基本类型,按值操作,新建的变量会将值复制给新的变量,各自的改变不会互相影响。 #### 通过引用操作 (浅拷贝) 对象`Object`类型是按引用操作的,如果它不是基本类型中的一个,那么它就是对象,这里如果我们细究的话,JavaScript中每一个东西都可以当做对象,甚至是基本的类型(不包括`null`和`undefined`),但我们尽量不要钻这个牛角尖。 一些JavaScript中的对象: `Object` `Function` `Array` `Set` `Map` 那对于数组来说,情况是否一样呢?延续上面的思路,下面我们来看看数组。 ```JS const players = ['Wes', 'Sarah', 'Ryan', 'Poppy']; const team = players; console.log(players, team); // ["Wes", "Sarah", "Ryan", "Poppy"] ["Wes", "Sarah", "Ryan", "Poppy"] team[3] = 'Lux'; console.log(players, team); // ["Wes", "Sarah", "Ryan", "Lux"] ["Wes", "Sarah", "Ryan", "Lux"] ``` > 结果显示原数组 plaryers 也被修改了。为什么会这样?因为 team 只是这个数组的引用,并不是它的复制。team 和 players 这两个变量指向的是同一个数组,也即是仅仅浅拷贝了指针,这个变量存储的指向原来数组存储空间的方向,两个变量指向的内容是一样的,这样修改其中一个另一个也会跟着改变。 #### 数组复制的解决方法 (数组深拷贝): - 方法一 Array.prototype.slice() 由于运行 slice ,原数组不会被修改。所以如果修改这两个数组中任意 一个,另一个都不会受到影响。 ```JS const players = ['Wes', 'Sarah', 'Ryan', 'Poppy']; const team2 = players.slice(); team2[3] = 'Lux2'; console.log(players, team2); // ["Wes", "Sarah", "Ryan", "Poppy"] ["Wes", "Sarah", "Ryan", "Lux2"] ``` - 方法二 Array.prototype.concat() concat() 方法是用来合并数组的,它也不会更改原有的数组,而是返回一个新数组,所以可以将 players 数组与一个空数组合并,得到的结果就符合预期了。 ```JS const players = ['Wes', 'Sarah', 'Ryan', 'Poppy']; const team3 = [].concat(players); //或者写成 team3 = players.concat(); team3[3] = 'Lux3'; console.log(players, team3); // ["Wes", "Sarah", "Ryan", "Poppy"] ["Wes", "Sarah", "Ryan", "Lux3"] ``` - 方法三 ES6 扩展运算符 扩展语法可以像扩展参数列表一样来扩展数组,效果与上述方法类似,但比较简洁。 ```JS const players = ['Wes', 'Sarah', 'Ryan', 'Poppy']; const team4 = [...players]; //扩展运算符这里实际上是“打散操作” team4[3] = 'Lux4'; console.log(players, team4); //["Wes", "Sarah", "Ryan", "Poppy"] ["Wes", "Sarah", "Ryan", "Lux4"] ``` - 方法四 Array.from() Array.from() 方法从一个类似数组或可迭代对象中创建一个新的数组实例。 ```JS const players = ['Wes', 'Sarah', 'Ryan', 'Poppy']; const team5 = Array.from(players); team4[3] = 'Lux5'; console.log(players, team4); //["Wes", "Sarah", "Ryan", "Poppy"] ["Wes", "Sarah", "Ryan", "Lux5"] ``` #### object对象的复制方法(对象深拷贝) 对于 Object 数据,我们用一个 person 对象来试试。 先声明对象: ```JS const person = { name: 'Web sun', age: 25 }; ``` 然后思考一下如何可以取得它的复制,试试想当然的做法: ```JS const captain = person; captain.number = 99; console.log(person, captain); // Object {name: "Web sun", age: 25, number: 99} // Object {name: "Web sun", age: 25, number: 99} ``` 这样好像行不通,person 的值也被更改了,那该如何才能真正复制呢,来达到两个变量之间互不影响? - 方法一 ES6的Object.assign() `Object.assign(target, ...sources)`用于对象的合并,将源对象中的所有可枚举属性,复制到目标对象中,并返回合并后的目标对象。后来的源对象的属性值,将会覆盖它之前的对象的属性。 ```JS var obj={ name:'sun', height:'180cm' } var copyobj=Object.assign({},obj); copyobj.name='zhao'; console.log(obj); //{name: "sun", height: "180cm"} console.log(copyobj); //{name: "zhao", height: "180cm"} ``` - 方法二 ES6的扩展运算符 用于取出参数对象的所有可遍历属性,拷贝到当前属性中。 ```JS var obj={ name:'sun', height:180cm } var copyobj={...obj}; copyobj.name='zhao'; console.log(obj); //{name: "sun", height: "180cm"} console.log(copyobj); //{name: "zhao", height: "180cm"} ``` #### 注意:上述对数组深拷贝的方法仅仅适用第一层级的值是基本数据类型的情况。若第一层级的值为对象或者数组等引用类型时,则上述方法失效。对象中若仍嵌套有对象,同样失效。 ## 拷贝所有层级 ##### 1.不仅拷贝第一层级,还能拷贝数组或对象所有层级的各项值。 ##### 2.不是单独针对数组或对象,而是能够通用与数组、对象和其他复杂的JSON形式的对象。 - 方法一 `JSON.parse(JSON.stringify(xxx))` ```JS var array=[{number:1},{number:2},{number:3}]; var copyArray=JSON.parse(JSON.stringify(array)); copyArray[0].number=100; console.log(array);//[{number:1},{number:2},{number:3}]; console.log(copyArray); //[{number:100},{number:2},{number:3}]; ``` > 序列化:将一个JavaScript值(数组或对象)转换为一个JSON字符串;JSON.stringify() 反序列化:将一个JSON字符串转换为对象;JSON.parse() - 方法二 深拷贝递归函数 ```JS function deepcopy(obj){ if(typeof obj == 'object') { //说明参数是一个对象类型,但是数组也是对象,需要具体判断是不是数组类型。 var result = obj.constructor==Array ? [] : {}; for(let i in obj){ //遍历obj的每一项 result[i]=typeof obj[i]=="object" ? deepcopy(obj[i]) : obj[i]; } }else{ var result = obj; } return result; } ``` ================================================ FILE: 14 - JavaScript 引用和值拷贝/index-FINISHED.html ================================================ JS Reference VS Copy ================================================ FILE: 14 - JavaScript 引用和值拷贝/index-START.html ================================================ JS Reference VS Copy ================================================ FILE: 15 - LocalStorage/README.md ================================================ ## 效果图 ![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/15%20-%20LocalStorage/GIF.gif) 第十五天主要是练习`LocalStorage`(本地存储)以及事件委托的使用,使用场景是一个简单的`todo list`的应用,实现基本的添加`item`,切换完成状态,将所有`todo`项存储在`localstorage`中,保证刷新浏览器后数据不丢失。 ## 主要思路 * 提前预定义好所有用到的变量; ```Javascript // 添加item的按钮 const addItems = document.querySelector('.add-items'); // todolist列表 const itemsList = document.querySelector('.plates'); // 本地缓存的所有todoitem const items = JSON.parse(localStorage.getItem('items')) || []; ``` * 为`addItems`按钮添加事件函数,添加一个新的`todo item`并存储到本地缓存; * 监听`checkbox`的点击事件,切换是否完成的状态,并更新本地存储,保证刷新本页面是数据不会丢失; * 分别设置两个监听器,监听`addItems`的`submit`事件,和`itemsList`的点击事件; ## 添加item事件 * 添加item的主要代码如下 ```Javascript function addItem(e) { // 阻止默认事件的触发,防止在提交后页面自己刷新 e.preventDefault(); const text = this.querySelector('[name=item]').value; const item = { // ES6的简写形式 => text: text; text, done: false }; items.push(item); localStorage.setItem('items', JSON.stringify(items)); populateList(items, itemsList); // 添加完数据后,重置输入框 this.reset(); } addItems.addEventListener('submit', addItem); ``` * 监听`addItems`的`submit`事件,当用户点击`enter`或者点击右侧的`submit`按钮的时候触发; * `text,`是ES6的缩写形式,即代表`text: text;` *`localStorage`的常用API: * `localStorage.setItem(‘key’,value); ->` 设置本地缓存,以`key-value`的形式 * `localStorage.getItem(‘key’); ->` 根据参数key取得本地缓存中对应的值 * `localStorage.clear(); ->` 清空本地的缓存 * `localStorage.removeItem(‘key’); ->` 删除key所对应的那一条本地缓存 * `localStorage`中只能存储字符串,所以我们经常会用到: `JSON.stringify(object)`将一个对象转换为字符串,再使用`JSON.parse(objSting)`将一个对象字符串转换为对象 * `this.reset();`代表将表单重置,清空表单中的值,在我们进行了一次submit之后,如果不重置表单的话,表单中的值将不会消失,这将大大影响用户体验 ## 切换完成状态事件 ```Javascript function toggleDone(e) { // if(!e.target.nodeName.match('INPUT')) return; // 跳过所有的input,只处理label if (!e.target.matches('input')) return; const node = e.target; const index = node.dataset.index; items[index].done = !items[index].done; localStorage.setItem('items', JSON.stringify(items)); populateList(items, itemsList); } itemsList.addEventListener('click', toggleDone); ``` * 此处使用到了事件委托,所谓事件委托,我是这么理解的: * 假设我们队一个input列表进行了事件监听,但我们如法保证,此列表在接下来的状态下是否进行了更新,刷新等改变原来节点的操作,如果有这样的操作出现,那么我们之前的事件监听器就无法再起到监听的作用; * 但我们可以对input列表的父元素进行事件监听,让它们的父元素处于监听状态,当我们所点击的元素是其子元素的话,就告诉它的子元素,执行相应的事件; * 相当于委托父元素帮我们监听所有子元素,这样无论子元素列表进行怎么样的更新,改变,只要父元素节点不发生改变就可以持续起到监听的 作用。 * 通过`e.target.matches('input')`可以判断所点击的元素是不是input元素,`e.target`返回所点击的宿主元素。 * 通过获取到所点击的列表的序号,更改其`done`属性,更新后进行存储,就可以实现完成状态的事件。 ## 列表显示函数 ```Javascript // 设置默认值,防止传参数出错的时候crash function populateList(populates = [], place { place.innerHTML = populates.map((populate, index) => { //之所以用‘’空字符是因为如果用null的话,会出现在html中 return `
  • `; // join()之后一定要加'',表示空字符,否则会加入逗号,造成错误 }).join(''); } ``` * 将所有的列表项转化为`li`传入页面的`html`中 * 将此函数抽象出来,以方便以后实现同样类似的操作,将一个数组中的元素动态添加到页面的一个节点中 ## 清除缓存 ```Javascript // 在关闭浏览器时或者刷新页面时清除缓存 window.onbeforeunload = function (e) { localStorage.removeItem('items'); e.returnValue=true; //弹出提示框阻止onunload事件的运行 }; ``` > onbeforeunload 事件在即将离开当前页面(刷新或关闭)时触发。该事件可用于弹出对话框,提示用户是继续浏览页面还是离开当前页面。 注意:当该事件返回的字符串(事前设置好的event.returnValue的值)不为null或者undefined时,弹出确认窗口让用户自行选择是否关闭当前页面。(换句话说就是使用event.returnValue可以阻止onunload卸载页面,因为它会出现一个提示窗口,让用户自己判断是否离开当前页)一些浏览器将该事件返回的字符串显示在弹出窗上。从Firefox 4、 Chrome 51、Opera 38 和Safari 9.1开始,通用确认信息代替事件返回的字符串。 * 有些时候,我们仅仅是为了练习`localStorage`的使用,并不想在浏览器中留下过多的缓存,那么这个方法就派上了用场。 * 当页面重新刷新或者关闭之前,执行`localStorage.removeItem('items’);`清除页面的缓存。 * **慎用**,尤其在生产环境中。 ## 整体代码架构 ```Javascript const addItems = document.querySelector('.add-items'); const itemsList = document.querySelector('.plates'); const items = JSON.parse(localStorage.getItem('items')) || []; function addItem(e) { e.preventDefault(); const text = this.querySelector('[name=item]').value; const item = { text, // ES6的简写形式 => text = text; done: false }; items.push(item); localStorage.setItem('items', JSON.stringify(items)); populateList(items, itemsList); this.reset(); // 添加完数据后,重置输入框 } function populateList(populates = [], place) { // 设置默认值,防止传参数出错的时候crash place.innerHTML = populates.map((populate, index) => { return `
  • `; //之所以用‘’空字符是因为如果用null的话,会出现在html中 }).join(''); // join()之后一定要加'',表示空字符,否则会加入逗号,造成错误 } function toggleDone(e) { // if(!e.target.nodeName.match('INPUT')) return; if (!e.target.matches('input')) return; // 跳过所有的input,只处理label const node = e.target; const index = node.dataset.index; items[index].done = !items[index].done; localStorage.setItem('items', JSON.stringify(items)); populateList(items, itemsList); } addItems.addEventListener('submit', addItem); itemsList.addEventListener('click', toggleDone); populateList(items, itemsList); ``` * 在页面加载的时候,先获取本地缓存的`items`,若存在就传给变量`items`,若第一次登录或者无`item`,初始化为空数组; * 在页面加载的时候先加载页面的所有`todolist`,执行一遍`populateList(items, itemsList);`函数即可。 ================================================ FILE: 15 - LocalStorage/demo.html ================================================ Document

    LOCAL TAPAS

    • Loading Tapasss...
    ================================================ FILE: 15 - LocalStorage/index.html ================================================ LocalStorage

    LOCAL TAPAS

    • Loading Tapasss...
    ================================================ FILE: 15 - LocalStorage/style.css ================================================ html { box-sizing: border-box; background:url('http://wes.io/hx9M/oh-la-la.jpg') center no-repeat; background-size:cover; min-height:100vh; display:flex; justify-content: center; align-items: center; text-align: center; font-family: Futura,"Trebuchet MS",Arial,sans-serif } *, *:before, *:after {box-sizing: inherit; } svg { fill:white; background: rgba(0,0,0,0.1); padding: 20px; border-radius: 50%; width:200px; margin-bottom: 50px; } .wrapper { padding: 20px; max-width: 350px; background: rgba(255,255,255,0.95); box-shadow: 0 0 0 10px rgba(0,0,0,0.1); } h2 { text-align: center; margin: 0; font-weight: 200; } .plates { margin: 0; padding: 0; text-align: left; list-style: none; } .plates li { border-bottom: 1px solid rgba(0,0,0,0.2); padding: 10px 0; font-weight: 100; display: flex; } .plates label { flex:1; cursor: pointer; } .plates input { display: none; } .plates input + label:before { content: '⬜️'; margin-right: 10px; } .plates input:checked + label:before { content: '☑'; } .add-items { margin-top: 20px; } .add-items input { padding:10px; outline:0; border:1px solid rgba(0,0,0,0.1); } ================================================ FILE: 16 - 移动鼠标让字体呈现彩虹效果/README.md ================================================ # Day16 - 鼠标移动让文字出现🌈效果中文指南 ## 效果图 ![](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) 鼠标移动时,元素的字体阴影随着鼠标移动的方向发生改变,达到字体阴影随着鼠标一起走的效果。 ## 基础知识 #### text-shadow `text-shadow: h-shadow v-shadow blur color; ` `none`:无阴影 ``①:第1个长度值用来设置对象的阴影水平偏移值。可以为负值 ``②:第2个长度值用来设置对象的阴影垂直偏移值。可以为负值 ``③:如果提供了第3个长度值则用来设置对象的阴影模糊值。不允许负值 ``:设置对象的阴影的颜色。 #### 解构赋值 > [参考文档](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment) 解构赋值(destructuring assignment)语法是一个Javascript表达式,它使得从数组或者对象中提取数据赋值给不同的变量成为可能。 ```js let a, b, rest; /* array 解构赋值 */ [a, b] = [1, 2]; console.log(a); // 1 console.log(b); // 2 [a, b, ...rest] = [1, 2, 3, 4, 5]; console.log(a); // 1 console.log(b); // 2 console.log(rest); // [3, 4, 5] /* object 解构赋值:这里的小括号不能掉 */ ({a, b} = {a:1, b:2}); console.log(a); // 1 console.log(b); // 2 /* object & ...rest 解构赋值 */ ({a, b, ...rest} = {a:1, b:2, c:3, d:4}); // {a: 1, b: 2, c: 3, d: 4} rest; // {c: 3, d: 4} ``` #### MouseEvent ![](http://om1c35wrq.bkt.clouddn.com/D6300A0F-CD68-4CE2-AEB7-22DF2CA6FF3F.png) [https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) [clientX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientX) 设置或获取鼠标指针位置相对于当前窗口的 x 坐标,其中客户区域不包括窗口自身的控件和滚动条。 [clientY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/clientY) 设置或获取鼠标指针位置相对于当前窗口的 y 坐标,其中客户区域不包括窗口自身的控件和滚动条。 [offsetX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetX) 设置或获取鼠标指针位置相对于触发事件的对象的 x 坐标。 [offsetY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/offsetY) 设置或获取鼠标指针位置相对于触发事件的对象的 y 坐标。 [screenX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenX) 设置或获取获取鼠标指针位置相对于用户屏幕的 x 坐标。 [screenY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/screenY) 设置或获取鼠标指针位置相对于用户屏幕的 y 坐标。 [x](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/x) 设置或获取鼠标指针位置相对于父文档的 x 像素坐标(亦即相对于当前窗口)。 [y](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/y) 设置或获取鼠标指针位置相对于父文档的 y 像素坐标(亦即相对于当前窗口)。 [pageX](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageX) 设置或获取指针位置相对于整个文档的x坐标 [pageY](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent/pageY) 设置或获取指针位置相对于整个文档的y坐标 #### 页面元素offset的几个属性示例 * HTMLElement.offsetParent:是一个只读属性,指向最近的包含该元素的定位元素.如果没有定位的元素,则 offsetParent 为最近的 table 元素对象或根元素(标准模式下为 html;quirks 模式下为 body)。当元素的 style.display 设置为 "none" 时,offsetParent 返回 null。 offsetParent 很有用,因为 _offsetTop_ 和 _offsetLeft_ 都是相对于其内边距边界的。 * HTMLElement.offsetTop:指的是当前元素到其offsetParent指向元素的__上边距__的距离。 * HTMLElement.offsetLeft:指的是当前元素到其offsetParent指向元素的__左边距__的距离。 * HTMLElement.offsetHeight:指的是当前元素的__高度__,包含__content,padding,border__的高度值,但不包括__margin__的值。 * HTMLElement.offsetWidth:指的是当前元素的__宽度__,包含__content,padding,border__的高度值,但不包括__margin__的值。 ## js代码 ```javascript const hero = document.querySelector('.hero'); const text = hero.querySelector('h1'); const walk = 40; // 鼠标左右移动共移动的距离 function draw(e){ const { offsetWidth: width, offsetHeight: height} = hero; let { offsetX: x, offsetY: y} = e; // 使鼠标移动到中间元素上,x、y的值连续变化 if(e.target !== this){ // if(e.target == text){ x = x + e.target.offsetLeft; y = y + e.target.offsetTop; } // const xaisx = (x/width*walk)-(walk/2); // const yaisx = (y/height*walk)-(walk/2); const xaisx = Math.floor((x/width*walk)-(walk/2)); const yaisx = Math.floor((y/height*walk)-(walk/2)); text.style.textShadow = ` ${xaisx}px ${yaisx * -1}px 2px rgba(0,255,0,0.7), ${xaisx * -1}px ${yaisx}px 2px rgba(255,0,0,0.7), ${yaisx}px ${xaisx * -1}px 2px rgba(188,188,188,0.7), ${yaisx * -1}px ${xaisx}px 2px rgba(0,0,255,0.7) `; // 多写几个就有了霓虹灯的效果 } hero.addEventListener('mousemove',draw); ``` * 分别获取到鼠标所在位置相对于页面左侧和顶端的距离,将这两个距离映射为自己想要移动的距离上(`walk`); * 其中当鼠标移动中间的文字上的时候,由于`e.target`变化了,所以造成x的值不连续,因此需要监测`e.target`的值,判断是否指在了文字上; * 为元素设置字体阴影,text-shadow样式,也可以设置多个,达到类似霓虹灯的效果; * 对元素添加`mousemove`事件。 ================================================ FILE: 16 - 移动鼠标让字体呈现彩虹效果/index.html ================================================ Mouse Shadow

    🔥liyc1215

    ================================================ FILE: 17 - 数组排序/README.md ================================================ ## Day17 - Sort Without Articles(无冠词排序) ### 效果图 ![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/17%20-%20%E6%95%B0%E7%BB%84%E6%8E%92%E5%BA%8F/show.PNG) ##### 实现效果:对列表中列表项内容进行无冠词排序,在排序时不考虑a,an,the(大小写)的影响,点击ascending按钮进行升序,按下descending进行降序。 ### 源码分析 ```CSS body{ background: url("https://source.unsplash.com/nDqA4d5NL0k/2000x2000"); background-size:cover; padding:50px; display:flex; flex-direction: column; /*这里是将ul,和div进行垂直居中排列,二者相对位置是上下,不加的话相对位置水平*/ align-items:center; min-height:100vh; } ``` ```HTML
      ``` > 这里给两个按钮进行命名,用以区分两个按钮,在进行CSS布局时使用`flex-direction:column`将ul和div处于竖直布局。 ```JS const button=[...document.querySelectorAll("button")];//使用扩展运算符,这里是打散,将伪数组转化为真正的数组 button.forEach(item=>item.addEventListener("click",paixu)); ``` > 这里取得所有的按钮元素,并给每个按钮添加一个鼠标点击事件,注意项:伪元素转换为真正元素,可以使用扩展运算符,也可以使用`Array.form()`. 排序函数: ```JS //用sort比较函数以及del函数将每一项去除完冠词后进行比较,然后根据比较条件的正负,对原来项进行排序,sort会改变原数组 function paixu(e){ let flag=this.getAttribute("name");//使用name值对相同元素进行区别 if(flag=="ascending"){ bands.sort((a,b)=>del(a)>del(b)?1:-1); }else{ bands.sort((a,b)=>del(a)>del(b)?-1:1); } show(bands,bandsele); } ``` > 这里的思路:刚开始使用的是两个函数,点击不同的按钮跳转到各自的事件处理函数上去。但是写完发现这两个函数及其相似,过于冗余,使用了name属性,然后当点击不同的按钮时,获取各自的name值,然后根据name值来判断点击的是哪一个按钮,来处理升序还是降序。 这里涉及的知识点: - 1.使用`get.Attribute()`来获取属性值,但是具体获取那个元素的,这里使用了事件项event,这里的this就是e.target:表示当前元素。 - 2.sort数组的排序函数,根据判断程序来判断条件的正负,正的升序,负的降序,会改变原来的数组,所以,在最后有个显示的语句show(bands,bandsele); 删去冠词的函数: ```JS // 取出冠词a,an,the的函数,返回一个新的字符串 function del(item){ return item.replace(/^(a|an|the)\s{1}/ig,''); } ``` > \s{1}表示有一个空格,replace返回的事一个新字符串。 列表项显示函数: ```JS //将数组中的元素进行显示在指定列表中的函数,遍历整个数组元素,然后将返回的元素进行拼接整体显示在innerHTML function show(arr,place){ place.innerHTML=arr.map(item=>{return `
    • ${item}
    • `}).join(''); } ``` > 这个函数相当实用,map函数返回一个新的数组。 ================================================ FILE: 17 - 数组排序/index.html ================================================ 无冠词排序
        ================================================ FILE: 18 - Day18 - Reduce、Map混合使用计算时分秒/README.md ================================================ ## Day18 - Reduce、Map混合使用计算时分秒 ### 效果图 ![](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) 第18天挑战的内容主要是如何将一系列的`data-time`加起来,最终计算总时间,总时间用时分秒显示。 挑战升级:点击显示时长按钮将每个video的时间显示出来,将选中的videos进行总时长的计算。 ### 代码解析 css页面布局和示例17:无冠词排序基本一致 主要分析JavaScript代码: ```JS ``` > 主要内容就是两部分: 1、获得各项的data-time值,然后进行累加 2、根据总秒数进行时分秒的计算 `let [mins,seconds] = datatime.split(":").map(item=>parseFloat(item));` 解构赋值:这里是将时和秒数进行切割形成新的数组,然后对数组中的每一项用parseFloat()进行字符串转换为数值,然后进行解构赋值,例如:[mins,seconds]=[03,58];这里将03赋值给mins,将58赋值给seconds. `input.value=Math.floor(sum/3600)+"个小时"+Math.floor((sum%3600)/60)+"分"+sum%60+"秒";` 总秒数除以3600得到的商就是小时个数,然后进行向下取整; 总秒数对3600取余,实际得到的是出去小时后还剩下多少秒,然后对剩下的秒数除以60(一分钟60秒)得到有多少分钟; 总秒数对60取余:即是说总秒数中把能凑成60的,也就是说能组成分钟的除去后还剩多少秒,就是要求的秒数。 ================================================ FILE: 18 - Day18 - Reduce、Map混合使用计算时分秒/index.html ================================================ Adding Up Times with Reduce
        • Video 1
        • Video 2
        • Video 3
        • Video 4
        • Video 5
        • Video 6
        • Video 7
        • Video 8
        • Video 9
        • Video 10
        • Video 11
        • Video 12
        • Video 13
        • Video 14
        • Video 15
        • Video 16
        • Video 17
        • Video 18
        • Video 19
        • Video 20
        • Video 21
        • Video 22
        • Video 23
        • Video 24
        • Video 25
        • Video 26
        • Video 27
        • Video 28
        • Video 29
        • Video 30
        • Video 31
        • Video 32
        • Video 33
        • Video 34
        • Video 35
        • Video 36
        • Video 37
        • Video 38
        • Video 39
        • Video 40
        • Video 41
        • Video 42
        • Video 43
        • Video 44
        • Video 45
        • Video 46
        • Video 47
        • Video 48
        • Video 49
        • Video 50
        • Video 51
        • Video 52
        • Video 53
        • Video 54
        • Video 55
        • Video 56
        • Video 57
        • Video 58
        ================================================ FILE: 19 - Webcam Fun/README.md ================================================ # Day19 - 摄像、拍照,滤镜中文指南 ## 效果图 ![](http://om1c35wrq.bkt.clouddn.com/day19.gif) ![](http://om1c35wrq.bkt.clouddn.com/day19-red%E6%95%88%E6%9E%9C%E5%9B%BE.png) JS30天第19天挑战的是如何调用摄像头录像、播放,如何捕捉视频将其绘制`canvas`,还有拍照,以及滤镜的制作。 ## 运行项目 1. 通过`npm install`安装依赖包 2. 通过`npm start`启动服务器 3. 浏览器直接访问`http://localhost:3000` ```js liyuechun:19 - Webcam Fun yuechunli$ pwd /Users/liyuechun/Documents/js30/JavaScript30-liyuechun/19 - Webcam Fun liyuechun:19 - Webcam Fun yuechunli$ ls README.md package-lock.json scripts.js index.html package.json style.css liyuechun:19 - Webcam Fun yuechunli$ npm install > fsevents@1.1.2 install /Users/liyuechun/Documents/js30/JavaScript30-liyuechun/19 - Webcam Fun/node_modules/fsevents > node install [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 Pass --update-binary to reinstall or --build-from-source to recompile npm WARN gum@1.0.0 No repository field. added 411 packages in 5.921s liyuechun:19 - Webcam Fun yuechunli$ npm start > gum@1.0.0 start /Users/liyuechun/Documents/js30/JavaScript30-liyuechun/19 - Webcam Fun > browser-sync start --server --files '*.css, *.html, *.js' [Browsersync] Access URLs: -------------------------------------- Local: http://localhost:3000 External: http://192.168.1.116:3000 -------------------------------------- UI: http://localhost:3001 UI External: http://192.168.1.116:3001 -------------------------------------- [Browsersync] Serving files from: ./ [Browsersync] Watching files... ``` ## 主要思路 * 获取到浏览器的摄像头的影像 * 将影像的记录导出到canvas中 * 通过获取canvas中的图片信息,对图片添加滤镜 ## Browsersync #### 项目结构 ![](http://om1c35wrq.bkt.clouddn.com/Snip20170809_21.png) #### 了解Browsersync 省时的浏览器同步测试工具,Browsersync能让浏览器实时、快速响应您的文件更改(html、js、css、sass、less等)并自动刷新页面。更重要的是`Browsersync可以同时在PC、平板、手机`等设备下进项调试。您可以想象一下:“假设您的桌子上有pc、ipad、iphone、android等设备,同时打开了您需要调试的页面,当您使用browsersync后,您的任何一次代码保存,以上的设备都会同时显示您的改动”。无论您是前端还是后端工程师,使用它将提高您30%的工作效率。 ![](http://om1c35wrq.bkt.clouddn.com/sync-demo.gif) 有了它,您不用在多个浏览器、多个设备间来回切换,频繁的刷新页面。更神奇的是您在一个浏览器中滚动页面、点击等行为也会同步到其他浏览器和设备中,这一切还可以通过可视化界面来控制。 ![](http://om1c35wrq.bkt.clouddn.com/scroll-demo.gif) ## 获取影像 ```javascript function getVideo(){ navigator.mediaDevices.getUserMedia({video:true,audio:false}) .then(videostream => { console.log(videostream); video.src = URL.createObjectURL(videostream); // 创建url(creates a URL for the specified object) video.play(); }) .catch((err) => { console.error('OH,Don\'t have permission to use your local cam!',err); }); } ``` - [MediaDevices.getUserMedia()](https://developer.mozilla.org/zh-CN/docs/Web/API/MediaDevices/getUserMedia) > `MediaDevices.getUserMedia()`方法提示用户允许使用一个视频和/或一个音频输入设备,例如相机或屏幕共享和/或麦克风。如果用户给予许可,就返回一个`Promise`对象,`MediaStream`对象作为此`Promise`对象的`Resolved`[成功]状态的回调函数参数,相应的,如果用户拒绝了许可,或者没有媒体可用的情况下,`PermissionDeniedError`或者`NotFoundError`作为此`Promise`的`Rejected`[失败]状态的回调函数参数。注意,由于用户不会被要求必须作出允许或者拒绝的选择,所以返回的`Promise`对象可能既不会触发`resolve`也不会触发`reject`。 - [URL.createObjectURL()](https://developer.mozilla.org/zh-CN/docs/Web/API/URL/createObjectURL)。 > `URL.createObjectURL()` 静态方法会创建一个`DOMString`,其中包含一个表示参数中给出的对象的`URL`。这个`URL` 的生命周期和创建它的窗口中的`document` 绑定。这个新的`URL`对象表示指定的`File` 对象或`Blob` 对象。 ## canvas绘图 ```javascript function printToCanvas(){ let width = video.videoWidth; let height = video.videoHeight; canvas.height = height; canvas.width = width; // 勿忘:设置canvas的宽和高 console.log(width,height); return setInterval(() => { ctx.drawImage(video,0,0,width,height); // get the image data let imagedata = ctx.getImageData(0,0,width,height); // console.log(imagedata.data); // mess the image data // imagedata = redEffect(imagedata); // imagedata = rgbsplit(imagedata); // ctx.globalAlpha = 0.2; imagedata = greenScreen(imagedata); // put the image data back ctx.putImageData(imagedata,0,0); },16); } ``` * `ctx.drawImage()` >它能够将当前的视频流(video)中的一帧画在canvas中。 - [getImageData()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/getImageData) > `ctx.getImageData()`返回一个ImageData对象,用来描述canvas区域隐含的像素数据,这个区域通过矩形表示,起始点为(sx, sy)、宽为sw、高为sh。 - [putImageData()](https://developer.mozilla.org/zh-CN/docs/Web/API/CanvasRenderingContext2D/putImageData) > `ctx.putImageData()`:该方法是 Canvas 2D API 将数据从已有的 ImageData 对象绘制到位图的方法。 如果提供了脏矩形,只能绘制矩形的像素。 - imagedata信息 > imagedata中有大量的数据,其中分别代表了图片的颜色信息,分别为red,green,blue,alpha的值,因此我们可以同添加自定义滤镜,通过改变颜色的rgba的值,控制页面的效果。 ## 摄像记录导出到canvas中 ```javascript function takePhoto(){ // 播放音效 snap.currentTime = 0; snap.play(); // 获取图像数据 let data = canvas.toDataURL('image/jpeg'); // console.log(data); let link = document.createElement('a'); link.href = data; link.setAttribute('downlond','handsome'); link.innerHTML = `handsome` strip.insertBefore(link,strip.firstChild); } ``` - 在没次点击照相的时候,都要求播一遍音效,并且为了模拟现实情况,我们在用户点击时,设置当前的播放时间为0,再播放音效。 - [canvas.toDataURL('image/jpeg');](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLCanvasElement/toDataURL)方法返回一个包含图片展示的 data URI 。可以使用 type 参数其类型,默认为 PNG 格式。图片的分辨率为96dpi。 - 接下来新建一个a元素,设置其href的值为data。在插入在文档中。实现截图成功的效果。 ## 自定义滤镜 ```javascript // 红色特效滤镜 function redEffect(imagedata){ for(let i = 0;i { levels[input.name] = input.value; }); for (i = 0; i < pixels.data.length; i = i + 4) { red = imagedata.data[i + 0]; green = imagedata.data[i + 1]; blue = imagedata.data[i + 2]; alpha = imagedata.data[i + 3]; if (red >= levels.rmin && green >= levels.gmin && blue >= levels.bmin && red <= levels.rmax && green <= levels.gmax && blue <= levels.bmax) { // take it out! imagedata.data[i + 3] = 0; } } return imagedata; } ``` 这部分主要定义了三个滤镜,由于我们通过`ctx.getImageData`可以获取到页面颜色的rgba的值,因此我们添加滤镜的原理也是这样,通过循环改变一张图片中的所有rgba的值即可。 ================================================ FILE: 19 - Webcam Fun/index.html ================================================ Get User Media Code Along!


        ================================================ FILE: 19 - Webcam Fun/package.json ================================================ { "name": "gum", "version": "1.0.0", "description": "", "main": "scripts.js", "scripts": { "start": "browser-sync start --server --files '*.css, *.html, *.js'" }, "author": "", "license": "ISC", "devDependencies": { "browser-sync": "^2.12.5" } } ================================================ FILE: 19 - Webcam Fun/scripts.js ================================================ const video = document.querySelector('.player'); const canvas = document.querySelector('.photo'); const ctx = canvas.getContext('2d'); const strip = document.querySelector('.strip'); const snap = document.querySelector('.snap'); function getVideo(){ navigator.mediaDevices.getUserMedia({video:true,audio:false}) .then(videostream => { console.log(videostream); video.src = URL.createObjectURL(videostream); // 创建url(creates a URL for the specified object) video.play(); }) .catch((err) => { console.error('OH,Don\'t have permission to use your local cam!',err); }); } function printToCanvas(){ let width = video.videoWidth; let height = video.videoHeight; canvas.height = height; canvas.width = width; // 勿忘:设置canvas的宽和高 console.log(width,height); return setInterval(() => { ctx.drawImage(video,0,0,width,height); // get the image data let imagedata = ctx.getImageData(0,0,width,height); // console.log(imagedata.data); // mess the image data imagedata = redEffect(imagedata); // imagedata = rgbsplit(imagedata); // ctx.globalAlpha = 0.2; // imagedata = greenScreen(imagedata); // put the image data back ctx.putImageData(imagedata,0,0); },16); } function takePhoto(){ // 播放音效 snap.currentTime = 0; snap.play(); // 获取图像数据 let data = canvas.toDataURL('image/jpeg'); // console.log(data); let link = document.createElement('a'); link.href = data; link.setAttribute('downlond','handsome'); link.innerHTML = `handsome` strip.insertBefore(link,strip.firstChild); } // 红色特效滤镜 function redEffect(imagedata){ for(let i = 0;i { levels[input.name] = input.value; }); for (i = 0; i < pixels.data.length; i = i + 4) { red = imagedata.data[i + 0]; green = imagedata.data[i + 1]; blue = imagedata.data[i + 2]; alpha = imagedata.data[i + 3]; if (red >= levels.rmin && green >= levels.gmin && blue >= levels.bmin && red <= levels.rmax && green <= levels.gmax && blue <= levels.bmax) { // take it out! imagedata.data[i + 3] = 0; } } return pixels; } getVideo(); video.addEventListener('canplay',printToCanvas); ================================================ FILE: 19 - Webcam Fun/style.css ================================================ html { box-sizing: border-box; } *, *:before, *:after { box-sizing: inherit; } html { font-size: 10px; background:#ffc600; } .photobooth { background:white; max-width:150rem; margin: 2rem auto; border-radius:2px; } /*clearfix*/ .photobooth:after { content: ''; display: block; clear: both; } .photo { width:100%; float:left; } .player { position: absolute; top:20px; right: 20px; width:200px; } /* Strip! */ .strip { padding:2rem; } .strip img { width:100px; overflow-x: scroll; padding:0.8rem 0.8rem 2.5rem 0.8rem; box-shadow:0 0 3px rgba(0,0,0,0.2); background:white; } .strip a:nth-child(5n+1) img { transform: rotate(10deg); } .strip a:nth-child(5n+2) img { transform: rotate(-2deg); } .strip a:nth-child(5n+3) img { transform: rotate(8deg); } .strip a:nth-child(5n+4) img { transform: rotate(-11deg); } .strip a:nth-child(5n+5) img { transform: rotate(12deg); } ================================================ FILE: 20 - Speech Detection/README.md ================================================ # Day20 - 语言识别系统中文指南 > 本文出自:[春哥个人博客:http://www.liyuechun.org](http://liyuechun.org) > 作者:©[黎跃春-追时间的人](http://weibo.com/mobiledevelopment) > 简介:[JavaScript30](https://javascript30.com) 是 [Wes Bos](https://github.com/wesbos) 推出的一个 30 天挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。目的是帮助人们用纯 JavaScript 来写东西,不借助框架和库,也不使用编译器和引用。现在你看到的是这系列指南的第 20 篇。完整中文版指南及视频教程在 [从零到壹全栈部落](http://kongyixueyuan.com/course/4188)。 ## 运行项目 ```js $ npm install $ npm start ``` 浏览器打开`http://localhost:3000/index-FINISHED.html` 效果图如下: ![](http://om1c35wrq.bkt.clouddn.com/Snip20170811_1.png) ![](http://om1c35wrq.bkt.clouddn.com/day20-100.gif) ## 程序源码 ### HTML代码 ```js html css Speech Detection
        ``` ### JS代码 ```js ``` #### JS实现思路 * 新建一个语音识别的对象 * 开启该语音识别对象的识别服务 * 监听`result`事件,实时获取语音输入内容 * 监听`end`事件,当结束时再次开启语音识别,使其持续监听 #### JS源码解析 - [SpeechRecognition参考文档](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition) - 其中监听`result`事件,根据事件返回值获取到语音输入的内容 ![](http://om1c35wrq.bkt.clouddn.com/Snip20170811_2.png) 可以看到`transcript`中保存的是语音输入的内容。其中可以看到还有一个属性为`confidence`,代表这段话是别的精度,越大正确率越高。 -`SpeechRecognition`属性 ```js var recognition = new SpeechRecognition(); recognition.continuous = false; recognition.lang = 'en-US'; recognition.interimResults = false; ... ``` 第20天的内容就到这里,主要学习`SpeechRecognition`相关属性的使用。 ## 源码下载 [Github Source Code](https://github.com/liyuechun/JavaScript30-liyuechun) |全栈部落|区块链部落| |:---------:|:------:| |![](http://orhm8wuhd.bkt.clouddn.com/quanzhanbuluo100.jpeg)|![](http://orhm8wuhd.bkt.clouddn.com/qukuailian100.jpg)| ================================================ FILE: 20 - Speech Detection/index-FINISHED.html ================================================ Speech Detection
        ================================================ FILE: 20 - Speech Detection/index-START.html ================================================ Speech Detection
        ================================================ FILE: 20 - Speech Detection/package.json ================================================ { "name": "gum", "version": "1.0.0", "description": "", "main": "scripts.js", "scripts": { "start": "browser-sync start --directory --server --files '*.css, *.html, *.js'" }, "author": "", "license": "ISC", "devDependencies": { "browser-sync": "^2.12.5" } } ================================================ FILE: 21 - Geolocation/README.md ================================================ # Day21 - Geolocation 中文指南 ### 涉及知识点主要是geolocation,这里不做详细介绍,[具体内容见我总结的博客,请戳我!](https://blog.csdn.net/qq_39207948/article/details/88081604) ## 运行项目 ```js liyuechun:21 - Geolocation yuechunli$ npm install ...... liyuechun:21 - Geolocation yuechunli$ npm start > gum@1.0.0 start /Users/liyuechun/Documents/js30/JavaScript30-liyuechun/21 - Geolocation > browser-sync start --directory --server --files '*.css, *.html, *.js' --https [Browsersync] Access URLs: ------------------------------------- Local: https://localhost:3000 External: https://192.168.1.7:3000 ------------------------------------- UI: http://localhost:3001 UI External: http://192.168.1.7:3001 ------------------------------------- [Browsersync] Serving files from: ./ [Browsersync] Watching files... ``` ## 效果图 ![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/21%20-%20Geolocation/show.PNG) 第21天的目的是练习`NavigatorGeolocation.geolocation`这一webAPI的使用,通过使用此API可以访问到设备的位置信息。这允许网站或应用根据用户的位置提供个性化结果。 ## UI源码 ```html Document

        0 KM/H

        ``` ![](http://om1c35wrq.bkt.clouddn.com/WX20170813-114518@2x.png) ## JS源码 ```js ``` ================================================ FILE: 21 - Geolocation/index.html ================================================ Document

        0 KM/H

        ================================================ FILE: 21 - Geolocation/package.json ================================================ { "name": "gum", "version": "1.0.0", "description": "", "main": "scripts.js", "scripts": { "start": "browser-sync start --directory --server --files '*.css, *.html, *.js' --https" }, "author": "", "license": "ISC", "devDependencies": { "browser-sync": "^2.12.5" } } ================================================ FILE: 22 - Follow Along Link Highlighter/README.md ================================================ # Day22 - 鼠标锚点动画生成指南 ## 效果图 ![](https://github.com/SUNNERCMS/30daysJavascript/blob/master/22%20-%20Follow%20Along%20Link%20Highlighter/SHOWGIF.gif) 第22天的练习是一个动画练习,当鼠标移动到锚点处,会有一个白色的色块移动到当前锚点所在的位置。演示图如下所示: ### 解决思路: 实际上是通过一个背景颜色Wie白色的小块进行覆盖导航元素这个白色小块的宽高及位置有getBoundingClientRect来获取,鼠标进入不同的元素就获取该元素的宽高和位置数据,然后赋值给这个白色小块,则白色小块定位到该元素位置处,作为背景来使用。 ## JS源码 ```js ``` ## 代码解析 - 通过HTML源码我们不难发现,所有锚点都是由`a`标签组成,所以在`js`代码中我们首先先获取所有的`a`标签对象,返回一个伪数组,用扩展运算符将其真正数组化,将其存储到`triggers`变量中,然后给每个a标签添加鼠标进入监听事件。 ```js let A=[...document.querySelectorAll("a")]; A.forEach(item=>item.addEventListener("mouseenter",addHightLight)); ``` - 在效果图中高亮状态的小块其实就是一个`span`标签,在JS代码中创建了一个`span`标签,并且为其添加了一个`highlight`的`class`,由于hightlight的样式中 是绝对定位,并且top和left的值均为0;那么在初始化时是位于左上角的,通过display进行隐藏,然后在改变之后在进行显示,中间加上过渡效果。 ```js //创建类名为hightLight的白色小块 --> let SPAN=document.createElement("span"); SPAN.setAttribute("class","hightlight");//或者使用SPAN.classList.add("hightlight"); document.body.appendChild(SPAN); SPAN.style.display="none"; ``` - `getBoundingClientRect()` [getBoundingClientRect](https://blog.csdn.net/qq_39207948/article/details/88147479) `Element.getBoundingClientRect()`方法返回元素的大小及其相对于视口的位置。 **语法:** ```js rectObject = object.getBoundingClientRect(); ``` **值:** 返回值是一个 [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 边框集合 。 **DOMRect属性表:** |属性|类型|描述| |:-----------|:-----------:|:-----------| |bottom|float|Y 轴,相对于视口原点(viewport origin)矩形盒子的底部。**只读**。 | |height|float|矩形盒子的高度(等同于 bottom 减 top)。**只读**。| |left|float|X 轴,相对于视口原点(viewport origin)矩形盒子的左侧。**只读**。 | |right|float|X 轴,相对于视口原点(viewport origin)矩形盒子的右侧。**只读**。 | |top|float|Y 轴,相对于视口原点(viewport origin)矩形盒子的顶部。**只读**。| |width|float|矩形盒子的宽度(等同于 right 减 left)。**只读**。 | |x|float|X轴横坐标,矩形盒子左边相对于视口原点(viewport origin)的距离。**只读**。| |y|float|Y轴纵坐标,矩形盒子顶部相对于视口原点(viewport origin)的距离。**只读**。| `DOMRect` 对象包含了一组用于描述边框的只读属性——left、top、right和bottom,单位为像素。除了 width 和 height 外的属性都是相对于视口的左上角位置而言的。 ![](http://om1c35wrq.bkt.clouddn.com/day22-rect.png) 空边框盒(译者注:没有内容的边框)会被忽略。如果所有的元素边框都是空边框,那么这个矩形给该元素返回的 width、height 值为0,left、top值为第一个css盒子(按内容顺序)的top-left值。 当计算边界矩形时,会考虑视口区域(或其他可滚动元素)内的滚动操作,也就是说,当滚动位置发生了改变,top和left属性值就会随之立即发生变化(因此,它们的值是相对于视口的,而不是绝对的)。如果不希望属性值随视口变化,那么只要给top、left属性值加上当前的滚动位置(通过window.scrollX和window.scrollY),这样就可以获取与当前的滚动位置无关的常量值。 - `highlightLink`方法 ```js function addHightLight(){ let RECT=this.getBoundingClientRect(); let rects={ width:RECT.width, height:RECT.height, left:RECT.left+window.scrollX, top:RECT.top+window.scrollY } // SPAN.style=`width:${rects.width},height:${rects.height},left:${rects.left},top:${rects.top}`; // SPAN.style.top= // SPAN.style.width = `${rects.width}px`; // SPAN.style.height = `${rects.height}px`; // // SPAN.style.transform = `translate(${rects.left}px, ${rects.top}px)`;用了位置移动,等价于下面的这两条语句 // SPAN.style.left = `${rects.left}px`; // SPAN.style.top = `${rects.top}px`; // SPAN.style.display="block"; // 采用cssText写法片段式样式改变,可以减少回流和重绘,上述的语句没改变一个样式都会触发回流和重绘。 SPAN.style.cssText=`width:${rects.width}px;height:${rects.height}px;left:${rects.left}px;top:${rects.top}px;display:block;`; } ``` > 给a元素添加高亮背景的函数。获取到当前进入的a元素,然后将该元素的top\left\width\height用一个对象进行保存。然后将span的白色小块的样式位置进行改变,移动到鼠标进入的这个元素。 -