├── 01 pianoHit ├── README.md ├── drumback.jpg ├── index-VUE.html ├── index.html ├── pianoback.jpg ├── sounds │ ├── 1.MP3 │ ├── 2.MP3 │ ├── 3.MP3 │ ├── 4.MP3 │ ├── 5.MP3 │ ├── 6.MP3 │ ├── 7.MP3 │ ├── boom.wav │ ├── clap.wav │ ├── hihat.wav │ ├── kick.wav │ ├── openhat.wav │ ├── ride.wav │ ├── snare.wav │ ├── tink.wav │ └── tom.wav └── style.css ├── 02 realTimeClock ├── README.md ├── index-VUE.html ├── index.html └── style.css ├── 03 imageProcessionwithJS ├── README.md ├── index-VUE.html └── index.html ├── 04 arrayOperation ├── README.md └── index.html ├── 05 flexPanel ├── README.md ├── index-VUE.html └── index.html ├── 06 AjaxTypeAhead ├── README.md ├── index-VUE.html └── index.html ├── 08 canvas ├── README.md ├── index-VUE.html └── index.html ├── 10 goList ├── README.md ├── index-VUE.html └── index.html ├── 11 videoPlayer ├── 652333414.mp4 ├── README.md ├── index-VUE.html ├── index.html ├── script.js └── style.css ├── 12 secretCode ├── README.md └── index.html ├── 13 slideinonScroll ├── README.md └── index.html ├── 14 JSreferenceVScopy ├── README.md └── index.html ├── 15 localStorage ├── README.md ├── index.html ├── oh-la-la.jpeg └── style.css ├── 16 mouseMoveShadow ├── README.md └── index.html ├── 17 sortWithoutArticles ├── README.md └── index.html ├── 18 timeWithReduce ├── README.md └── index.html ├── 19 webCamFun ├── README.md ├── index.html ├── script.js ├── snap.mp3 └── style.css ├── 20 speechDetection ├── README.md └── index.html ├── 21 geoLocation ├── README.md └── index.html ├── 22 linkHighlighter ├── README.md └── index.html ├── 23 speechSynthesis ├── README.md └── index.html ├── 24 stickyNav ├── README.md ├── index.html └── style.css ├── 25 eventCapture ├── README.md └── index.html ├── 26 stripAlongNav ├── README.md ├── index.html └── style.css ├── 27 clickAndDrag ├── README.md ├── index.html └── style.css ├── 28 VideoSpeedController ├── README.md ├── index.html └── style.css ├── 29 CountdownTimer ├── README.md ├── index.html ├── scripts-FINISHED.js ├── scripts-START.js └── style.css ├── 30 WhackAMole ├── dirt.svg ├── index-FINISHED.html ├── index-START.html ├── index-VUE.html ├── mole.svg └── style.css └── README.md /01 pianoHit/drumback.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/drumback.jpg -------------------------------------------------------------------------------- /01 pianoHit/pianoback.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/pianoback.jpg -------------------------------------------------------------------------------- /01 pianoHit/sounds/1.MP3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/1.MP3 -------------------------------------------------------------------------------- /01 pianoHit/sounds/2.MP3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/2.MP3 -------------------------------------------------------------------------------- /01 pianoHit/sounds/3.MP3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/3.MP3 -------------------------------------------------------------------------------- /01 pianoHit/sounds/4.MP3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/4.MP3 -------------------------------------------------------------------------------- /01 pianoHit/sounds/5.MP3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/5.MP3 -------------------------------------------------------------------------------- /01 pianoHit/sounds/6.MP3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/6.MP3 -------------------------------------------------------------------------------- /01 pianoHit/sounds/7.MP3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/7.MP3 -------------------------------------------------------------------------------- /01 pianoHit/sounds/boom.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/boom.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/clap.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/clap.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/hihat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/hihat.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/kick.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/kick.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/openhat.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/openhat.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/ride.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/ride.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/snare.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/snare.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/tink.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/tink.wav -------------------------------------------------------------------------------- /01 pianoHit/sounds/tom.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/01 pianoHit/sounds/tom.wav -------------------------------------------------------------------------------- /01 pianoHit/style.css: -------------------------------------------------------------------------------- 1 | a{ 2 | text-decoration: none; 3 | color:black; 4 | } 5 | html { 6 | font-size: 10px; 7 | background: url('./drumback.jpg') bottom center; 8 | background-size: cover; 9 | /* 让背景图片不随滚轮缩放 */ 10 | background-attachment:fixed; 11 | } 12 | 13 | .drumKit,.pianoKit{ 14 | /* background-color: red; */ 15 | /* 在需要垂直居中的父元素上,设置display:flex和align-items:center。要求:父元素必须显示设置height值 */ 16 | display: flex; 17 | flex:1; 18 | min-height: 90vh; /*vh 就是当前屏幕可见高度的100% https://blog.csdn.net/weixin_42207975/article/details/107145439*/ 19 | align-items: center; /*子元素水平居中*/ 20 | justify-content: center; /*子元素垂直居中*/ 21 | 22 | } 23 | .key{ 24 | width: 90px; 25 | height: 80px; 26 | background: rgba(0,0,0,0.4); 27 | text-shadow: 0 0 .5rem black; 28 | margin-right: 10px; 29 | /* margin-top: 310px; */ 30 | text-align: center; 31 | border:1px solid rgba(255,255,255,0.2); 32 | border-radius: 5px; 33 | transition:all 0.07s ease; 34 | } 35 | kbd{ 36 | display: block; 37 | font-size: 30px; 38 | color:white; 39 | margin-bottom: 0px; 40 | margin-top: 10px; 41 | } 42 | .key-tune{ 43 | font-size: 1.2rem; 44 | text-transform: uppercase; 45 | letter-spacing: .1rem; 46 | color: #ffc600; 47 | } 48 | .playing{ 49 | transform: scale(1.1); 50 | border-color: #ffc600; 51 | box-shadow: 0 0 1rem #ffc600; 52 | } 53 | .switch{ 54 | width: 300px; 55 | height:30px; 56 | font-size: 20px; 57 | background:rgb(66,132,243,0.4); 58 | position: fixed; 59 | left:0; 60 | right:0; 61 | margin:0 auto; 62 | /* border-bottom:2px solid black; */ 63 | line-height: 30px; 64 | text-align: center; 65 | color:black; 66 | } 67 | 68 | .switch a:nth-child(1){ 69 | margin-right: 30px; 70 | 71 | } 72 | .switch .chosen{ 73 | color:white; 74 | } 75 | 76 | .notShow{ 77 | display: none; 78 | } 79 | -------------------------------------------------------------------------------- /02 realTimeClock/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Real time clock 02 3 | date: 2021-11-12 14:46:15 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | ### 引言 9 | 10 | 本文利用javascript写一个实时显示时间的时钟特效网页。 11 | 12 | 网址为(https://janice143.github.io/realTImeClock/) 13 | 14 | ### 正文 15 | 16 | #### 1网页布局与功能 17 | 18 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_Clock.png?raw=true) 19 | 20 | 网页主体为一个时钟,具有表盘(12个数字)和三个指针(时针、分针、秒针)。 21 | 22 | #### 2实现原理 23 | 24 | 一、 html代码 25 | 26 | 使用一个类名为clock为的div容器,里面包含时针.hour-hand,分针.minute-hand,秒针second-hand,以及12个数字。 27 | 28 | ```html 29 |
30 |
31 |
32 |
33 |
34 | 12 35 | 1 36 | 2 37 | 3 38 | 4 39 | 40 | 5 41 | 6 42 | 7 43 | 8 44 | 45 | 9 46 | 10 47 | 11 48 | 49 |
50 |
51 | ``` 52 | 53 | 二、css代码 54 | 55 | 1 先让时钟显示在页面的中部(垂直居中,水平居中),这可以用个在clock的上一级词main中设置flex容器。 56 | 57 | ```css 58 | #main{ 59 | 60 | display: flex; 61 | min-height: 100vh; 62 | align-items: center; 63 | justify-content: center; 64 | 65 | } 66 | ``` 67 | 68 | 2 时钟的表盘的样式 69 | 70 | ```css 71 | .clock{ 72 | width: 300px; 73 | height: 300px; 74 | border-radius: 300px; 75 | border: 20px solid white; 76 | position: relative; 77 | } 78 | ``` 79 | 80 | 3 指针的样式 81 | 82 | 指针旋转的特效是由transfrom:rotate(deg)实现的(本文这里是通过js代码后面再设置的)。transform-origin默认是50%,元素会绕着中间旋转,设置成100%后,元素绕着一端旋转。transition-timing-function是设置过渡的时间函数特效,不设置是默认均匀地过渡。 83 | 84 | ```css 85 | .hand{ 86 | width: 120px; 87 | height: 6px; 88 | background-color: blueviolet; 89 | position:absolute; 90 | top:148px; 91 | right: 148px; 92 | transform-origin: 100%; 93 | transition:all 0.05s; 94 | transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1); 95 | } 96 | .hour-hand{ 97 | width: 100px; 98 | } 99 | .second-hand{ 100 | height: 4px; 101 | } 102 | ``` 103 | 104 | 4 12个数字的样式 105 | 106 | 先给数字设置相对定位,相对于上一级.number。然后再给每个数字设置相应的top和left. 数字位置算法为: 107 | 108 | num=2; top=135*sin((num-3)*30/180*pi)+135; left=135*cos((num-3)*30/180*pi)+135 (num为1-12的数字) 109 | 110 | ```css 111 | .number{ 112 | width: 300px; 113 | height:300px; 114 | font-size: 15px; 115 | 116 | 117 | position:absolute; 118 | top:0px; 119 | right: 0px; 120 | } 121 | 122 | ``` 123 | 124 | 三、 javascript代码 125 | 126 | 分别获取时针、分钟、秒针的类名,然后通过当前时间给三个指针分配正确的旋转角度。 127 | 128 | 1 秒针: parseInt(second/60*360)+90; 129 | 130 | 2 分针: parseInt(minute/(60)*360+second/10)+90; 131 | 132 | 3 时针:parseInt(hour/(12)*360+minute/(60)*30)+90; 133 | 134 | +90度是因为设置指针css样式的时候,指针都在指在9点钟的位置,+90度可让指针从12点为起始点旋转。 135 | 136 | 设置为指针的角度后,利用定时器每隔一秒刷新指针的位置,这样就可以达到实时显示的效果。 137 | 138 | 当指针转弯一圈后,重新运行设置时间函数 setDate()重置指针的角度。 139 | 140 | ```javascript 141 | // 获取指针的transform样式,从而让其旋转 142 | const secondHand = document.querySelector('.second-hand'); 143 | const minuteHand = document.querySelector('.minute-hand'); 144 | const hourHand = document.querySelector('.hour-hand'); 145 | 146 | const audio = document.querySelector('audio'); 147 | // 获取当前时间,从时间里设置指针 148 | function setDate(){ 149 | const time = new Date(); 150 | const second = time.getSeconds(); 151 | const secondDeg = parseInt(second/60*360)+90; 152 | secondHand.style.transform = `rotate(${secondDeg}deg)`; 153 | 154 | const minute= time.getMinutes(); 155 | const minuteDeg = parseInt(minute/(60)*360+second/10)+90; 156 | minuteHand.style.transform = `rotate(${minuteDeg}deg)`; 157 | 158 | const hour= time.getHours(); 159 | const hourDeg = parseInt(hour/(12)*360+minute/(60)*30)+90; 160 | hourHand.style.transform = `rotate(${hourDeg}deg)`; 161 | 162 | } 163 | // 设置定时器美隔一秒时间进行刷新页面 164 | setInterval(setDate,1000); 165 | // 166 | setDate(); // 当指针转一圈后,重置度数 167 | 168 | ``` 169 | 170 | ### 总结 171 | 172 | 完整代码放在了[Github](https://github.com/janice143/realTImeClock)上,如果读者有兴趣,不妨试一试。 173 | 174 | -------------------------------------------------------------------------------- /02 realTimeClock/index-VUE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Real Time Clock 8 | 9 | 10 | 11 | 12 |
13 |
14 |
17 |
20 |
23 |
24 | {{num.text}} 28 |
29 |
30 |
31 | 78 | 79 | -------------------------------------------------------------------------------- /02 realTimeClock/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Real Time Clock 8 | 139 | 140 | 141 |
142 |
143 |
144 |
145 |
146 |
147 | 12 148 | 1 149 | 2 150 | 3 151 | 4 152 | 153 | 5 154 | 6 155 | 7 156 | 8 157 | 158 | 9 159 | 10 160 | 11 161 | 162 |
163 |
164 |
165 | 201 | 202 | 203 | 204 | 205 | -------------------------------------------------------------------------------- /02 realTimeClock/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | padding: 0; 4 | background-color: black; 5 | color:white; 6 | text-shadow: 0 0 .5rem black; 7 | } 8 | #app{ 9 | 10 | display: flex; 11 | min-height: 100vh; 12 | align-items: center; 13 | justify-content: center; 14 | 15 | } 16 | .clock{ 17 | width: 300px; 18 | height: 300px; 19 | border-radius: 300px; 20 | border: 20px solid white; 21 | position: relative; 22 | 23 | } 24 | .hand{ 25 | width: 120px; 26 | height: 6px; 27 | background-color: blueviolet; 28 | position:absolute; 29 | top:148px; 30 | right: 148px; 31 | 32 | transform-origin: 100%; 33 | transition:all 0.05s; 34 | transition-timing-function: cubic-bezier(0.1, 2.7, 0.58, 1); 35 | } 36 | .hour-hand{ 37 | width: 100px; 38 | } 39 | .second-hand{ 40 | height: 4px; 41 | } 42 | .number{ 43 | width: 300px; 44 | height:300px; 45 | font-size: 15px; 46 | 47 | /* border: 1px solid blue; */ 48 | /* border-radius: 280px; */ 49 | 50 | position:absolute; 51 | top:0px; 52 | right: 0px; 53 | } 54 | .number span{ 55 | display: block; 56 | width: 30px; 57 | height: 30px; 58 | /* background-color: red; */ 59 | text-align: center; 60 | line-height: 30px; 61 | } 62 | /* 数字位置算法 63 | clc; 64 | clear; 65 | num=2; 66 | top=135*sin((num-3)*30/180*pi)+135 67 | left=135*cos((num-3)*30/180*pi)+135 68 | */ 69 | .num12{ 70 | position: absolute; 71 | left:135px; 72 | } 73 | .num1{ 74 | position: absolute; 75 | top: 18.1px; 76 | left:202.5px; 77 | } 78 | .num2{ 79 | position: absolute; 80 | top: 67.5px; 81 | left:251.9px; 82 | } 83 | .num4{ 84 | position: absolute; 85 | top:202.5px; 86 | left:251.9px; 87 | 88 | } 89 | .num5{ 90 | position: absolute; 91 | top: 251.9px; 92 | left:202.5px; 93 | } 94 | .num7{ 95 | position: absolute; 96 | top: 251.9px; 97 | left:67.5px; 98 | } 99 | .num8{ 100 | position: absolute; 101 | top: 202.5px; 102 | left:18.1px; 103 | } 104 | .num10{ 105 | position: absolute; 106 | top: 67.5px; 107 | left:18.1px; 108 | } 109 | .num11{ 110 | position: absolute; 111 | top: 18.1px; 112 | left:67.5px; 113 | } 114 | .num3{ 115 | position: absolute; 116 | right:0px; 117 | top:135px; 118 | } 119 | .num6{ 120 | position: absolute; 121 | bottom:0px; 122 | left:135px; 123 | } 124 | .num9{ 125 | position: absolute; 126 | left:0px; 127 | top:135px; 128 | } -------------------------------------------------------------------------------- /03 imageProcessionwithJS/index-VUE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image Procession with Javascipt 8 | 9 | 10 | 59 | 60 |
61 |

利用JS更新CSS自定义变量

62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | 74 | 75 |
76 | 121 | 122 | -------------------------------------------------------------------------------- /03 imageProcessionwithJS/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Image Procession with Javascipt 8 | 9 | 58 | 59 | 60 |
61 |

利用JS更新CSS自定义变量

62 | 63 |
64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 |
73 | 74 | 75 | 76 |
77 | 78 | 91 | 92 | 93 | 94 | -------------------------------------------------------------------------------- /04 arrayOperation/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】数组操作 04 3 | date: 2021-11-15 19:30:39 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | ### 引言 9 | 10 | 本文介绍一下数组操作的一些常用方法。 11 | 12 | ### 正文 13 | 14 | #### 1创建数组 15 | 16 | ```javascript 17 | const people = [ 18 | 'Bernhard, Sandra', 'Bethea, Erin', 'Becker, Carl', 'Bentsen, Lloyd', 'Beckett, Samuel', 'Blake, William', 'Berger, Ric', 'Beddoes, Mick', 'Beethoven, Ludwig', 19 | 'Belloc, Hilaire', 'Begin, Menachem', 'Bellow, Saul', 'Benchley, Robert', 'Blair, Robert', 'Benenson, Peter', 'Benjamin, Walter', 'Berlin, Irving', 20 | 'Benn, Tony', 'Benson, Leana', 'Bent, Silas', 'Berle, Milton', 'Berry, Halle', 'Biko, Steve', 'Beck, Glenn', 'Bergman, Ingmar', 'Black, Elk', 'Berio, Luciano', 21 | 'Berne, Eric', 'Berra, Yogi', 'Berry, Wendell', 'Bevan, Aneurin', 'Ben-Gurion, David', 'Bevel, Ken', 'Biden, Joseph', 'Bennington, Chester', 'Bierce, Ambrose', 22 | 'Billings, Josh', 'Birrell, Augustine', 'Blair, Tony', 'Beecher, Henry', 'Biondo, Frank' 23 | ]; 24 | ``` 25 | 26 | #### 2 获取数组长度 27 | 28 | ```javascript 29 | console.log(people.length); 30 | ``` 31 | 32 | #### 3循环 33 | 34 | ```javascript 35 | people.forEach(function(item,index){ 36 | console.log(item,index); 37 | }) 38 | ``` 39 | 40 | #### 4 数据尾部添加一个元素 41 | 42 | ```javascript 43 | let newLength = people.push('Wheeler, Ben');// newLength的结果是people的长度,不是新添加的元素内容 44 | console.log(people.length); 45 | ``` 46 | 47 | #### 5 从尾部删除一个元素 48 | 49 | ```javascript 50 | let last = people.pop(); 51 | console.log(people.length); 52 | ``` 53 | 54 | #### 6 从头部删除一个元素 55 | 56 | ```javascript 57 | let first = people.shift(); 58 | console.log(people.length); 59 | ``` 60 | 61 | #### 7 从提添加一个新元素 62 | 63 | ```javascript 64 | let firstItem = people.unshift('Bernhard, Sandra'); 65 | console.log(people); 66 | ``` 67 | 68 | #### 8 寻找下标 69 | 70 | ```javascript 71 | let pos = people.indexOf('Blair, Tony'); 72 | console.log(pos); 73 | ``` 74 | 75 | #### 9 根据下标删除元素 76 | 77 | ```javascript 78 | let removeItem = people.splice(pos,1);//参数1表示Pos下标后多少个 79 | console.log(removeItem); 80 | ``` 81 | 82 | #### 10 复制数组 83 | 84 | ```javascript 85 | // 复制数组 1 86 | let newPeople1 = people; //people和newPeople1指向同一个内存 87 | // 复制数组 2 88 | let newPeople2 = people.slice(); // people和newPeople1指向不同一个内存 89 | ``` 90 | 91 | #### 11 Index超出数组长度 92 | 93 | ```javascript 94 | people[100] = 'bbb'; 95 | console.log(people); 96 | ``` 97 | 98 | #### 12 Array.prototype.filter() 过滤 99 | 100 | ```javascript 101 | const fifteen = inventors.filter(inventor=>(inventor.year>=1500 && inventor.year < 1600)); 102 | console.table(fifteen); 103 | ``` 104 | 105 | #### 13 Array.prototype.map() 106 | 107 | ```javascript 108 | 109 | const fullNames = inventors.map(inventor => (inventor.first + ' ' + inventor.last)); 110 | const fullNames2 = inventors.map(inventor => `${inventor.first} ${inventor.last}`); 111 | console.log(fullNames); 112 | console.log(fullNames2); 113 | ``` 114 | 115 | #### 14 Array.prototype.sort() 116 | 117 | ```javascript 118 | // 升序 119 | const ordered = inventors.sort((a,b) => a.year > b.year ? 1 : -1); 120 | console.table(ordered); 121 | // 降序 122 | const oldest = inventors.sort(function(a,b){ 123 | const lastInventor = a.passed - a.year; 124 | const nextInventor = b.passed - b.year; 125 | return lastInventor > nextInventor ? -1:1; 126 | }); 127 | console.table(oldest); 128 | ``` 129 | 130 | #### 15 Array.prototype.reduce() 结果返回单个值 131 | 132 | ```javascript 133 | // Array.prototype.reduce() 结果返回单个值 134 | const totalYears = inventors.reduce((total,inventor) => { 135 | return total + (inventor.passed - inventor.year); 136 | }, 0); // 0表示返回的单个值再加上0 137 | console.log(totalYears); 138 | 139 | ``` 140 | 141 | ### 结论 142 | 143 | 完整代码放在了[Github](https://github.com/janice143/arrayOperation)上,如果读者有兴趣,不妨试一试。 144 | 145 | -------------------------------------------------------------------------------- /04 arrayOperation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Array Operation 8 | 9 | 10 |

数组操作

11 | 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /05 flexPanel/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】 Flex Panel 05 3 | date: 2021-11-17 20:10:40 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | ### 引言 9 | 10 | 本文介绍一个动态放映网页,知识点主要涉及到flex容器,以及元素点击事件。 11 | 12 | 效果网站:https://janice143.github.io/flexPanel/ 13 | 14 | ### 正文 15 | 16 | #### 一、html部分 17 | 18 | 主要为5个div元素将网页分为5块,横向排列。每一个div元素中有上中下三个p标签,对应3段话。 19 | 20 | ```html 21 |
22 |
23 |

Hey

24 |

Let's

25 |

Dance

26 |
27 |
28 |

Give

29 |

Take

30 |

Receive

31 |
32 |
33 |

Experience

34 |

It

35 |

Today

36 |
37 |
38 |

Give

39 |

All

40 |

You can

41 |
42 |
43 |

Life

44 |

In

45 |

Motion

46 |
47 |
48 | ``` 49 | 50 | #### 二、css部分 51 | 52 | css代码主要要实现5个div元素均匀横向排列,每个div元素内有对应的背景图片、3个p标签的文本。P标签的文本在每一个div元素中也是左右居中、上下均匀排列。主要是利用了flex容器来实现。 53 | 54 | #### 1 flex容器 55 | 56 | 参考网站:https://www.ruanyifeng.com/blog/2015/07/flex-grammar.html 57 | 58 | 采用 Flex 布局的元素,称为 Flex 容器(flex container),简称"容器"。它的所有子元素自动成为容器成员,称为 Flex 项目(flex item),简称"项目"。 59 | 60 | 容器的属性: 61 | 62 | - flex-direction:项目的排列方向 63 | - flex-wrap:默认情况下,项目都排在一条线(又称"轴线")上。`flex-wrap`属性定义,如果一条轴线排不下,如何换行。 64 | - flex-flow:`flex-direction`属性和`flex-wrap`属性的简写形式 65 | - justify-content:项目在主轴上的对齐方式 66 | - align-items:项目在交叉轴上如何对齐 67 | - align-content:定义了多根轴线的对齐方式 68 | 69 | 项目的属性: 70 | 71 | - order:项目的排列顺序,数值越小,排列越靠前 72 | - flex-grow:定义项目的放大比例,默认为0 73 | - flex-shrink:项目的缩小比例,默认为1 74 | - flex-basis: 项目占据的主轴空间 75 | - flex:是`flex-grow`, `flex-shrink` 和 `flex-basis`的简写,默认值为`0 1 auto` 76 | - align-self:允许单个项目有与其他项目不一样的对齐方式,可覆盖`align-items`属性 77 | 78 | #### 2 css字体 79 | 80 | ```css 81 | 82 | text-transform: uppercase; 83 | font-family: 'Amatic SC',cursive; 84 | text-shadow: 0 0 4px rgba(0,0,0,0.72),0 0 14px rgba(0,0,0,0.45); 85 | ``` 86 | 87 | #### 3过渡样式 88 | 89 | ```css 90 | transition: 91 | font-size 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11), 92 | flex 0.7s cubic-bezier(0.61,-0.19, 0.7,-0.11), 93 | background 0.2s; 94 | transform:translateY(-100%); 95 | ``` 96 | 97 | #### 4 背景图片 98 | 99 | ```css 100 | background-size: cover; 101 | background-position: center; 102 | .panel1 { background-image:url(https://source.unsplash.com/gYl-UtwNg_I/1500x1500); } 103 | .panel2 { background-image:url(https://source.unsplash.com/rFKUFzjPYiQ/1500x1500); } 104 | .panel3 { background-image:url(https://images.unsplash.com/photo-1465188162913-8fb5709d6d57?ixlib=rb-0.3.5&q=80&fm=jpg&crop=faces&cs=tinysrgb&w=1500&h=1500&fit=crop&s=967e8a713a4e395260793fc8c802901d); } 105 | .panel4 { background-image:url(https://source.unsplash.com/ITjiVXcwVng/1500x1500); } 106 | .panel5 { background-image:url(https://source.unsplash.com/3MNzGlQM7qs/1500x1500); } 107 | ``` 108 | 109 | #### 5其他 110 | 111 | CSS选择器 112 | 113 | `*` 将匹配文档的所有元素;`>` 组合器选择前一个元素的直接子代的节点。 114 | 115 | .panel > *选择类名为panel的所以直接子元素 116 | 117 | ### Js部分 118 | 119 | 我写的程序,大致思路是在html代码里设置onclick属性,然后函数内容在js里写,不同的panel传入参数不一样 120 | 121 | 定位类名有两个以上的标签document.getElementsByClassName(`panel${num} panel-open`); 122 | 123 | ```javascript 124 | // 点击panel 12345,给对应的panel添加.panel-open属性 125 | function clickOpen(num){ 126 | const panelNumIf = document.getElementsByClassName(`panel${num} panel-open`); 127 | const panelNum = document.getElementsByClassName(`panel${num}`); 128 | // console.log(panelNumIf[0]) 129 | if (panelNumIf[0]) 130 | panelNumIf[0].classList.remove('panel-open'); 131 | else 132 | panelNum[0].classList.add('panel-open');//添加panel-open类属性 133 | console.log(`panel${num} panel-open`); 134 | }; 135 | ``` 136 | 137 | 别人的程序,大致思路是遍历5个Panel,监控是否有点击事件,有的话就运行toggleOpen函数,该函数里 this.classList.toggle('open')表示如果this有open类名,则删除,没有则加上。 138 | 139 | e.propertyName获取transitionend的属性名,e.propertyName.includes('flex')包含flex字段的属性名 140 | 141 | ```javascript 142 | const panels = document.querySelectorAll('.panel'); 143 | function toggleOpen() { 144 | console.log('Hello'); 145 | this.classList.toggle('open'); 146 | } 147 | function toggleActive(e) { 148 | console.log(e.propertyName); 149 | if (e.propertyName.includes('flex')) { 150 | this.classList.toggle('open-active'); 151 | } 152 | } 153 | panels.forEach(panel => panel.addEventListener('click', toggleOpen)); 154 | panels.forEach(panel => panel.addEventListener('transitionend', toggleActive)); 155 | ``` 156 | 157 | ### 总结 158 | 159 | 完整代码放在了[Github](https://github.com/janice143/flexPanel)上,如果读者有兴趣,不妨试一试。 160 | 161 | -------------------------------------------------------------------------------- /05 flexPanel/index-VUE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | flex panel 8 | 9 | 10 | 77 | 78 | 79 |
80 |
81 |
87 |

{{panel.topWord}}

88 |

{{panel.middleWord}}

89 |

{{panel.bottomWord}}

90 |
91 |
92 |
93 | 94 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /05 flexPanel/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Flex Panel 8 | 9 | 76 | 77 | 78 | 79 |
80 |
81 |

Hey

82 |

Let's

83 |

Dance

84 |
85 |
86 |

Give

87 |

Take

88 |

Receive

89 |
90 |
91 |

Experience

92 |

It

93 |

Today

94 |
95 |
96 |

Give

97 |

All

98 |

You can

99 |
100 |
101 |

Life

102 |

In

103 |

Motion

104 |
105 |
106 | 123 | 124 | 125 | -------------------------------------------------------------------------------- /06 AjaxTypeAhead/index-VUE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fun dictionary 8 | 9 | 68 | 69 | 70 |
71 |
72 | 73 | 87 |
88 |
89 | 150 | 151 | -------------------------------------------------------------------------------- /06 AjaxTypeAhead/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Fun dictionary 8 | 67 | 68 | 69 | 70 | 71 | 72 | 73 |
74 | 75 | 79 |
80 | 81 | 160 | 161 | 162 | -------------------------------------------------------------------------------- /08 canvas/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Canvas 08 3 | date: 2022-01-07 20:13:07 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第8天项目,为了更有挑战性,我实现了 **移动端绘图** 、**画笔样式选择**的功能。Have fun with the website! ♪(^∇^*) 12 | 13 | 14 | 15 | 网页效果:https://janice143.github.io/myCanvas/ (画了个嗅嗅,一不小心暴露自己是个魔法师啦,哈哈) 16 | 17 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_Canvas08.png?raw=true) 18 | 19 | ## 项目描述 20 | 21 | 利用html5 中的canvas实现的画板。在**电脑端**可利用鼠标移动点击进行绘图,在**移动端**可以通过触摸移动实现绘图。绘图的笔**颜色**、**粗细**可以调节,选中**橡皮擦**可对局部区域进行修改,**清屏**按钮可以一键清屏。 22 | 23 | 项目重点 24 | 25 | - canvas 26 | - window.innerWidth 27 | - lineJoin、lineWidth、lineCap 28 | - strokeColor 29 | - beginPath、moveTo、lineTo、stroke() 30 | 31 | - 鼠标事件 32 | - mousemove: *e*.offsetX, *e*.offsetY 33 | - mouseup 34 | - mouseout 35 | - mousedown 36 | - 触摸事件 37 | - touchmove 38 | - touchend 39 | - touchcancel 40 | - touchstart 41 | - touch坐标计算的坐标偏移问题 42 | - *input*标签的change事件 43 | - this.value 44 | - this.name 45 | - this.checked 46 | - 滑块input线性过渡变成非线性过渡 47 | - css的flex容器属性 48 | - *display*:flex 49 | - *align-items*: center 竖直居中 50 | - *justify-content*: center 水平居中 51 | 52 | ## 项目过程 53 | 54 | #### html部分 55 | 56 | 1. 三个input控件 57 | - 颜色 type="color" 58 | - 粗细 type="range" 59 | - 橡皮擦 type="checkbox" 60 | 2. canvas标签设置画布 61 | 3. 清屏按钮 type="button" 62 | 63 | #### Js部分 64 | 65 | - 获取`canvas`标签,并设定宽度和高度;获取四个input元素 66 | - 利用`getContext()`获取渲染上下文,存储在变量`ctx`中。 67 | - 在二维渲染上下文中,左上点坐标为(0,0),向右(x轴)向下(y轴)为正 68 | - 设置初始值 69 | - 初始化画笔颜色(`ctx.StrokeStyle`)粗细(`ctx.lineWidth`),橡皮擦不选中(`eraserChecked = false`); 70 | - 设置lineCap(线的末端形状)为圆形,lineJoin(两条线段连接处形状)为圆形 71 | - 编写`updateValue()`函数 72 | - 三个控件input发生改变时,触发事件,调用该函数,更新画笔三个初始值。 73 | - 编写`draw()`函数 74 | - 设定一个用于标记绘画状态的变量,画或者不画(true or false) 75 | - 判断是鼠标事件还是触摸事件,返回当前鼠标点和触摸点的坐标 76 | - 赋值新的画笔参数 77 | - 绘制前调用`beginPath()`,设定路径起点、终点 78 | - 编写`clearCanvas()`函数 79 | - 清屏的原理就是在画布上画满一个白色矩阵 80 | - 所有input的监听事件,控件中的change事件,清屏是click事件 81 | 82 | #### CSS部分 83 | 84 | 使用over-flow:hidden来设置页面不动,这点在移动端触摸时显得必不可少。 85 | 86 | ## 项目知识点 87 | 88 | #### [Canvas](https://www.w3school.com.cn/html/html5_canvas.asp) 89 | 90 | - 创建 Canvas 元素 91 | 92 | ```html 93 | 94 | ``` 95 | 96 | - 通过 JavaScript 来绘制 97 | 98 | ```javascript 99 | var canvas = document.getElementById('canvas'); 100 | var ctx = canvas.getContext('2d'); 101 | ``` 102 | 103 | - 基本样式属性 104 | - 颜色`:strokeStyle`:线条描边的颜色,`fillStyle`:填充的颜色 105 | - 线型:`lineCap`:笔触的形状;`lineJoin`:线条相较的方式;`lineWidth`:线条的宽度 106 | - 路径绘制 107 | - `beginPath()`:新建一条路径 108 | - `stroke()`:绘制轮廓 109 | - `moveTo()`:绘制操作的起点 110 | - `lineTo()`:路径的终点 111 | 112 | #### 触摸屏端坐标偏移问题 113 | 114 | 使用鼠标事件在canvas画布上画画,非常容易就能获取到画布上的坐标,使用(e.offsetX,e.offsetY)就行。但是对于移动端的触摸屏,必须利用【页面上的坐标】-【画布左上角的坐标】=【画布上的坐标】公式去计算当前触摸位置坐标。 115 | 116 | changedTouches[0].clientX表示当前触摸点在页面上的坐标,e.target.offsetLeft表示画布偏离页面左上角的位置。 117 | 118 | ```javascript 119 | x = e.changedTouches[0].clientX -e.target.offsetLeft; 120 | y = e.changedTouches[0].clientY-e.target.offsetTop; 121 | ``` 122 | 123 | 124 | 125 | > JS30的第8个项目圆满完成啦,虽然对原项目做了一些改进,但是整体上也实现了一些我自己的独特功能。PS:发现自己真的很喜欢编程,fun with front end development.希望自己能力越来越强,实现自己的程序员梦想。 126 | > 127 | > 感谢阅读,有问题联系我的邮箱1803105538@qq.com. 128 | 129 | 130 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /10 goList/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Go list 10 3 | date: 2022-01-09 20:27:51 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第10天的“待办清单”项目,我增加了 **双击删除任务** 、**添加任务**的功能。Have fun with the website! ♪(^∇^*) 12 | 13 | 网页效果:https://janice143.github.io/goList/ 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_Golist10.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 利用一些 `checkbox` 类型的 `input` 元素,通过在js中实现特定功能,而设计的待办清单网页。在网页中,可以通过点击checkbox来**标记**任务状态,通过按住shift键可以对任务实现**多项check**。也可以通过点击添加来**增加**任务,完成的任务可以通过**双击实现删除**。 20 | 21 | 项目重点 22 | 23 | - 类型为checkbox的input元素的点击事件,回调函数为`clickCheck` 24 | - *e*.shiftKey 25 | - this.checked 26 | - 标记上一次点击的input,以及多选内部的Input 27 | - 类型为text的input元素的change事件,回调函数为`displayTask` 28 | - 使用createElement创建元素 29 | - 使用append来在指定结点后添加html 30 | - 输入文本回车后情况文本,this.value = '' 31 | - 监听双击事件,移除任务 32 | - checkbox后面的文本双击事件,回调函数为`removeTask` 33 | - *e*.path[1].remove() 34 | 35 | ## 项目过程 36 | 37 | #### html部分 38 | 39 | 1. 输入文本框input 40 | 2. 定位在文本框上的div元素,点击后display: none 41 | 3. 四个类名为task-item是checkbox和任务文本p 42 | 4. 新添任务的占位标签,新添加的任务将会append在这里 43 | 44 | #### Js部分 45 | 46 | - 获取类型为checkbox的所有input,遍历点击事件 47 | - 点击事件为`clickCheck` 48 | - 多选操作的原理 49 | 50 | ```javascript 51 | if(e.shiftKey && this.checked) 52 | inputChecks.forEach(inputCheck => { 53 | console.log(inputCheck === this || inputCheck === lastChecked); 54 | if (inputCheck === this || inputCheck === lastChecked) { 55 | inBetween = !inBetween; 56 | // console.log('Starting to check them in between!'); 57 | } 58 | // console.log('行内是否',inBetween); 59 | if (inBetween) { 60 | inputCheck.checked = true; 61 | } 62 | }) 63 | ``` 64 | 65 | - 获取所有p元素,遍历双击事件 66 | - 双击事件为`removeTask` 67 | - 找到对应的任务路径,remove即可 68 | 69 | ```javascript 70 | function removeTask(e){ 71 | console.log(e.path[1].remove()); 72 | } 73 | ``` 74 | 75 | - 获取类名为add-icon的元素,监听点击事件,点击隐藏 76 | 77 | - 使页面元素隐藏和显示可以有两种方式: 78 | 79 | - 方式1:设置元素style属性中的display 80 | 81 | ```javascript 82 | var t = document.getElementById('test');//选取id为test的元素 83 | t.style.display = 'none'; // 隐藏选择的元素 84 | t.style.display = 'block'; // 以块级样式显示 85 | ``` 86 | 87 | - 方式2:设置元素style属性中的visibility 88 | 89 | ```javascript 90 | var t = document.getElementById('test'); 91 | t.style.visibility = 'hidden'; // 隐藏元素 92 | t.style.visibility = 'visible'; // 显示元素 93 | ``` 94 | 95 | 二者的区别在于设置display隐藏后不占用原来的位置,而visibility隐藏后元素位置任然被占用。 96 | 97 | - 获取类型为text的input元素,监听change事件 98 | 99 | - change事件调用`displayTask` 100 | - [动态插入html](https://zh.javascript.info/modifying-document) 101 | - 使用createElement创建元素 102 | - 使用append挂载元素 103 | - 监听双击事件 104 | 105 | ```javascript 106 | function displayTask(){ 107 | // 使用createElement创建元素 108 | const newTaskItem = document.createElement('div'); 109 | newTaskItem.className = 'new-task-item'; 110 | const html = 111 | ` 112 |
113 | 114 |

${this.value}

115 |
116 | `; 117 | newTaskItem.innerHTML = html; 118 | newTask.append(newTaskItem); 119 | // console.log(newTask); 120 | // console.log(html); 121 | this.value = '' 122 | newTaskItem.addEventListener('dblclick',removeTask) 123 | } 124 | ``` 125 | 126 | ### CSS部分 127 | 128 | - `:checked` 选择器 129 | - 紧邻兄弟组合器:A`+` B 组合器选择相邻元素,即后一个元素B紧跟在前一个A之后,并且共享同一个父节点 130 | - 添加删除线 *text-decoration*: line-through; 131 | 132 | ## 项目知识点 133 | 134 | #### DOM 树 135 | 136 | DOM为文档对象模型,每个 HTML 标签都是一个对象。 137 | 138 | DOM 将 HTML 表示为标签的树形结构。标签被称为 **元素节点**(或者仅仅是元素),并形成了树状结构:`` 在根节点,`` 和 `` 是其子项,等。元素内的文本形成 **文本节点**,被标记为 `#text`。一个文本节点只包含一个字符串。它没有子项,并且总是树的叶子。 139 | 140 | #### 利用JS修改文档 141 | 142 | - 创建一个元素(DOM节点) 143 | - `document.createElement(tag)`创建一个新 **元素节点(element node)** 144 | - `document.createTextNode(text)`创建一个 **文本节点** 145 | 146 | - 创建 `div` 分为 3 个步骤: 147 | 148 | ```javascript 149 | // 1. 创建
元素 150 | let div = document.createElement('div'); 151 | 152 | // 2. 将元素的类设置为 "alert" 153 | div.className = "alert"; 154 | 155 | // 3. 填充消息内容 156 | div.innerHTML = "Hi there! You've read an important message."; 157 | ``` 158 | 159 | 这时已经创建了该元素。但到目前为止,它还只是在一个名为 `div` 的变量中,尚未在页面中。所以我们无法在页面上看到它。 160 | 161 | - `append`挂载元素 162 | 163 | 为了让 `div` 显示出来,我们需要将其插入到 `document` 中的某处。 164 | 165 | - `append`:`document.body.append(div)`。 166 | - `node.append(...nodes or strings)` —— 在 `node` **末尾** 插入节点或字符串, 167 | - `node.prepend(...nodes or strings)` —— 在 `node` **开头** 插入节点或字符串, 168 | - `node.before(...nodes or strings)` —— 在 `node` **前面** 插入节点或字符串, 169 | - `node.after(...nodes or strings)` —— 在 `node` **后面** 插入节点或字符串, 170 | - `node.replaceWith(...nodes or strings)` —— 将 `node` 替换为给定的节点或字符串。 171 | 172 | 173 | 174 | > JS30的第10个项目圆满完成啦,虽然对原项目做了一些改进,但是整体上也实现了一些我自己的独特功能。PS:中间跳了第7和9个项目,如果有时间我后面会补上滴! 175 | > 176 | > 感谢阅读,有问题联系我的邮箱1803105538@qq.com. 177 | -------------------------------------------------------------------------------- /10 goList/index-VUE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | go list! 8 | 9 | 86 | 87 | 88 |
89 |
90 | 93 |
+
96 |
102 | 105 |

{{item.text}}

106 |
107 |
108 |
109 | 165 | 166 | -------------------------------------------------------------------------------- /10 goList/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Go list! 8 | 9 | 86 | 87 | 88 |
89 | 90 | 91 |
+
92 | 93 |
94 | 95 |

tip1: 双击删除任务

96 |
97 | 98 |
99 | 100 |

tip2: 完成一项任务打钩

101 |
102 |
103 | 104 |

tip3: 按住shif键多选

105 |
106 |
107 | 108 |

开启你的任务

109 |
110 | 111 |
112 |
113 | 114 | 188 | -------------------------------------------------------------------------------- /11 videoPlayer/652333414.mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/11 videoPlayer/652333414.mp4 -------------------------------------------------------------------------------- /11 videoPlayer/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Video player 11 3 | date: 2022-01-18 13:58:54 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第11天的“自定义视频播放器”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 网页效果: https://janice143.github.io/videoPlayer/ 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_VideoPlayer11.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 利用video标签,以及一些div标签,在js中设置视频的播放控件,包括**暂停/播放**,**声音调节**,**视频进度调节**,**视频播放率**,**跳过/退后**。 20 | 21 | 项目重点 22 | 23 | - [video对象的各种属性、方法和事件](https://www.w3school.com.cn/jsref/dom_obj_video.asp) 24 | - `paused` 25 | - `play()` 26 | - `pause()` 27 | - `currentTime` 28 | - `volume` 29 | - `playbackRate` 30 | 31 | - HTML DOM offsetWidth 属性 32 | - 获取元素的宽度,包含内边距(padding)和边框(border): 33 | - [HTML5 data-* 自定义属性](https://developer.mozilla.org/zh-CN/docs/Web/HTML/Global_attributes/data-*) 34 | - this.dataset. 35 | - data- 36 | 37 | ## 项目过程 38 | 39 | #### html部分 40 | 41 | 1. `video`标签标记视频文件 42 | 2. `div`标签和`button`标签实现的一些视频控件,类名为.controlers 43 | - .progress进度条,.progress_filled进度条填充颜色 44 | - .player_button播放按钮 45 | - 声音滑块 46 | - 播放速度滑块 47 | - 前进/后退按钮 48 | 49 | #### Js部分 50 | 51 | - 获取标签 52 | 53 | - 编写自定义函数 54 | 55 | - 播放按键 56 | 57 | ```javascript 58 | const method = video.paused ? 'play' : 'pause'; 59 | video[method](); 60 | ``` 61 | 62 | - 更新播放键的按键 63 | 64 | ```javascript 65 | const icon = this.paused ? '►' : '❚ ❚'; 66 | toggle.textContent = icon; 67 | ``` 68 | 69 | - 前进/后退 70 | 71 | ```javascript 72 | video.currentTime += parseFloat(this.dataset.skip); 73 | ``` 74 | 75 | - 更新滑块的值 76 | 77 | ```javascript 78 | video[this.name] = this.value; 79 | ``` 80 | 81 | - 更新进度条(填充颜色) 82 | 83 | ```javascript 84 | const percent = (video.currentTime / video.duration) * 100; 85 | progressBar.style.flexBasis = `${percent}%`; 86 | ``` 87 | 88 | - 鼠标移动进度条 89 | 90 | ```javascript 91 | const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration; 92 | video.currentTime = scrubTime; 93 | ``` 94 | 95 | - 添加监听事件 96 | 97 | - 视频的click,play,pause,timeupdata事件 98 | - 播放按钮、前进后退按钮的click事件 99 | - 滑块的change,mousemove事件 100 | - 进度条的click,mousemove,mousedown,mouseup事件 101 | 102 | ### CSS部分 103 | 104 | - flex容器的项目属性 105 | - `flex-basis`:项目占据的主轴空间(main size) 106 | - `flex` 107 | 108 | - [属性选择器](https://developer.mozilla.org/zh-CN/docs/Web/CSS/Attribute_selectors) 109 | - input[type=range] 110 | 111 | ## 项目知识点 112 | 113 | #### [data-*](https://juejin.cn/post/6844904039407157262) 114 | 115 | 自定义数据属性,可通过所属元素的 [`HTMLElement`](https://developer.mozilla.org/zh-CN/docs/Web/API/HTMLElement) 接口访问,确切地说是`HTMLElement.dataset` , `HTMLElement.dataset["testValue"]` 属性访问。 116 | 117 | 注*:data-后面的命名规则 118 | 119 | - 该名称不能以`xml`开头,无论这些字母是大写还是小写; 120 | - 该名称不能包含任何分号; 121 | - 该名称不能包含A至Z的大写字母 122 | - data后面的命名中有-,如 *data-test-value* ,可通过 `HTMLElement.dataset.testValue` ( 或者是` HTMLElement.dataset["testValue"] `) 来访问,任何短线符号都会被下个字母的大写替代(驼峰拼写)。 123 | 124 | > JS30的第11个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 125 | > 126 | 127 | -------------------------------------------------------------------------------- /11 videoPlayer/index-VUE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Video Player 8 | 9 | 10 | 11 | 12 |
13 |
14 | 17 |
18 |
24 |
25 |
26 | 27 | 28 | 29 | 30 | 31 |
32 |
33 |
34 | 97 | 98 | -------------------------------------------------------------------------------- /11 videoPlayer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Video Player 8 | 9 | 10 | 11 | 12 |
13 | 16 |
17 |
18 |
19 |
20 | 21 | 22 | 23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /11 videoPlayer/script.js: -------------------------------------------------------------------------------- 1 | // 获取标签 2 | const player = document.querySelector('.player'); 3 | const video = player.querySelector('.viewer'); 4 | const progress = player.querySelector('.progress'); 5 | const progressBar = player.querySelector('.progress_filled'); 6 | const toggle = player.querySelector('.toggle'); 7 | const skipButtons = player.querySelectorAll('[data-skip]');// 获取自定义属性 8 | const ranges = player.querySelectorAll('.player_slider'); 9 | 10 | // 写自定义函数 11 | // 播放按键 12 | function togglePlay() { 13 | const method = video.paused ? 'play' : 'pause'; 14 | console.log(method) 15 | video[method](); 16 | } 17 | // console.log(video.paused) 18 | 19 | // 更新播放键的按键 20 | function updateButton() { 21 | const icon = this.paused ? '►' : '❚ ❚'; 22 | console.log(icon); 23 | toggle.textContent = icon; 24 | } 25 | // 跳过 26 | function skip() { 27 | video.currentTime += parseFloat(this.dataset.skip); 28 | } 29 | // 更新滑块的值 30 | function handleRangeUpdate() { 31 | video[this.name] = this.value; 32 | console.log(this.name) 33 | } 34 | // 更新进度条 35 | function handleProgress() { 36 | const percent = (video.currentTime / video.duration) * 100; 37 | progressBar.style.flexBasis = `${percent}%`; 38 | } 39 | // 鼠标移动进度条 40 | function scrub(e) { 41 | const scrubTime = (e.offsetX / progress.offsetWidth) * video.duration; 42 | video.currentTime = scrubTime; 43 | } 44 | 45 | 46 | 47 | // 添加监听事件 48 | 49 | video.addEventListener('click', togglePlay); 50 | video.addEventListener('play', updateButton); 51 | video.addEventListener('pause', updateButton); 52 | video.addEventListener('timeupdate', handleProgress); 53 | 54 | toggle.addEventListener('click', togglePlay); 55 | skipButtons.forEach(button => button.addEventListener('click', skip)); 56 | ranges.forEach(range => range.addEventListener('change', handleRangeUpdate)); 57 | ranges.forEach(range => range.addEventListener('mousemove', handleRangeUpdate)); 58 | 59 | let mousedown = false; 60 | progress.addEventListener('click', scrub); 61 | progress.addEventListener('mousemove', (e) => mousedown && scrub(e)); 62 | progress.addEventListener('mousedown', () => mousedown = true); 63 | progress.addEventListener('mouseup', () => mousedown = false); 64 | 65 | 66 | -------------------------------------------------------------------------------- /11 videoPlayer/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | padding: 0; 4 | display: flex; 5 | background: #7A419B; 6 | min-height: 100vh; 7 | background: linear-gradient(135deg, #7c1599 0%,#921099 48%,#7e4ae8 100%); 8 | background-size: cover; 9 | align-items: center; 10 | justify-content: center; 11 | } 12 | .player{ 13 | max-width: 750px; 14 | border: 5px solid rgba(0,0,0,0.2); 15 | box-shadow: 0 0 20px rgba(0,0,0,0.2); 16 | position: relative; 17 | font-size: 0; 18 | overflow: hidden; 19 | 20 | } 21 | 22 | .player_button { 23 | background: none; 24 | border: 0; 25 | line-height: 1; 26 | color: white; 27 | text-align: center; 28 | outline: 0; 29 | padding: 0; 30 | cursor: pointer; 31 | max-width: 50px; 32 | } 33 | 34 | .player_button:focus { 35 | border-color: #ffc600; 36 | } 37 | 38 | .player_slider { 39 | width: 10px; 40 | height: 30px; 41 | } 42 | 43 | .controlers { 44 | display: flex; 45 | position: absolute; 46 | bottom: 0; 47 | width: 100%; 48 | transform: translateY(100%) translateY(-5px); 49 | transition: all .3s; 50 | flex-wrap: wrap; 51 | background: rgba(0,0,0,0.1); 52 | } 53 | 54 | .player:hover .controlers{ 55 | transform: translateY(0); 56 | } 57 | 58 | .player:hover .progress { 59 | height: 15px; 60 | } 61 | 62 | .controlers > * { 63 | flex: 1; 64 | } 65 | 66 | .progress { 67 | flex: 10; 68 | position: relative; 69 | display: flex; 70 | flex-basis: 100%; 71 | height: 5px; 72 | transition: height 0.3s; 73 | background: rgba(0,0,0,0.5); 74 | cursor: ew-resize; 75 | } 76 | 77 | .progress_filled { 78 | /* width: 50%; */ 79 | background: #ffc600; 80 | flex: 0; 81 | flex-basis: 50%; 82 | } 83 | 84 | 85 | input[type=range] { 86 | -webkit-appearance: none; 87 | background: transparent; 88 | width: 100%; 89 | margin: 0 5px; 90 | } 91 | 92 | input[type=range]:focus { 93 | outline: none; 94 | } 95 | 96 | input[type=range]::-webkit-slider-runnable-track { 97 | width: 100%; 98 | height: 8.4px; 99 | cursor: pointer; 100 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0); 101 | background: rgba(255,255,255,0.8); 102 | border-radius: 1.3px; 103 | border: 0.2px solid rgba(1, 1, 1, 0); 104 | } 105 | 106 | input[type=range]::-webkit-slider-thumb { 107 | height: 15px; 108 | width: 15px; 109 | border-radius: 50px; 110 | background: #ffc600; 111 | cursor: pointer; 112 | -webkit-appearance: none; 113 | margin-top: -3.5px; 114 | box-shadow:0 0 2px rgba(0,0,0,0.2); 115 | } 116 | 117 | input[type=range]:focus::-webkit-slider-runnable-track { 118 | background: #bada55; 119 | } 120 | 121 | input[type=range]::-moz-range-track { 122 | width: 100%; 123 | height: 8.4px; 124 | cursor: pointer; 125 | box-shadow: 1px 1px 1px rgba(0, 0, 0, 0), 0 0 1px rgba(13, 13, 13, 0); 126 | background: #ffffff; 127 | border-radius: 1.3px; 128 | border: 0.2px solid rgba(1, 1, 1, 0); 129 | } 130 | 131 | input[type=range]::-moz-range-thumb { 132 | box-shadow: 0 0 0 rgba(0, 0, 0, 0), 0 0 0 rgba(13, 13, 13, 0); 133 | height: 15px; 134 | width: 15px; 135 | border-radius: 50px; 136 | background: #ffc600; 137 | cursor: pointer; 138 | } 139 | 140 | -------------------------------------------------------------------------------- /12 secretCode/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Secret code sequence 12 3 | date: 2022-01-20 15:41:16 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第12天的“字符序列检测”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 网页效果: https://janice143.github.io/secretCode/ 14 | 15 | 键盘输入 **happy 2022** 即可触发彩蛋。 16 | 17 | ## 项目描述 18 | 19 | 通过判断键盘输入的字符串中是否含有指定字符串序列,开启网页中的隐藏彩蛋。本项目的彩蛋是 20 | 21 | 从网页 [Cornify.com](https://www.cornify.com/) 中加载一个 JS 文件,调用其中的 `cornify_add()` 方法时,随机在页面出加载独角兽的图标和`p`标签。 22 | 23 | 项目重点 24 | 25 | - window的keyup事件 26 | - `window.addEventListener('keyup',)` 27 | - `e.key` 28 | 29 | - 数组操作 30 | - `.push()` 31 | - `.splice()` 32 | - `.length` 33 | - `.join()` 34 | - `.includes()` 35 | 36 | ## 项目过程 37 | 38 | #### html部分 39 | 40 | 1. `p`标签标记一段提示的文字 41 | 42 | #### Js部分 43 | 44 | - 声明一个变量用来存储按下的字符串序列数组 45 | 46 | - 声明一个变量用来存储已知的指定字符串序列 47 | 48 | - window的键盘监听事件 49 | 50 | - 将字符串指定规则切分 51 | 52 | ```javascript 53 | pressCode.splice(-secretCode.length - 1, pressCode.length - secretCode.length) 54 | ``` 55 | 56 | - 判断是否包含指定字符串 57 | 58 | ```javascript 59 | if (pressCode.join('').includes(secretCode)){} 60 | ``` 61 | 62 | - 随机在页面中加载独角兽图标 63 | 64 | ```javascript 65 | cornify_add(); 66 | ``` 67 | 68 | ## 项目知识点 69 | 70 | #### js中的splice方法的使用说明 71 | 72 | splice方法可以用来对js的数组进行删除,添加,替换等操作。 73 | 74 | 1. 删除。第一个参数为起始位置(如果为负数,表示倒数),第二个参数为要删除几个。 75 | 76 | `array.splice(index,num)` 77 | 78 | 2. 插入。第一个参数(插入位置),第二个参数(0),第三个参数(插入的项)。 79 | 80 | `array.splice(index,0,insertValue)` 81 | 82 | 3. 替换。第一个参数(起始位置),第二参数(删除项数),第三参数(插入任意数量的项)。 83 | 84 | `array.splice(index,num,insertValue)` 85 | 86 | > JS30的第12个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 87 | 88 | -------------------------------------------------------------------------------- /12 secretCode/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Key Sequence Detection 8 | 9 | 10 | 11 |

Press the secret code to enter another world!

12 | 13 | 38 | 39 | -------------------------------------------------------------------------------- /13 slideinonScroll/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Slide in on scroll 13 3 | date: 2022-01-30 19:41:50 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[iaineisalsoyan](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第13天的“图片随屏幕滚动而滑入滑出”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 网页效果: https://janice143.github.io/sliderIn/ 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_Slidein13.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 本项目为一个图文浏览网页,其中,当浏览到图片时(屏幕滚动到图片上),图片具有滑入特效,浏览完毕后,图片滑出。滑入滑出特效由css的`translateX()`实现,触发特效有javascript控制。 20 | 21 | #### 项目重点 22 | 23 | - window的scroll事件 24 | - `window.addEventListener('scroll')` 25 | - 一些位置(像素值) 26 | - `window.scrollY` 文档当前垂直滚动的像素数 27 | - `window.innerHeight` viewport部分的高度 28 | - `sliderImage.offsetTop` 当前元素顶部相对于其 offsetParent 元素的顶部的距离 29 | - [未枝丫博客有图解](https://github.com/soyaine/JavaScript30/tree/master/13%20-%20Slide%20in%20on%20Scroll) 30 | 31 | - `debounce` 的作用(函数防抖) 32 | - 降低事件监听的频率,使用了 Lodash 中的 debounce 方法 33 | 34 | ## 项目过程 35 | 36 | #### html部分 37 | 38 | 1. `p`标签的文字 39 | 2. `img`标签的图片 40 | 41 | #### JS部分 42 | 43 | - 监听window的scroll事件 44 | - 触发`checkSlide`函数 45 | - 图片滑入条件:屏幕滚动位置以及屏幕高度之和 > 图片顶部距离页面距离以及图片半高;屏幕滚动位置 < 图片底部距离 46 | 47 | ```javascript 48 | const slideInAt = (window.scrollY + window.innerHeight); 49 | const imageBottom = sliderImage.offsetTop + sliderImage.height; 50 | const isHalfShown = slideInAt > (sliderImage.offsetTop + sliderImage.height / 2); 51 | const isNotScrolledPast = window.scrollY < imageBottom; 52 | ``` 53 | 54 | - 函数防抖 55 | 56 | 由于每次滚动都触发监听事件,会降低 JavaScript 运行性能,所以用 `debounce` 函数来降低触发的次数 57 | 58 | ```javascript 59 | function debounce(func, wait = 20, immediate = true) { 60 | var timeout; 61 | return function() { 62 | var context = this, args = arguments; 63 | var later = function() { 64 | timeout = null; 65 | if (!immediate) func.apply(context, args); 66 | }; 67 | var callNow = immediate && !timeout; 68 | clearTimeout(timeout); 69 | timeout = setTimeout(later, wait); 70 | if (callNow) func.apply(context, args); 71 | }; 72 | }; 73 | ``` 74 | 75 | #### CSS部分 76 | 77 | - 屏幕滚动之前,图片的状态是:不透明度为0(隐藏),x方向偏移30%(相对于图片大小),缩放95%。 78 | 79 | ```css 80 | .align-right.slide-in { 81 | transform: translateX(30%) scale(0.95); 82 | } 83 | .slide-in.active { 84 | opacity: 1; 85 | transform: translateX(0%) scale(1); 86 | } 87 | ``` 88 | 89 | - 触发特效,图片的状态是:不透明度为1,x方向偏移0%(相对于图片大小),缩放1。 90 | 91 | ```css 92 | .slide-in.active { 93 | opacity: 1; 94 | transform: translateX(0%) scale(1); 95 | } 96 | ``` 97 | 98 | ## 项目补充 99 | 100 | #### 元素浮动 101 | 102 | ##### 作用 103 | 104 | 能够实现让多个元素排在问一行,并且给这些元素设置宽度与高度。 105 | 106 | ##### 背景 107 | 108 | 在标准文档流中的元素只有两种:块级元素和行内元素。让多个元素排在同一行:行内元素的特性;给这些元素设置宽高:块级元素的特性。如果想让一些元素既要有块级元素的特点也要有行内元素的特点,只能让这些元素脱离标准文档流(脱标),浮动可以让元素脱离标准文档流,可以实现让多个元素排在同一行并且可以设置宽高。 109 | 110 | ##### 实现 111 | 112 | 浮动通过浮动属性来实现,`float`这个属性有两个值left向左浮动,向左移动、right向右浮动,向右移动。 113 | 114 | ##### 浮动元素的特性 115 | 116 | - 浮动元素脱离标准文档流不再占用空间; 117 | 118 | - 我们可以把浮动元素理解为“漂” 119 | - 浮动元素的层级比标准文档流里面的元素层级要高,会将标准文档流中的元素给压盖住 120 | - 行内素浮动后,变成块状元素 121 | 122 | ##### 清除浮动:只要有浮动那么必须有清除浮动 123 | 124 | **1** **为什么要清除浮动?** 125 | 126 | 因为经过浮动元素会影响到下面的元素的排版布局,浮动元素的父元素没有将浮动元素包裹,只要清除了浮动,就不会影响到浮动元素的下面进行排版布局,浮动元素的父元素会将浮动元素从视觉上包裹着。 127 | 128 | **2** **清除浮动有以下三种方法:** 129 | 130 | - 给浮动元素的父元素设置一个固定的高度 131 | - 使用清除浮动的样式属性 clear.(clear:left清除左浮动, clear: right:;清除右浮动 clear: both两者都清除),这个属性一般用在最后一个浮动元素的下面,在最后一个浮动元素的下面(不是子级,而是并列下一行)新建一个空白的div,这个div什么内容都不要放,只做一件事件,就是清除浮动 132 | - 使用 overflow: hidden这个属性来清除浮动 133 | 134 | **注意***:使用float脱离文档流时,其他盒子会无视这个元素,但其他盒子内的文本依然会为这个元素让出位置,环绕在该元素的周围 135 | 136 | 137 | 138 | > JS30的第13个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 139 | 140 | 141 | 142 | 143 | 144 | -------------------------------------------------------------------------------- /14 JSreferenceVScopy/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Reference VS copy 14 3 | date: 2022-01-31 20:10:10 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第14天的“JS中引用和复制区别”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | ## 项目描述 14 | 15 | 本项目主要是在javascript中对比引用和复制变量的区别,效果在console(控制台)中显示。 16 | 17 | #### 项目重点 18 | 19 | - 对于基础类型的值,存储的是值 20 | - number 21 | - string 22 | - boolean 23 | - 对于复杂类型的值,存储的是引用(指针) 24 | - arr 25 | - object 26 | - regx 27 | 28 | - 对于复杂类型的值,如果实现复制 29 | - arr 30 | - Array.prototype.slice() 31 | - Array.prototype.concat() 32 | - ES6 扩展语法 33 | - Array.from() 34 | - 对象 35 | - Object.assign() 36 | - JSON 转换 37 | 38 | ## 项目过程 39 | 40 | #### JS部分 41 | 42 | - 首先从 String、Number、Boolean 类型的值开始。 43 | 44 | ```javascript 45 | let age = 100; 46 | let age2 = age; 47 | console.log(age, age2); // 100 100 48 | age = 200; 49 | console.log(age, age2); // 200 100 50 | ``` 51 | 52 | 改动age不会影响age2。 53 | 54 | - 对于数组 55 | 56 | ```javascript 57 | const players = ['Wes', 'Sarah', 'Ryan', 'Poppy']; 58 | const team = players; 59 | console.log(players, team); 60 | team[3] = 'Lux'; 61 | console.log(players, team); 62 | // ["Wes", "Sarah", "Ryan", "Lux"] ["Wes", "Sarah", "Ryan", "Lux"] 63 | ``` 64 | 65 | 对数组进行和Number类型相同的复制操作,发现改动team会改变players。 66 | 67 | **结论**:基础类型(number,string,boolean)将内容直接存储在**栈**中(大小固定位置连续的存储空间),记录的是该数据类型的值,即直接访问,基础类型赋值是复制(copy); 68 | 69 | 复杂类型(object即广义的对象类型(arr,object,regx))将内容存储在堆中,堆所对应的栈中记录的是**指针**(堆的地址),外部访问时先引出地址,再通过地址去找到值所存放的位置。复杂类型赋值是地址引用。 70 | 71 | - 数组的复制 72 | 73 | - 方法一 Array.prototype.slice() 74 | 75 | ```javascript 76 | const team2 = players.slice(); 77 | team2[3] = 'Lux2'; 78 | console.log(players, team2); 79 | ``` 80 | 81 | - 方法二 Array.prototype.concat() 82 | 83 | ```javascript 84 | const team3 = [].concat(players); 85 | team3[3] = 'Lux3'; 86 | console.log(players, team3); 87 | ``` 88 | 89 | - 方法三 ES6 扩展语法 90 | 91 | ```javascript 92 | const team4 = [...players]; 93 | team4[3] = 'Lux4'; 94 | console.log(players, team4); 95 | ``` 96 | - 方法四 Array.from() 97 | 98 | ```javascript 99 | const team5 = Array.from(players); 100 | team5[3] = 'Lux5'; 101 | console.log(players, team5); 102 | ``` 103 | 104 | - 对象的复制 105 | 106 | - 方法一 Object.assign() 107 | 108 | ```javascript 109 | const person = { 110 | name: 'Wes Bos', 111 | age: 80 112 | }; 113 | const cap2 = Object.assign({}, person, { number: 99, age: 12 }); 114 | console.log(cap2); // Object {name: "Wes Bos", age: 12, number: 99} 115 | ``` 116 | 117 | - 方法二 JSON 转换 118 | 119 | ```javascript 120 | const wes = { 121 | name: 'Wes', 122 | age: 100, 123 | social: { 124 | twitter: '@wesbos', 125 | facebook: 'wesbos.developer' 126 | } 127 | }; 128 | const dev = Object.assign({}, wes); 129 | const dev2 = JSON.parse(JSON.stringify(wes)); 130 | console.log(wes); 131 | console.log(dev); 132 | console.log(dev2); 133 | ``` 134 | 135 | ## 项目补充 136 | 137 | #### ES6扩展运算符 138 | 139 | 符号:`...` 140 | 141 | 作用:将数组或对象进行展开 142 | 143 | 例如,对于数组arr=[1,2,3] 144 | 145 | console.log(...arr)相当于for循环把arr中每个元素打印一下。 146 | 147 | #### 参考博客 148 | 149 | 1 [js 值引用和值复制](https://segmentfault.com/a/1190000015411195) 150 | 151 | 2 [对象引用和复制]( https://zh.javascript.info/object-copy) 152 | 153 | 3 [总结 ES6 扩展运算符(...)](https://segmentfault.com/a/1190000020259974) 154 | 155 | 4 [未枝丫](https://github.com/soyaine)的[JS30博客](https://github.com/soyaine/JavaScript30/tree/master/14%20-%20JavaScript%20References%20VS%20Copying) 156 | 157 | > JS30的第14个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 158 | 159 | -------------------------------------------------------------------------------- /14 JSreferenceVScopy/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | JS reference VS copy 8 | 9 | 10 | 15 | 16 | -------------------------------------------------------------------------------- /15 localStorage/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Local storage 8 | 9 | 10 | 11 | 12 |
13 |

LOCAL TAPAS

14 |

15 | 18 |
19 | 20 | 21 |
22 |
23 | 75 | 76 | 77 | -------------------------------------------------------------------------------- /15 localStorage/oh-la-la.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/15 localStorage/oh-la-la.jpeg -------------------------------------------------------------------------------- /15 localStorage/style.css: -------------------------------------------------------------------------------- 1 | body{ 2 | margin: 0; 3 | padding: 0; 4 | } 5 | html { 6 | box-sizing: border-box; 7 | background: url("oh-la-la.jpeg") center no-repeat; 8 | background-size: cover; 9 | min-height: 100vh; 10 | display: flex; 11 | justify-content: center; 12 | align-items: center; 13 | text-align: center; 14 | font-family: Futura, "Trebuchet MS", Arial, sans-serif; 15 | } 16 | svg { 17 | fill: white; 18 | background: rgba(0, 0, 0, 0.1); 19 | padding: 20px; 20 | border-radius: 50%; 21 | width: 200px; 22 | margin-bottom: 50px; 23 | } 24 | .wrapper { 25 | padding: 20px; 26 | max-width: 350px; 27 | background: rgba(255, 255, 255, 0.95); 28 | box-shadow: 0 0 0 10px rgba(0, 0, 0, 0.1); 29 | } 30 | 31 | h2 { 32 | text-align: center; 33 | margin: 0; 34 | font-weight: 200; 35 | } 36 | 37 | .plates { 38 | margin: 0; 39 | padding: 0; 40 | text-align: left; 41 | list-style: none; 42 | } 43 | 44 | .plates li { 45 | border-bottom: 1px solid rgba(0, 0, 0, 0.2); 46 | padding: 10px 0; 47 | font-weight: 100; 48 | display: flex; 49 | } 50 | 51 | .plates label { 52 | flex: 1; 53 | cursor: pointer; 54 | } 55 | 56 | .plates input { 57 | display: none; 58 | } 59 | 60 | .plates input + label:before { 61 | content: "⬜️"; 62 | margin-right: 10px; 63 | } 64 | 65 | .plates input:checked + label:before { 66 | content: "🌮"; 67 | } 68 | 69 | .add-items { 70 | margin-top: 20px; 71 | } 72 | 73 | .add-items input { 74 | padding: 10px; 75 | outline: 0; 76 | border: 1px solid rgba(0, 0, 0, 0.1); 77 | } 78 | 79 | 80 | 81 | -------------------------------------------------------------------------------- /16 mouseMoveShadow/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Mouse Move Shadow 16 3 | date: 2022-02-10 21:10:23 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第16天的“文字阴影随鼠标移动”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 网页效果: https://janice143.github.io/mouseMoveShadow/ 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_MouseMoveShadow16.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 本项目实现的是一个文字阴影随鼠标位置移动的特效。其中文字阴影是通过添加CSS的text-shadow属性实现的,为了让文字阴影随鼠标位置移动,需要获取当前鼠标的位置,通过一些转化变成对应的文字阴影位置。 20 | 21 | #### 项目重点 22 | 23 | - CSS的[text-shadow](https://developer.mozilla.org/en-US/docs/Web/CSS/text-shadow)属性 24 | - `text-shadow: 10px 10px 0 rgba(0,0,0,1), 10px 20px 0 rgba(200,0,0,1);` 25 | - HTMLElement的一些只读属性 26 | - `offsetWidth` 27 | - `offsetHeight` 28 | - `offsetLeft` 29 | - `offsetTop` 30 | 31 | - 鼠标事件的一些属性 32 | - `offsetX` 33 | - `offsetY` 34 | 35 | ## 项目过程 36 | 37 | #### HTML部分 38 | 39 | - 类名为hero的div元素 40 | - h1标签,加了`contenteditable`属性,表示浏览网页的用户可以编辑 41 | 42 | #### CSS部分 43 | 44 | - 让网页主题内容水平、垂直居中 45 | 46 | ```css 47 | display: flex; 48 | justify-content: center; 49 | align-items: center; 50 | ``` 51 | 52 | - 让文字具有阴影(后面再JS中会修改) 53 | 54 | ```css 55 | h1 { 56 | text-shadow: 10px 10px 0 rgba(0,0,0,1); 57 | /* text-shadow: 10px 10px 0 rgba(0,0,0,1), 10px 20px 0 rgba(200,0,0,1); */ 58 | font-size: 100px; 59 | } 60 | ``` 61 | 62 | #### JS部分 63 | 64 | - 首先创建三个变量,一个指向类名为`hero`的元素,一个指向`h1`元素,最后一个变量`walk`用来存储文字阴影距离原文字最大距离的一半。 65 | 66 | - 监听`hero`上的`mouseover`的事件,回调函数为`shadow` 67 | 68 | - 回调函数要实现的是,获取鼠标移动事件的位置`offsetX`和`offsetY`,通过一些公式将这两个位置变成新的位置信息,然后修改CSS样式上的text-shadow属性。 69 | 70 | - 首先设置变量`width`和`height`存储hero元素的宽高信息 71 | 72 | - 设置变量`x`和`y`存储鼠标移动事件的位置信息 73 | 74 | ```javascript 75 | const { offsetWidth: width, offsetHeight: height } = hero; 76 | let { offsetX: x, offsetY: y } = e; 77 | ``` 78 | 79 | 这里的写法采用了ES6的**[解构赋值写法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Operators/Destructuring_assignment)**,语句`let { offsetX: x, offsetY: y } = e;`等同于`let x = e.offsetX; let y = e.offsetY;` 80 | 81 | `offsetX/offsetY`:鼠标位置(相对于最近父元素的坐标) 82 | 83 | `offsetWidth/offsetHeight`:元素的宽高(width+padding+border) 84 | 85 | - 转换的公式如下所示,其中x/width是一个比例系数 86 | 87 | ```javascript 88 | const xWalk = Math.round((x / width * walk) - (walk / 2)); 89 | const yWalk = Math.round((y / height * walk) - (walk / 2)); 90 | ``` 91 | 92 | - 利用JS修改CSS中的text-shadow属性,具体来说有四个文字阴影,分布在text的四个角落 93 | 94 | ```javascript 95 | text.style.textShadow = ` 96 | ${xWalk}px ${yWalk}px 0 rgba(255,0,255,0.7), 97 | ${xWalk * -1}px ${yWalk}px 0 rgba(0,255,255,0.7), 98 | ${yWalk}px ${xWalk * -1}px 0 rgba(0,255,0,0.7), 99 | ${yWalk * -1}px ${xWalk}px 0 rgba(0,0,255,0.7) 100 | `; 101 | ``` 102 | 103 | - 程序写到这里会出现一个bug,当鼠标移动到h1时,文字阴影没有在文字中聚焦,这是因为鼠标移动到h1时,`offsetX`表示的是相对于h1的位置;当鼠标移动在hero上时,`offsetX`表示的是相对于hero的位置。 104 | 105 | 所以还需要添加在shadow函数中,首先需要做个**条件判断** 106 | 107 | ```javascript 108 | if (this !== e.target) { 109 | x = x + e.target.offsetLeft; 110 | y = y + e.target.offsetTop; 111 | } 112 | ``` 113 | 114 | - 写到这里程序就大体完成啦!具体代码我放在了[github](https://github.com/janice143/mouseMoveShadow)上。 115 | 116 | ## 项目补充 117 | 118 | #### [JavaScript中event.target与this区别](https://segmentfault.com/a/1190000023596603#:~:text=%E6%80%BB%E7%BB%93%EF%BC%9Athis%E4%B8%8Eevent.target,%E8%AF%A5%E4%BA%8B%E4%BB%B6%E7%9A%84%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E3%80%82) 119 | 120 | `this`一直指向函数的调用者,在本程序中,鼠标无论移动到`hero`上还是`h1`上,console.log(this)显示的一直是 121 | 122 | ```html 123 |
124 |

🔥WOAH!

125 |
126 | ``` 127 | 128 | 而`event.target`指向的是触发该事件的目标节点,在本程序中,鼠标移动到`hero`上,显示内容和上述一样,但是移动到`h1`上时,显示内容为 129 | 130 | ```html 131 |

🔥WOAH!

132 | ``` 133 | 134 | 因此,**this与event.target的区别为当含有事件冒泡时,this一直指向该函数的调用者,而event.target则指向触发该事件的目标节点** 135 | 136 | #### ES6 解构赋值 137 | 138 | **解构赋值**语法是一种 Javascript 表达式。通过**解构赋值,** 可以将属性/值从对象/数组中取出,赋值给其他变量。 139 | 140 | 以前,为变量赋值,只能直接指定值。 141 | 142 | ```javascript 143 | var a = 1; 144 | var b = 2; 145 | var c = 3; 146 | ``` 147 | 148 | ES6允许写成下面这样。 149 | 150 | ```javascript 151 | var [a, b, c] = [1, 2, 3]; 152 | ``` 153 | 154 | 上述为数组赋值。 155 | 156 | 对于对象赋值,可以写成在这样 157 | 158 | ```javascript 159 | var { foo, bar } = { foo: "aaa", bar: "bbb" }; 160 | ``` 161 | 162 | 如果变量名与属性名不一致,必须写成下面这样 163 | 164 | ```javascript 165 | let obj = { first: 'hello', last: 'world' }; 166 | let { first: f, last: l } = obj; 167 | f // 'hello' 168 | l // 'world' 169 | ``` 170 | 171 | #### 参考博客 172 | 173 | 1. [ES6 变量的解构赋值](http://caibaojian.com/es6/destructuring.html) 174 | 2. [JavaScript中event.target与this区别](https://segmentfault.com/a/1190000023596603#:~:text=%E6%80%BB%E7%BB%93%EF%BC%9Athis%E4%B8%8Eevent.target,%E8%AF%A5%E4%BA%8B%E4%BB%B6%E7%9A%84%E7%9B%AE%E6%A0%87%E8%8A%82%E7%82%B9%E3%80%82) 175 | 176 | > JS30的第16个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 177 | 178 | 179 | 180 | -------------------------------------------------------------------------------- /16 mouseMoveShadow/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | mouseMoveShadow 8 | 32 | 33 | 34 |
35 |

🔥WOAH!

36 |
37 | 75 | 76 | 77 | 78 | 79 | -------------------------------------------------------------------------------- /17 sortWithoutArticles/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Sort without Articles 17 3 | date: 2022-02-14 20:38:39 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第17天的“去除冠词排序”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 网页效果: https://janice143.github.io/sortWithoutArticles/ 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_SortWithoutArticles17.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 本项目首先给定了一个内容为band名字的列表`bands`,在JS中,对列表进行**特殊的排序**操作,得到新的列表最终以列表的形式显示到网页中。 20 | 21 | 其中特殊的排序操作,具体来说,是先去除列表元素中"a, an, the"的前缀,然后按照字母排序。排序的列表还是原列表,无需使列表元素去除特定前缀。 22 | 23 | #### 项目重点 24 | 25 | - 字符串的一些方法 26 | - `String.prototype.replace()` 27 | - `String.prototype.trim()` 28 | 29 | - 数组的一些方法 30 | - `Array.prototype.sort()` 31 | 32 | - 正则表达式 33 | - `/^(a |the |an )/i` 34 | 35 | ## 项目过程 36 | 37 | #### HTML部分 38 | 39 | - id 属性为`bands`的ul元素,列表内容在JS中添加 40 | 41 | #### JS部分 42 | 43 | - 首先提供一个已知列表bands 44 | 45 | - 将列表`bands`内容显示到网页中 46 | 47 | ```javascript 48 | document.querySelector('#bands').innerHTML = bands.map(band => `
  • ${band}
  • `).join(''); 49 | ``` 50 | 51 | - 显示已经实现,下一步我们需要对bands进行一些操作,得到的新列表再按照上述方法显示到网页中。**注意**:无需对原列表bands进行操作,也就是不用改变bands的值 52 | 53 | - 去除前缀 54 | 55 | ```javascript 56 | function strip(bandName){ 57 | return bandName.replace(/^(a |the |an )/i,'').trim(); 58 | } 59 | ``` 60 | 61 | - 排序 62 | 63 | ```javascript 64 | const sortedBands = bands.sort( 65 | function(a,b){ 66 | return strip(a) > strip(b) ? 1 : -1 67 | } 68 | ) 69 | ``` 70 | 71 | 程序写到这里就已经ok啦!本项目需要注意的是最后显示的内容还是原bands中的元素,但是排序方式要求去掉前缀后排序。 72 | 73 | 如果项目要求最后显示的内容是去除前缀的元素,那么下面的程序提供了一个实现思路: 74 | 75 | - 去除前缀 76 | 77 | ```javascript 78 | // 先将bands元素中开头为a|the|an的去掉前缀,返回新的bands 79 | function newBands(bands){ 80 | return bands.map(band => { 81 | return band.replace(/^(a |the |an )/i,'').trim(); 82 | }) 83 | } 84 | ``` 85 | 86 | - 排序 87 | 88 | ```javascript 89 | const sortedBands = newBands(bands).sort() 90 | ``` 91 | 92 | ## 项目补充 93 | 94 | #### [String.prototype.replace()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/replace) 95 | 96 | **`replace(pattern,replacement)`** 方法返回一个新字符串,该字符串由`replacement`替换**部分或所有**的`pattern`匹配项后的新字符串。 97 | 98 | `pattern`可以是一个字符串或者一个[正则表达式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/RegExp),`replacement`可以是一个字符串或者一个每次匹配都要调用的回调函数。 99 | 100 | 如果`pattern`是字符串,则仅替换第一个匹配项。 101 | 102 | 原字符串不会改变。 103 | 104 | ##### 语法 105 | 106 | ``` 107 | str.replace(regexp|substr, newSubStr|function) 108 | ``` 109 | 110 | #### [String.prototype.trim()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/String/Trim) 111 | 112 | 从一个字符串的两端删除**所有**空白字符。 113 | 114 | #### [Array.prototype.sort()](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 115 | 116 | ``` 117 | arr.sort([compareFunction]) 118 | ``` 119 | 120 | 主要讲讲有`compareFunction`的情况,该函数具有两个参数a,b。 121 | 122 | - 如果 `compareFunction(a, b)` 小于 0 ,那么 a 会被排列到 b 之前; 123 | 124 | - 如果 `compareFunction(a, b)` 等于 0 , a 和 b 的相对位置不变。 125 | 126 | - 如果 `compareFunction(a, b)` 大于 0 , b 会被排列到 a 之前。 127 | 128 | 例如比较数字,`compareFunction`函数可以简单的以 a 减 b,如下的函数将会将数组升序排列 129 | 130 | ``` 131 | function compareNumbers(a, b) { 132 | return a - b; 133 | } 134 | // 也可以这样些 135 | function compareNumbers(a, b) { 136 | return a > b ? 1 : -1; 137 | } 138 | ``` 139 | 140 | #### [正则表达式](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Guide/Regular_Expressions)的特殊字符 ^ 141 | 142 | 匹配输入的开始。如果多行标志被设置为 true,那么也匹配换行符后紧跟的位置。 143 | 144 | 例如,`/^A/` 并不会匹配 "an A" 中的 'A',但是会匹配 "An E" 中的 'A'。 145 | 146 | #### 参考博客 147 | 148 | 1. [正则表达式中的特殊字符](https://www.cnblogs.com/louby/p/4882148.html) 149 | 150 | > JS30的第17个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 151 | 152 | -------------------------------------------------------------------------------- /17 sortWithoutArticles/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Sort withoout articles 8 | 9 | 36 | 37 | 38 | 39 | 40 | 68 | 69 | -------------------------------------------------------------------------------- /18 timeWithReduce/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Time with Redece 18 3 | date: 2022-02-15 15:06:27 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第18天的“利用reduce进行时间累加”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 网页代码: https://janice143.github.io/timeWithReduce/ 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_TimeWithReduce18.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 项目首先在html中提供了若干个属性名为`data-time`的列表元素,`data-time`的值以00:00(分:秒)的格式显示。要求在JS中计算出`data-time`的总值,并且用?时?分?秒的格式显示结果。 20 | 21 | #### 项目重点 22 | 23 | - `Array.from()` 24 | - `timeNode.dataset.time` 25 | - `.split(':')` 26 | - `.map(parseFloat)` 27 | - `.reduce()` 28 | - `Math.floor()` 29 | 30 | ## 项目过程 31 | 32 | #### HTML部分 33 | 34 | - 若干个`li`标签,添加了`data-time`属性 35 | 36 | #### JS部分 37 | 38 | JS的整体思路是先获取所有的`data-time`的值,然后将所有值转化成秒,并且计算出的总秒数。根据总秒数得到对应的时、分、秒。 39 | 40 | 为了显示最后的结果,在本项目中国通过创建一个p元素来实现。 41 | 42 | - 获取所有的`data-time`元素,转化成数组,并存储在`timeNodes`变量中 43 | 44 | ```javascript 45 | const timeNodes = Array.from(document.querySelectorAll('[data-time]')); 46 | ``` 47 | 48 | - 从`timeNodes`中可以得到`data-time`的值 49 | 50 | ```javascript 51 | const seconds = timeNodes.map( 52 | timeNode => timeNode.dataset.time 53 | ) 54 | ``` 55 | 56 | - 将每个`data-time`的值转化成秒 57 | 58 | ```javascript 59 | .map( 60 | timeCode => { 61 | const [min, sec] = timeCode.split(':').map(parseFloat) 62 | // console.log(typeof(min),sec) 63 | return (min*60)+sec 64 | } 65 | ) 66 | ``` 67 | 68 | - 利用reduce方法累加得到总秒数 69 | 70 | ```javascript 71 | .reduce( 72 | (total,vidSecond) => total + vidSecond 73 | ) 74 | ``` 75 | 76 | - 根据总秒数,计算出时、分、秒 77 | 78 | ```javascript 79 | let leftSec = seconds; 80 | const hour = Math.floor(leftSec/3600); 81 | leftSec = leftSec % 3600; 82 | 83 | const min = Math.floor(leftSec/60); 84 | leftSec = leftSec % 60; 85 | ``` 86 | 87 | - 新建一个p元素,添加显示内容,最后挂载到网页上,显示结果 88 | 89 | ```javascript 90 | function display(hour,min,leftSec){ 91 | // 使用createElement创建元素 92 | const newTaskItem = document.createElement('p'); 93 | newTaskItem.className = 'total-time'; 94 | const html = 95 | ` 96 | 总播放时间为:${hour}小时${min}分${leftSec}秒。 97 | `; 98 | newTaskItem.innerHTML = html; 99 | document.querySelector('ul').before(newTaskItem); 100 | } 101 | ``` 102 | 103 | ## 项目补充 104 | 105 | [Array.from](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/from):将一个伪数组对象转化成数组。 106 | 107 | `.split()`:将一个`String`对象分割成子字符串数组 108 | 109 | #### `parseFloat(string)`将字符串解析为浮点数 110 | 111 | - 如果 `parseFloat` 在解析过程中遇到了正号(`+`)、负号(`-` )、数字(`0`-`9`)、小数点(`.`)、或者科学记数法中的指数(e 或 E)以外的字符,则它会忽略该字符以及之后的所有字符,返回当前已经解析到的浮点数。 112 | - 第二个小数点的出现也会使解析停止。 113 | - 参数首位和末位的空白符会被忽略。 114 | - 如果字符串的第一个字符不能被解析成为数字,则返回 `NaN`。 115 | - `parseFloat` 也可以解析并返回 [`Infinity`](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Infinity)。 116 | 117 | #### [reduce方法](https://developer.mozilla.org/zh-CN/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce) 118 | 119 | ##### 语法 120 | 121 | ```javascript 122 | arr.reduce(callback(accumulator, currentValue[, index[, array]])[, initialValue]) 123 | ``` 124 | 125 | `initialValue`为作为第一次调用 `callback`函数时的第一个参数的值。 如果没有提供初始值,则将使用数组中的第一个元素。 126 | 127 | ##### 示例 128 | 129 | ```javascript 130 | [0, 1, 2, 3, 4].reduce(function(accumulator, currentValue){ 131 | return accumulator + currentValue; 132 | }); // 10 133 | ``` 134 | 135 | > JS30的第18个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /18 timeWithReduce/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Time with Reduce 8 | 9 | 10 | 186 | 187 | 188 | 231 | -------------------------------------------------------------------------------- /19 webCamFun/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】WebCam Fun 19 3 | date: 2022-02-16 22:02:13 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第19天的“网络摄像头”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/19%20webCamFun/index.html 14 | 15 | ## 项目描述 16 | 17 | 通过访问网络摄像头,获取了当前摄像头拍摄的信息,作为`video`元素的内容。`video`元素中的视频信息被定时器一帧一帧绘制在`canvas`元素中。 18 | 19 | 提供了一个名为`take photo`的按钮用来抓取当前视频帧的内容,该内容最后通过通过创建`a`标签显示在网页中。 20 | 21 | #### 项目重点 22 | 23 | - `mediaDevices.getUserMedia` 24 | - `video`属性和方法 25 | - `video.videoWidth` 26 | - `video.srcObject ` 27 | - `setInterval` 28 | - `canvas.toDataURL` 29 | - `HTML DOM setAttribute(属性名,值)` 30 | - `canplay`事件 31 | 32 | ## 项目过程 33 | 34 | #### HTML部分 35 | 36 | - 最外层为类名为`photobooth`的`div`元素 37 | - 作为控件的`div`元素:take photo按钮 38 | - `canvas`元素用来绘制`video`的视频帧 39 | - `video`元素用来播放从网络摄像头获取的数据流 40 | - 音频`audio`标签 41 | 42 | #### JS部分 43 | 44 | JS的大致思路是: 45 | 46 | 1. 请求调用网络摄像头 47 | 2. 摄像头中的数据流给video元素 48 | 3. 在canvas上绘制video的内容 49 | 4. 点击take photo按钮获取当前canvas上的画面,显示到网页上 50 | 51 | - 编写`getVideo`函数:访问网络摄像头的权限,播放视频,放在`video`标签里 52 | 53 | ```javascript 54 | function getVideo(){ 55 | navigator.mediaDevices.getUserMedia({ video: true, audio: false }) 56 | .then(localMediaStream => { 57 | console.log(localMediaStream); 58 | video.srcObject = localMediaStream; 59 | video.play(); 60 | }) 61 | .catch(err => { 62 | console.error(`OH NO!!!`, err); 63 | }); 64 | } 65 | ``` 66 | 67 | - 把视频信息放到`canvas`中 68 | 69 | - 先获取视频的宽高信息,复制给canvas.width和height,保证canvas上显示视频画面完整(注意这里并不是设置canvas在网页上显示的宽高) 70 | 71 | - `canvas`流畅显示的机制是利用定时器不断获取当前`video`的内容 72 | 73 | - 利用`ctx.drawImage`实现绘制 74 | 75 | ```javascript 76 | function paintToCanvas(){ 77 | const width = video.videoWidth; 78 | const height = video.videoHeight; 79 | canvas.width = width; 80 | canvas.height = height; 81 | 82 | // canvas上显示的机制是利用定时器,将视频中当前帧的图像绘制在canvas上 83 | return setInterval(() => { 84 | ctx.drawImage(video, 0, 0, width, height); 85 | }, 16); 86 | } 87 | video.addEventListener('canplay', paintToCanvas); 88 | ``` 89 | 90 | - 编写take photo的点击函数 91 | 92 | - 播放音效 93 | - 获取当前canvas的data,变成图片 94 | - 创建元素,显示到网页中 95 | 96 | ```javascript 97 | function takePhoto(){ 98 | // 播放音频 99 | snap.currentTime = 0; 100 | snap.play(); 101 | 102 | // 获取当前canvas的data,变成图片 103 | const data = canvas.toDataURL('image/jpeg'); 104 | const link = document.createElement('a'); 105 | link.href = data; 106 | link.setAttribute('download', 'handsome'); 107 | link.innerHTML = `Handsome Man`; 108 | strip.insertBefore(link, strip.firstChild); 109 | } 110 | ``` 111 | 112 | ## 项目补充 113 | 114 | #### HTML 音频/视频 DOM `canplay` 事件 115 | 116 | 当浏览器能够开始播放指定的音频/视频时,触发canplay 事件 117 | 118 | #### 常用CSS的长度单位(相对/绝对) 119 | 120 | | 单位 | 名称 | 121 | | ----- | ------------------------------------------------------------ | 122 | | `em` | 在 font-size 中使用是相对于父元素的字体大小,在其他属性中使用是相对于自身的字体大小,如 width | 123 | | `rem` | 根元素的字体大小 | 124 | | `vw` | 视窗宽度的1% | 125 | | `vh` | 视窗高度的1% | 126 | | `px` | 像素 | 127 | 128 | #### [navigator.mediaDevices.getUserMedia](https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia) 129 | 130 | 提示用户允许一个媒体输入(视频、音频等),媒体输入会产生一个mediaStream包换了媒体信息。该方法返回一个promise 131 | 132 | **示例** 133 | 134 | ```javascript 135 | navigator.mediaDevices.getUserMedia(constraints) 136 | .then(function(stream) { 137 | /* use the stream */ 138 | }) 139 | .catch(function(err) { 140 | /* handle the error */ 141 | }); 142 | ``` 143 | 144 | 其中`constrains`参数可为`{ audio: true, video: true }` 145 | 146 | #### X:after 选择器 147 | 148 | 在元素内部的后面插入内容。常用来清楚浮动`clear-fix`。 149 | 150 | ```css 151 | .clearfix:after { 152 | content: ""; 153 | display: block; 154 | clear: both; 155 | visibility: hidden; 156 | font-size: 0; 157 | height: 0; 158 | } 159 | .clearfix { 160 | *display: inline-block; 161 | _height: 1%; 162 | } 163 | ``` 164 | 165 | 原理是使用`:after`伪类元素来在元素后增加一个空间,然后清除它。 166 | 167 | #### overflow-x 168 | 169 | overflow-x 属性规定是否对内容的左/右边缘进行裁剪,如果溢出元素内容区域的话。 170 | 171 | overflow-y 属性对上/下边缘的裁剪。 172 | 173 | #### a:nth-child(5n+1) 174 | 175 | 选择第1、6、11...个`a`标签 176 | 177 | #### [利用canvas操纵video](https://developer.mozilla.org/en-US/docs/Web/API/Canvas_API/Manipulating_video_using_canvas) 178 | 179 | 获取到video的每一帧内容后,绘制在canavs上。这是显示的第一步,除此之外,还可以做一些进阶,比如将每一帧画面。 180 | 181 | ```javascript 182 | let pixels = ctx.getImageData(0, 0, width, height); 183 | ``` 184 | 185 | #### JavaScript 定时器 186 | 187 | - `setTimeout()`:指定多久时间运行回调函数 188 | 189 | ```javascript 190 | setTimeout(() => { 191 | // 2 秒之后运行 192 | }, 2000) 193 | ``` 194 | 195 | `setTimeout` 会返回定时器的 id。 通常不使用它,但是可以保存此 id,并在要删除此安排的函数执行时清除它: 196 | 197 | ```javascript 198 | const id = setTimeout(() => { 199 | // 应该在 2 秒之后运行 200 | }, 2000) 201 | // 改变主意了 202 | clearTimeout(id) 203 | ``` 204 | 205 | - `setInterval()`:指定多少时间间隔运行一次回调函数 206 | 207 | ```javascript 208 | setInterval(() => { 209 | // 每 2 秒运行一次 210 | }, 2000) 211 | ``` 212 | 213 | **问题**:这里如何清除定时器呢?每次触发`video`的`canplay`事件,会执行`paintToCanvas`函数,而该函数可以返回定时器的`id`。如果要清除该定时器,怎么清除呢? 214 | 215 | ## 参考博客 216 | 217 | 1. [30个你必须记住的CSS选择符](https://yanhaijing.com/css/2014/01/04/the-30-css-selectors-you-must-memorize/) 218 | 2. [探索 JavaScript 定时器](http://nodejs.cn/learn/discover-javascript-timers) 219 | 3. [JS设置定时器和清除定时器](https://www.cnblogs.com/chenyoumei/p/12695381.html) 220 | 221 | > JS30的第19个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 222 | 223 | -------------------------------------------------------------------------------- /19 webCamFun/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Document 8 | 9 | 10 | 11 | 12 |
    13 |
    14 | 15 |
    16 | 17 | 18 |
    19 |
    20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /19 webCamFun/script.js: -------------------------------------------------------------------------------- 1 | const video = document.querySelector('.player'); 2 | const canvas = document.querySelector('.photo'); 3 | const ctx = canvas.getContext('2d'); 4 | const strip = document.querySelector('.strip'); 5 | const snap = document.querySelector('.snap'); 6 | 7 | // 访问网络摄像头的权限,播放视频,放在video标签里 8 | function getVideo(){ 9 | navigator.mediaDevices.getUserMedia({ video: true, audio: false }) 10 | .then(localMediaStream => { 11 | console.log(localMediaStream); 12 | video.srcObject = localMediaStream; 13 | video.play(); 14 | }) 15 | .catch(err => { 16 | console.error(`OH NO!!!`, err); 17 | }); 18 | 19 | } 20 | 21 | // 视频信息放到canvas中 22 | function paintToCanvas(){ 23 | const width = video.videoWidth; 24 | const height = video.videoHeight; 25 | canvas.width = width; 26 | canvas.height = height; 27 | console.log(height,width); 28 | 29 | // canvas上显示的机制是利用定时器,将视频中当前帧的图像绘制在canvas上 30 | return setInterval(() => { 31 | ctx.drawImage(video, 0, 0, width, height); 32 | // 取出每一帧的画面 33 | // let pixels = ctx.getImageData(0, 0, width, height); 34 | // mess with them 35 | // pixels = redEffect(pixels); 36 | 37 | // pixels = rgbSplit(pixels); 38 | // ctx.globalAlpha = 0.8; 39 | 40 | // pixels = greenScreen(pixels); 41 | // put them back 42 | // ctx.putImageData(pixels, 0, 0); 43 | }, 16); 44 | } 45 | 46 | // 47 | function takePhoto(){ 48 | // console.log("拍照了"); 49 | // 播放音频 50 | snap.currentTime = 0; 51 | snap.play(); 52 | 53 | // 获取当前canvas的data,变成图片 54 | const data = canvas.toDataURL('image/jpeg'); 55 | const link = document.createElement('a'); 56 | link.href = data; 57 | link.setAttribute('download', 'handsome'); 58 | link.innerHTML = `Handsome Man`; 59 | strip.insertBefore(link, strip.firstChild); 60 | 61 | } 62 | getVideo(); 63 | 64 | video.addEventListener('canplay', paintToCanvas); 65 | 66 | 67 | 68 | -------------------------------------------------------------------------------- /19 webCamFun/snap.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/19 webCamFun/snap.mp3 -------------------------------------------------------------------------------- /19 webCamFun/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | 5 | *, *:before, *:after { 6 | box-sizing: inherit; 7 | } 8 | 9 | html { 10 | font-size: 10px; 11 | background: #ffc600; 12 | } 13 | 14 | .photobooth { 15 | background: white; 16 | /* max-height: 80vh; */ 17 | max-width: 100rem; 18 | margin: 1rem auto; 19 | border-radius: 2px; 20 | } 21 | 22 | /*clearfix 清除浮动*/ 23 | .photobooth:after { 24 | content: ''; 25 | display: block; 26 | clear: both; 27 | border:1px solid red; 28 | } 29 | 30 | 31 | .photo { 32 | width: 100%; 33 | /* margin: 0 auto; */ 34 | 35 | 36 | } 37 | 38 | .player { 39 | position: absolute; 40 | top: 20px; 41 | right: 20px; 42 | width:200px; 43 | } 44 | 45 | /* 46 | Strip! 47 | */ 48 | 49 | .strip { 50 | padding: 2rem; 51 | } 52 | 53 | .strip img { 54 | width: 100px; 55 | overflow-x: scroll; 56 | padding: 0.8rem 0.8rem 2.5rem 0.8rem; 57 | box-shadow: 0 0 3px rgba(0,0,0,0.2); 58 | background: white; 59 | } 60 | 61 | .strip a:nth-child(5n+1) img { transform: rotate(10deg); } 62 | .strip a:nth-child(5n+2) img { transform: rotate(-2deg); } 63 | .strip a:nth-child(5n+3) img { transform: rotate(8deg); } 64 | .strip a:nth-child(5n+4) img { transform: rotate(-11deg); } 65 | .strip a:nth-child(5n+5) img { transform: rotate(12deg); } 66 | -------------------------------------------------------------------------------- /20 speechDetection/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Speech Detetion 20 3 | date: 2022-02-22 20:56:50 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第20天的“语音检测”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/20%20speechDetection/index.html 14 | 15 | ## 项目描述 16 | 17 | 本项目是一个语音识别系统,网页首先会向用户请求麦克风权限,允许后可识别出用户的speech(语言为每个英语`en-US'`),并显示在网页中。 18 | 19 | 本项目用到的语音识别系统是`Web Speech API`,只能在 Chrome浏览器上使用,而且功能也一直在完善中,因此,本项目只是提供一种语音识别系统的解决思路,以便参考。 20 | 21 | #### 项目重点 22 | 23 | - `Web Speech API` 24 | - [`SpeechRecognition`](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition) 接口 25 | - `SpeechRecognition.interimResults` 26 | - `SpeechRecognition.lang` 27 | - `SpeechRecognition.start()` 28 | - `new SpeechRecognition()` 29 | - `result`事件 30 | - `e.results` 31 | - `result.transcript` 32 | - `e.results[0].isFinal` 33 | 34 | - `p.textContent ` 35 | - `end`事件 36 | 37 | ## 项目过程 38 | 39 | #### HTML部分 40 | 41 | 只有一个`div`元素,可编辑`contenteditable` 42 | 43 | ```html 44 |
    45 | ``` 46 | 47 | #### JS部分 48 | 49 | JS的大致思路是: 50 | 51 | 1. 添加Chrome support 52 | 2. 定义语音识别实例 53 | 3. 开启语音识别功能 54 | 4. 监听`result`事件,实时获取捕获到的speech,并通过创建元素的方法显示到网页中 55 | 5. 监听`end`事件,当语音捕获结束后,重新开启语音识别功能 56 | 57 | - Chrome support 58 | 59 | [`SpeechRecognition`](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition) 接口只能在 Chrome浏览器上使用,,因此需要适配Chrome浏览器的对象以及未来其他浏览器也能使用的一些修正 60 | 61 | ```javascript 62 | window.SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; 63 | ``` 64 | 65 | - 定义语音识别实例 66 | 67 | ```javascript 68 | const recognition = new SpeechRecognition(); 69 | ``` 70 | 71 | - 创建一个`p`元素,后面可以讲识别到的语音放进去 72 | 73 | ```javascript 74 | const p = document.createElement('p'); 75 | const words = document.querySelector('.words'); 76 | words.appendChild(p); 77 | ``` 78 | 79 | - 打开语音识别功能,监听`result`事件,实时获取捕获到的speech,并显示到网页中 80 | 81 | ```javascript 82 | recognition.addEventListener('result',e => { 83 | const transcript = Array.from(e.results) 84 | .map(result => result[0]) 85 | .map(result => result.transcript) 86 | .join(''); 87 | p.textContent = transcript; 88 | // console.log(e.results[0].transcript) 89 | if (e.results[0].isFinal) { 90 | p = document.createElement('p'); 91 | words.appendChild(p); 92 | } 93 | }); 94 | 95 | recognition.start(); // 打开语音功能 96 | ``` 97 | 98 | - 监听`end`事件,当语音捕获结束后,重新开启语音识别功能 99 | 100 | ```javascript 101 | recognition.addEventListener('end', recognition.start); 102 | ``` 103 | 104 | ## 项目补充 105 | 106 | #### [Web Speech API](https://developer.mozilla.org/en-US/docs/Web/API/Web_Speech_API/Using_the_Web_Speech_API) 语音识别和语音输出 107 | 108 | 主要的语音识别接口是[`SpeechRecognition`](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition) 接口,只能在 Chrome浏览器上使用 109 | 110 | [`SpeechRecognition`](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition) 接口的一些属性: 111 | 112 | - [`SpeechRecognition.interimResults`](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/interimResults):设置语音识别系统是否返回中间结果,还是最终结果 113 | 114 | - [`SpeechRecognition.lang`](https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/lang): 设置语音识别系统的语言 115 | 116 | #### innerText、textContent和innerHTML三者的区别 117 | 118 | `innerText`、`textContent`和`innerHTML`可以设置标签中的文本内容。 119 | 120 | **不同点** 121 | 122 | `innerHTML`可以将内容中的标签为标签,而其他两个则不行,只是纯文本 123 | 124 | `innerText`,`textContent`获取的是该标签和该标签下子标签中的文本内容 125 | 126 | ```html 127 | 134 | 152 | ``` 153 | 154 | #### let var const区别 155 | 156 | var 声明会变量提升 157 | 158 | let 块级作用域,声明不会变量提升 159 | 160 | const 块级作用域声明的变量为常量,值不可修改 161 | 162 | **更多内容见参考博客[2-4]** 163 | 164 | #### CSS position 165 | 166 | | 值 | | 167 | | -------- | ------------------------------------------------------------ | 168 | | relative | 相对自己原来(正常文档流)的位置 | 169 | | absolute | 脱离文档流,相对于最近的已定位父元素 | 170 | | fixed | 脱离文档流,相对于浏览器窗口是固定位置 | 171 | | sticky | 基于用户的滚动位置来定位。行为就像 relative,而当页面滚动超出目标区域时,它的表现就像 fixed,固定在目标位置。 | 172 | | static | HTML 元素的默认值,即没有定位,遵循正常的文档流对象。 | 173 | 174 | ## 参考博客 175 | 176 | 1. [innerText、textContent和innerHTML三者的区别](https://juejin.cn/post/6844903684317380616) 177 | 2. [一看就懂的var、let、const三者区别](https://juejin.cn/post/6925641096152399880) 178 | 3. [var、let和const的区别详解](https://www.cnblogs.com/JobsOfferings/p/varLetConst.html) 179 | 4. [块作用域](https://tsejx.github.io/javascript-guidebook/core-modules/executable-code-and-execution-contexts/compilation/blocks-as-scopes) 180 | 181 | > JS30的第20个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 182 | 183 | -------------------------------------------------------------------------------- /20 speechDetection/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Speech Detection 8 | 44 | 45 | 46 |
    47 |
    48 | 80 | 81 | -------------------------------------------------------------------------------- /21 geoLocation/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Geolocation 21 3 | date: 2022-02-24 21:43:51 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第21天的“地理位置”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/21%20geoLocation/index.html 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_geoLocation21.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 本项目是一个可视化的指南,利用网络地址位置`Web Geolocation API`获取的地理位置和速度。 20 | 21 | 本项目的JS代码相对比较简单,但是由于电脑一般没有速度及方向传感器,所以实际的功能并没显示出来,只是提供了一种实现途径。 22 | 23 | #### 项目重点 24 | 25 | - `Geolocation.watchPosition()` API 26 | - `.coords.speed` 27 | - `.coords.heading` 28 | - CSS `radial-gradient` 29 | - `background-attachment`设置背景图是否固定不到 30 | 31 | ## 项目过程 32 | 33 | #### HTML部分 34 | 35 | - `svg`图片元素 36 | - `h1`元素 37 | - 类名为`speed-value` `span`标签 38 | - 类名为`speed-unit` `span`标签 39 | 40 | #### CSS部分 41 | 42 | - 设置背景图片 43 | 44 | `radial-gradient`由圆心向外的径向的颜色渐变 45 | 46 | ```css 47 | radial-gradient(rgba(255,255,255,.1) 15%, transparent 20%) 0 1px 48 | ``` 49 | 50 | #### JS部分 51 | 52 | JS的大致思路是: 53 | 54 | 1. 请求调用[Geolocation接口](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) 55 | 2. 获取当前的地理位置信息data 56 | 3. 显示速度信息 `data.coords.speed` 57 | 4. 改变页面中指南针的朝向 `data.coords.heading` 58 | 59 | 具体代码如下: 60 | 61 | ```javascript 62 | navigator.geolocation.watchPosition((data) => { 63 | console.log(data); 64 | speed.textContent = data.coords.speed; 65 | arrow.style.transform = `rotate(${data.coords.heading}deg)`; 66 | }, (err) => { 67 | console.error(err); 68 | }); 69 | 70 | ``` 71 | 72 | ## 项目补充 73 | 74 | #### [Geolocation接口](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation) :获取设备的地理位置信息 75 | 76 | 方法 1:`Geolocation.getCurrentPosition()` 获取当前的位置信息 77 | 78 | 方法 2:[`Geolocation.watchPosition()`](https://developer.mozilla.org/en-US/docs/Web/API/Geolocation/watchPosition) 返回位置变化后的最新信息 79 | 80 | 方法 3:`Geolocation.clearWatch()` 删除使用`watchPosition()`后的句柄 81 | 82 | > JS30的第21个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 83 | 84 | -------------------------------------------------------------------------------- /21 geoLocation/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | geoLocation 8 | 51 | 52 | 53 | 54 |

    55 | 0 56 | KM/H 57 |

    58 | 72 | 73 | -------------------------------------------------------------------------------- /22 linkHighlighter/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Link Highlighter 22 3 | date: 2022-02-25 14:10:10 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第22天的“链接高亮显示”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/22%20linkHighlighter/index.html 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_HighLighter.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 本项目页面主要有一个导航栏菜单和正文内容组成。页面实现的效果是:当鼠标进入`a`标签时,背景颜色以白色高亮显示,当鼠标移到下一个`a`标签时,白色高亮块上一个`a`标签中移动下来。 20 | 21 | #### 项目重点 22 | 23 | - `Element.getBoundingClientRect()` 24 | - `window.scrollY` 25 | - `mouseenter`事件 26 | 27 | ## 项目过程 28 | 29 | #### HTML部分 30 | 31 | - `nav`标签包裹的导航栏菜单 32 | - `ul`标签 33 | - 5个`li`标签,为菜单内容 34 | 35 | - 类名为`wrapper`的`div`标签包含了正文内容 36 | - 链接用`a`标签标记,在JS中要实现高亮显示 37 | 38 | #### CSS部分 39 | 40 | 使用如下技巧将外边距和内边距重置为零 41 | 42 | ```css 43 | *, *:before, *:after { 44 | box-sizing: inherit; 45 | } 46 | ``` 47 | 48 | 高亮块的CSS样式,基本思路是加上绝对定位(相对于最近定位的父元素定位,在这里父元素是body),通过在JS中改变`top`和`left`以及`width`和`height`属性,来呈现不同链接选中的状态。 49 | 50 | ```css 51 | .highlight{ 52 | transition: all 0.2s; 53 | border-bottom: 2px solid white; 54 | position: absolute; 55 | top: 0; 56 | background: white; 57 | left: 0; 58 | z-index: -1; 59 | border-radius: 20px; 60 | display: block; 61 | box-shadow: 0 0 10px rgba(0,0,0,0.2); 62 | } 63 | ``` 64 | 65 | #### JS部分 66 | 67 | JS的大致思路是: 68 | 69 | 1. 获取链接标签,以及创建`span`标签,用来添加`highlight`样式 70 | 71 | ```javascript 72 | const triggers = document.querySelectorAll('a'); 73 | const highlight = document.createElement('span'); 74 | highlight.classList.add('highlight'); 75 | document.body.appendChild(highlight); 76 | ``` 77 | 78 | 2. 获取当前鼠标进入的链接元素的位置信息 79 | 80 | 3. 修改类名为`highlight`的样式 81 | 82 | 4. 给`a`标签添加鼠标进入`mouseenter`事件 83 | 84 | - 获取链接标签,以及创建`span`标签,用来添加`highlight`样式 85 | 86 | ```javascript 87 | function highlightter(){ 88 | const link = this.getBoundingClientRect(); 89 | console.log(link); 90 | const linkCoordinates ={ 91 | width:link.width, 92 | height:link.height, 93 | top:link.top+window.scrollY, 94 | left:link.left+window.scrollX 95 | }; 96 | highlight.style.width = `${linkCoordinates.width}px`; 97 | highlight.style.height = `${linkCoordinates.height}px`; 98 | highlight.style.transform = `translate(${linkCoordinates.left}px,${linkCoordinates.top}px)`; 99 | } 100 | triggers.forEach(a => a.addEventListener('mouseenter', highlightter)); 101 | ``` 102 | 103 | ## 项目补充 104 | 105 | #### [Element.getBoundingClientRect()](https://developer.mozilla.org/en-US/docs/Web/API/Element/getBoundingClientRect) 106 | 107 | 返回一个DOMRect对象,包含了元素的大小、相对于视口的位置信息。 108 | 109 | **DOMRect相关只读属性** 110 | 111 | | Attribute | Description | 112 | | --------- | ------------------------------------------- | 113 | | height | 矩形盒子的高度 | 114 | | width | 矩形盒子的宽度 | 115 | | top | Y 轴,相对于视口原点(viewport origin)顶部 | 116 | | left | X 轴,相对于视口原点左侧 | 117 | | bottom | Y 轴,相对于视口原点底部 | 118 | | right | X 轴,相对于视口原点右侧 | 119 | | x | 盒子左上角位置的X轴横坐标 | 120 | | y | 盒子左上角位置的Y轴横坐标 | 121 | 122 | #### window.scrollY 123 | 124 | 鼠标滑动的垂直距离 125 | 126 | window.scrollX 鼠标滑动的水平距离 127 | 128 | #### mousemove, mouseenter 和mouseover区别 129 | 130 | mousemove:鼠标指针进入`div`以及其子元素时触发; 131 | 132 | mouseenter:鼠标指针进入`div`时触发; 133 | 134 | mouseover:鼠标每次滑过`div`时触发 135 | 136 | 点击此链接体验三者的效果 👉 [体验](https://www.w3schools.com/jquery/tryit.asp?filename=tryjquery_event_mouseenter_mouseover#:~:text=mouseenter%20and%20mouseover.-,The%20mouseover%20event%20triggers%20when%20the%20mouse%20pointer%20enters%20the,moved%20over%20the%20div%20element.) 137 | 138 | > JS30的第22个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 139 | 140 | 141 | 142 | -------------------------------------------------------------------------------- /22 linkHighlighter/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Link Highlighter 8 | 62 | 63 | 64 | 73 |
    74 |

    Lorem ipsum dolor sit amet, consectetur adipisicing elit. Est explicabo unde natus necessitatibus esse obcaecati distinctio, aut itaque, qui vitae!

    75 |

    Aspernatur sapiente quae sint soluta modi, atque praesentium laborum pariatur earum quaerat cupiditate consequuntur facilis ullam dignissimos, aperiam quam veniam.

    76 |

    Cum ipsam quod, incidunt sit ex tempore placeat maxime corrupti possimus veritatis ipsum fugit recusandae est doloremque? Hic, quibusdam, nulla.

    77 |

    Esse quibusdam, ad, ducimus cupiditate nulla, quae magni odit totam ut consequatur eveniet sunt quam provident sapiente dicta neque quod.

    78 |

    Aliquam dicta sequi culpa fugiat consequuntur pariatur optio ad minima, maxime odio, distinctio magni impedit tempore enim repellendus repudiandae quas!

    79 |
    80 | 102 | 103 | -------------------------------------------------------------------------------- /23 speechSynthesis/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Speech Synthesis 8 | 9 | 96 | 97 | 98 |
    99 |

    The voiceinator 5000

    100 | 101 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 |
    115 | 160 | 161 | 162 | 163 | -------------------------------------------------------------------------------- /24 stickyNav/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Sticky Nav 24 3 | date: 2022-02-26 19:46:22 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第23天的“语音合成”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/24%20stickyNav/index.html 14 | 15 | ![](https://github.com/janice143/myblog.github.io/blob/master/images/js30_Nav.png?raw=true) 16 | 17 | ## 项目描述 18 | 19 | 本项目主要亮点在于实现导航栏的位置粘贴固定,此外,当鼠标滚动导航栏(本来)消失时,除了要固定导航栏,还要加一点其他样式。 20 | 21 | 技术要点是通过`scroll`事件中,判断窗口位置和当前导航栏的位置,如果前者大于或等于后者,则通过添加一个类名`'fixed-nav'`(其样式事先在CSS中完善),来实现上述两亮点。 22 | 23 | 当前者小于后者,则移除`'fixed-nav'`类名。 24 | 25 | #### 项目重点 26 | 27 | - scroll事件 28 | - `window.scrollY `>= `topOfNav` 29 | - `nav.offsetTop` 30 | - `document.body.classList.add()` 31 | - `document.body.classList.remove()` 32 | - `position: fixed` 33 | 34 | ## 项目过程 35 | 36 | #### HTML部分 37 | 38 | - `header`标题 39 | - `nav`导航栏菜单 40 | - `.site-wrap`正文 41 | 42 | #### JS部分 43 | 44 | JS的大致思路是: 45 | 46 | 1. 监听页面滚动事件 47 | 2. 判断页面当前滚动位置,如果大于等于导航栏距离窗口顶部位置时,则通过添加类名 48 | 3. 否则移除类名 49 | 4. 该类名的样式在CSS中设置好,原理在于position设置为fix 50 | 51 | ```javascript 52 | const nav = document.querySelector('#main'); 53 | let topOfNav = nav.offsetTop; 54 | function fixNav() { 55 | if (window.scrollY >= topOfNav) { 56 | // document.body.style.paddingTop = nav.offsetHeight + 'px'; 57 | document.body.classList.add('fixed-nav'); 58 | } else { 59 | document.body.classList.remove('fixed-nav'); 60 | document.body.style.paddingTop = 0; 61 | } 62 | } 63 | 64 | window.addEventListener('scroll', fixNav); 65 | ``` 66 | 67 | 项目补充 68 | 69 | #### text-align: justify 70 | 71 | 均匀分布,有点像word软件的“两端对齐”。 72 | 73 | > JS30的第24个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 74 | -------------------------------------------------------------------------------- /24 stickyNav/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | background: #eeeeee; 4 | font-family: 'helvetica neue'; 5 | font-size: 20px; 6 | font-weight: 200; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | } 12 | 13 | *, *:before, *:after { 14 | box-sizing: inherit; 15 | } 16 | .site-wrap{ 17 | max-width: 700px; 18 | margin: 70px auto; 19 | background: white; 20 | padding: 40px; 21 | text-align: justify; 22 | box-shadow: 0 0 10px 5px rgba(0,0,0,0.1); 23 | transition:transform 0.5s; 24 | } 25 | 26 | header { 27 | text-align: center; 28 | height: 50vh; 29 | background: url(https://source.unsplash.com/GKN6rpDr0EI/960x640) bottom center no-repeat; 30 | background-size: cover; 31 | display: flex; 32 | align-items: center; 33 | justify-content: center; 34 | } 35 | 36 | h1 { 37 | /* color: white; */ 38 | font-size: 7vw; 39 | text-shadow: 3px 4px 0 rgba(0,0,0,0.2); 40 | } 41 | 42 | nav { 43 | background: black; 44 | top: 0; 45 | width: 100%; 46 | transition:all 0.5s; 47 | position: relative; 48 | z-index: 1; 49 | /* position: sticky; 50 | top: -1px; */ 51 | /* padding-top:0px; */ 52 | } 53 | 54 | nav ul { 55 | margin: 0; 56 | padding: 0; 57 | list-style: none; 58 | display: flex; 59 | } 60 | 61 | nav li { 62 | flex: 1; 63 | text-align: center; 64 | display: flex; 65 | justify-content: center; 66 | align-items: center; 67 | } 68 | 69 | li.logo { 70 | max-width: 0; 71 | overflow: hidden; 72 | background: white; 73 | transition: all 0.5s; 74 | font-weight: 600; 75 | font-size: 30px; 76 | } 77 | 78 | li.logo a { 79 | color: black; 80 | } 81 | 82 | nav a { 83 | text-decoration: none; 84 | padding: 20px; 85 | display: inline-block; 86 | color: white; 87 | transition: all 0.2s; 88 | text-transform: uppercase; 89 | } 90 | .fixed-nav li.logo { 91 | max-width: 500px; 92 | } 93 | body.fixed-nav nav { 94 | position: fixed; 95 | box-shadow: 0 5px 0 rgba(0,0,0,0.1); 96 | } 97 | body.fixed-nav .site-wrap { 98 | transform: scale(1); 99 | } -------------------------------------------------------------------------------- /25 eventCapture/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Event Capture 25 3 | date: 2022-02-27 14:09:03 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第25天的“语音合成”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/25%20eventCapture/index.html 14 | 15 | ## 项目描述 16 | 17 | 本项目主要目的是理解事件的捕获、传播、冒泡、单次触发等机制。首先提供3个尺寸颜色不一的`
    `元素,通过点击事件来理解上述内容。 18 | 19 | #### 项目重点 20 | 21 | - `capture` 22 | - `once` 23 | 24 | ## 项目过程 25 | 26 | #### JS部分 27 | 28 | - **冒泡** 29 | 30 | 当点击某个`div`时,自该`div`起以及其外层的`div`也将监听到点击事件。 31 | 32 | 例如,点击第3个div(最内层的),控制台显示的结果是three,two,one。 33 | 34 | ```javascript 35 | const divs = document.querySelectorAll('div'); 36 | function textLog(){ 37 | console.log(this.classList.value); 38 | } 39 | divs.forEach(div=>div.addEventListener('click',textLog)); 40 | ``` 41 | 42 | - **捕获** 43 | 44 | 点击某个`div`时,从不具体的`div`元素到最具体的元素(被点击的元素)从上到下监听到点击事件。 45 | 46 | 例如,点击第3个`div`(最内层的),控制台显示的结果是one,two,three。 47 | 48 | ```javascript 49 | divs.forEach(div=>div.addEventListener('click',textLog,{ 50 | capture: true 51 | })); 52 | ``` 53 | 54 | - 停止事件继续传递 55 | 56 | 在冒泡的基础上,加上`e.stopPropagation();`来设置不再继续传播 57 | 58 | ```javascript 59 | function textLog(e){ 60 | console.log(this.classList.value); 61 | e.stopPropagation(); 62 | } 63 | divs.forEach(div=>div.addEventListener('click',textLog,{ 64 | capture: false 65 | })); 66 | ``` 67 | 68 | - Once:允许事件触发一次,之后相当于`removeEventListener`。 69 | 70 | ```javascript 71 | function textLog(e){ 72 | console.log(this.classList.value); 73 | } 74 | divs.forEach(div=>div.addEventListener('click',textLog,{ 75 | capture: false, 76 | once:true 77 | })); 78 | ``` 79 | 80 | ## 项目补充 81 | 82 | #### 事件冒泡 83 | 84 | 事件开始由最精确的元素触发,逐级向上传播到其他节点 85 | 86 | #### 事件捕获 87 | 88 | 事件由不太具体的节点传播到最具体的节点 89 | 90 | ## 参考博客 91 | 92 | 1. [事件流](https://tsejx.github.io/javascript-guidebook/document-object-model/events/event-flow) 93 | 94 | JS30的第25个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 95 | -------------------------------------------------------------------------------- /25 eventCapture/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | eventCapture 8 | 9 | 10 | 11 |
    1 12 |
    2 13 |
    3 14 |
    15 |
    16 |
    17 | 18 | 44 | 56 | 57 | -------------------------------------------------------------------------------- /26 stripAlongNav/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | stripAlongNav 8 | 9 | 10 | 11 |

    Cool

    12 | 78 | 112 | 113 | 114 | -------------------------------------------------------------------------------- /26 stripAlongNav/style.css: -------------------------------------------------------------------------------- 1 | html{ 2 | box-sizing: border-box; 3 | font-family: "Arial Rounded MT Bold","Helvetica Rounded",Arial,sans-serif; 4 | } 5 | *,*:before,*:after{ 6 | box-sizing: inherit; 7 | } 8 | body{ 9 | margin: 0; 10 | min-height: 100vh; 11 | background: 12 | linear-gradient(45deg, hsla(340, 100%, 55%, 1) 0%, hsla(340, 100%, 55%, 0) 70%), 13 | linear-gradient(135deg, hsla(225, 95%, 50%, 1) 10%, hsla(225, 95%, 50%, 0) 80%), 14 | linear-gradient(225deg, hsla(140, 90%, 50%, 1) 10%, hsla(140, 90%, 50%, 0) 80%), 15 | linear-gradient(315deg, hsla(35, 95%, 55%, 1) 100%, hsla(35, 95%, 55%, 0) 70%); 16 | } 17 | h2{ 18 | margin-top: 0; 19 | padding-top: 0.8em; 20 | } 21 | nav{ 22 | position: relative; 23 | perspective: 600px; 24 | } 25 | .cool > li > a { 26 | color: yellow; 27 | text-decoration: none; 28 | font-size: 20px; 29 | background: rgba(0,0,0,0.2); 30 | padding: 10px 20px; 31 | display: inline-block; 32 | margin: 20px; 33 | border-radius: 5px; 34 | } 35 | nav ul{ 36 | list-style: none; 37 | margin:0; 38 | padding: 0; 39 | display: flex; 40 | justify-content: center; 41 | } 42 | .cool >li{ 43 | position: relative; 44 | display:flex; 45 | justify-content: center; 46 | } 47 | .dropdown{ 48 | opacity: 0; 49 | position: absolute; 50 | overflow: hidden; 51 | padding: 20px; 52 | top:-20px; 53 | border-radius: 2px; 54 | transition:all 0.5s; 55 | transform: translateY(100px); 56 | will-change: opacity; 57 | display: none; 58 | } 59 | 60 | .trigger-enter .dropdown { 61 | display: block; 62 | } 63 | 64 | .trigger-enter-active .dropdown { 65 | opacity: 1; 66 | } 67 | 68 | .dropdownBackground { 69 | width: 100px; 70 | height: 100px; 71 | position: absolute; 72 | background: #fff; 73 | border-radius: 4px; 74 | box-shadow: 0 50px 100px rgba(50,50,93,.1), 0 15px 35px rgba(50,50,93,.15), 0 5px 15px rgba(0,0,0,.1); 75 | transition: all 0.3s, opacity 0.1s, transform 0.2s; 76 | transform-origin: 50% 0; 77 | display: flex; 78 | justify-content: center; 79 | opacity: 0; 80 | } 81 | 82 | .dropdownBackground.open { 83 | opacity: 1; 84 | } 85 | 86 | .arrow { 87 | position: absolute; 88 | width: 20px; 89 | height: 20px; 90 | display: block; 91 | background: white; 92 | transform: translateY(-50%) rotate(45deg); 93 | } 94 | 95 | .bio { 96 | min-width: 500px; 97 | display: flex; 98 | justify-content: center; 99 | align-items: center; 100 | line-height: 1.7; 101 | } 102 | 103 | .bio img { 104 | float: left; 105 | margin-right: 20px; 106 | } 107 | 108 | .courses { 109 | min-width: 300px; 110 | } 111 | 112 | .courses li { 113 | padding: 10px 0; 114 | display: block; 115 | border-bottom: 1px solid rgba(0,0,0,0.2); 116 | } 117 | 118 | .dropdown a { 119 | text-decoration: none; 120 | color: #ffc600; 121 | } 122 | 123 | a.button { 124 | background: black; 125 | display: block; 126 | padding: 10px; 127 | color: white; 128 | margin-bottom: 10px; 129 | } 130 | 131 | /* Matches Twitter, TWITTER, twitter, tWitter, TWiTTeR... */ 132 | .button[href*=twitter] { background: #019FE9; } 133 | .button[href*=facebook] { background: #3B5998; } 134 | .button[href*=courses] { background: #ffc600; } 135 | 136 | -------------------------------------------------------------------------------- /27 clickAndDrag/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Click and drag 27 3 | date: 2022-03-05 15:13:56 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第26天的“内容悬浮显示”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/27%20clickAndDrag/index.html 14 | 15 | ## 项目描述 16 | 17 | 本项目为一个横向条幅,实现的特效是鼠标点击实现横向拖拽。 18 | 19 | #### 项目重点 20 | 21 | - `e.pageX ` 22 | - `.scrollLeft` 23 | - `.offsetLeft` 24 | - `e.preventDefault()` 25 | 26 | ## 项目过程 27 | 28 | #### JS部分 29 | 30 | 编程思路为 31 | 32 | 1. 监听最外层items元素的`mousedown`事件,触发后添加`active`类名,此类名具有一定的CSS特效。记录此时的起点`startX` 以及 左边滚动的位置`scrollLeft`。 33 | 2. 监听鼠标移动`mousemove`事件,给`scrollLeft`赋值,即可调整元素在水平方向滚动的位置。 34 | 3. 鼠标离开`mouseleave`和不点击`mouseup`事件触发时,去掉`active`类名。 35 | 36 | - `mousedown`事件 37 | 38 | ```javascript 39 | slider.addEventListener('mousedown', (e) => { 40 | isDown = true; 41 | slider.classList.add('active'); 42 | startX = e.pageX - slider.offsetLeft; 43 | scrollLeft = slider.scrollLeft; 44 | }); 45 | ``` 46 | 47 | - `mousemove`事件 48 | 49 | ```javascript 50 | 51 | slider.addEventListener('mousemove', (e) => { 52 | if (!isDown) return; // stop the fn from running 53 | e.preventDefault(); 54 | const x = e.pageX - slider.offsetLeft; 55 | const walk = (x - startX) * 3; 56 | slider.scrollLeft = scrollLeft - walk; 57 | }); 58 | ``` 59 | 60 | - `mouseleave`和不点击`mouseup`事件 61 | 62 | ```javascript 63 | slider.addEventListener('mouseleave', () => { 64 | isDown = false; 65 | slider.classList.remove('active'); 66 | }); 67 | 68 | slider.addEventListener('mouseup', () => { 69 | isDown = false; 70 | slider.classList.remove('active'); 71 | }); 72 | ``` 73 | 74 | ## 项目补充 75 | 76 | #### 一些CSS样式 77 | 78 | 1. `overflow`属性:控制元素溢出时的特性,主要有一下几个值 79 | - `visible` 默认值,溢出也是可见的,没有被裁剪 80 | - `hidden` 溢出的内容被裁剪,并且看不到 81 | - `scroll `溢出的内容被裁剪,但是可以添加滑块scrollbar看到溢出内容 82 | - `auto` 有点像`scroll` 83 | 84 | 2. `white-space`空格处理 85 | - `normal`表示合并空格,多个相邻空格合并成一个空格 86 | - `nowrap`不换行,经常和`overflow`,`text-overflow`一起使用 87 | - `pre`保留空格不换行,有几个空格算几个空格显示 88 | - `pre-wrap`的作用是保留空格 89 | 90 | 3. user-select:禁止用户用鼠标在页面上选中文字、图片等,也就是,让页面内容不可选 91 | 92 | > JS30的第27个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 93 | -------------------------------------------------------------------------------- /27 clickAndDrag/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Click and drag 8 | 9 | 10 | 11 |
    12 |
    01
    13 |
    02
    14 |
    03
    15 |
    04
    16 |
    05
    17 |
    06
    18 |
    07
    19 |
    08
    20 |
    09
    21 |
    10
    22 |
    11
    23 |
    12
    24 |
    13
    25 |
    14
    26 |
    15
    27 |
    16
    28 |
    17
    29 |
    18
    30 |
    19
    31 |
    20
    32 |
    21
    33 |
    22
    34 |
    23
    35 |
    24
    36 |
    25
    37 |
    38 | 69 | 70 | -------------------------------------------------------------------------------- /27 clickAndDrag/style.css: -------------------------------------------------------------------------------- 1 | 2 | html { 3 | box-sizing: border-box; 4 | background: url('https://source.unsplash.com/NFs6dRTBgaM/2000x2000') fixed; 5 | background-size: cover; 6 | } 7 | *, *:before, *:after { 8 | box-sizing: inherit; 9 | } 10 | body { 11 | min-height: 100vh; 12 | display: flex; 13 | justify-content: center; 14 | align-items: center; 15 | font-family: sans-serif; 16 | font-size: 20px; 17 | margin: 0; 18 | } 19 | 20 | .items { 21 | height: 800px; 22 | padding: 100px; 23 | width: 100%; 24 | border: 1px solid white; 25 | overflow-x: scroll; 26 | overflow-y: hidden; 27 | white-space: nowrap; 28 | user-select: none; 29 | cursor: pointer; 30 | transition: all 0.2s; 31 | transform: scale(0.98); 32 | will-change: transform; 33 | position: relative; 34 | background: rgba(255,255,255,0.1); 35 | font-size: 0; 36 | perspective: 500px; 37 | } 38 | .items.active { 39 | background: rgba(255,255,255,0.3); 40 | cursor: grabbing; 41 | cursor: -webkit-grabbing; 42 | transform: scale(1); 43 | } 44 | 45 | .item { 46 | width: 200px; 47 | height: calc(100% - 40px); 48 | display: inline-flex; 49 | align-items: center; 50 | justify-content: center; 51 | font-size: 80px; 52 | font-weight: 100; 53 | color: rgba(0,0,0,0.15); 54 | box-shadow: inset 0 0 0 10px rgba(0,0,0,0.15); 55 | } 56 | 57 | .item:nth-child(9n+1) { background: dodgerblue;} 58 | .item:nth-child(9n+2) { background: goldenrod;} 59 | .item:nth-child(9n+3) { background: paleturquoise;} 60 | .item:nth-child(9n+4) { background: gold;} 61 | .item:nth-child(9n+5) { background: cadetblue;} 62 | .item:nth-child(9n+6) { background: tomato;} 63 | .item:nth-child(9n+7) { background: lightcoral;} 64 | .item:nth-child(9n+8) { background: darkslateblue;} 65 | .item:nth-child(9n+9) { background: rebeccapurple;} 66 | 67 | .item:nth-child(even) { transform: scaleX(1.31) rotateY(40deg); } 68 | .item:nth-child(odd) { transform: scaleX(1.31) rotateY(-40deg); } 69 | -------------------------------------------------------------------------------- /28 VideoSpeedController/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Video Speed Controller 28 3 | date: 2022-03-11 21:06:44 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第27天的“视频播放速度控制器”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/28%20VideoSpeedController/index.htm 14 | 15 | 16 | 17 | **本项目的笔记内容选自 [大史不说话](https://github.com/dashnowords)**,他本人是JS30社区里的知名参与者,他的全部笔记在[这里](https://github.com/soyaine/JavaScript30)。 18 | 19 | 本项目的笔记在[这里](https://github.com/soyaine/JavaScript30/tree/master/28%20-%20Video%20Speed%20Controller)。 20 | 21 | 22 | 23 | 24 | 25 | ## 挑战任务 26 | 27 | 初始文档`index-start.html`中提供了一个视频播放区域(使用的是H5原生的控制器)以及一个表示播放速度的滑块区域,本次的编程任务需要实现的效果是当鼠标拖动滑块时,实时改变视频播放的速度。 28 | 29 | ## 实现效果 30 | 31 | [![结果展示](https://github.com/soyaine/JavaScript30/raw/master/28%20-%20Video%20Speed%20Controller/effect.png)](https://github.com/soyaine/JavaScript30/blob/master/28 - Video Speed Controller/effect.png) 32 | 33 | ## 编程思路 34 | 35 | 本次的编程任务难度系数较低,在右侧速度条上监听鼠标点击事件,调整滑块的高度来表示不同的填充百分比,即不同的播放速度,将速度赋值给video对象的`playbackRate`属性即可实时改变播放速度。难点在于高度的百分比转换。 36 | 37 | ## 过程指南 38 | 39 | 本篇实现较为简单,不再分步骤讲解,示例代码如下: 40 | 41 | ```javascript 42 | const speed = document.querySelector(".speed"); 43 | const speedBar = speed.querySelector(".speed-bar"); 44 | const video = document.querySelector(".flex"); 45 | 46 | function changeSpeed(e) { 47 | const height = e.offsetY;//获取滑块的高度 48 | const percentage = e.offsetY / speed.offsetHeight; 49 | const min = 0.5; 50 | const max = 5; 51 | //依据自定义播放速度范围和滑块高度百分比确定播放速率 52 | const playbackRate = percentage * (max - min) + min; 53 | speedBar.style.height = Math.round(percentage*100) + '%'; 54 | speedBar.textContent = playbackRate.toFixed(2) + '×'; 55 | video.playbackRate = playbackRate; 56 | } 57 | 58 | speed.addEventListener('click',changeSpeed); 59 | ``` 60 | 61 | > JS30的第28个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 62 | -------------------------------------------------------------------------------- /28 VideoSpeedController/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Video Speed Scrubber 6 | 7 | 8 | 9 | 10 |
    11 | 12 |
    13 |
    14 |
    15 |
    16 | 17 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /28 VideoSpeedController/style.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | display: flex; 4 | justify-content: center; 5 | align-items: center; 6 | min-height: 100vh; 7 | background: #4C4C4C url('https://unsplash.it/1500/900?image=1021'); 8 | background-size: cover; 9 | font-family: sans-serif; 10 | } 11 | 12 | .wrapper { 13 | width: 850px; 14 | display: flex; 15 | } 16 | 17 | video { 18 | box-shadow: 0 0 1px 3px rgba(0,0,0,0.1); 19 | } 20 | 21 | .speed { 22 | background: #efefef; 23 | flex: 1; 24 | display: flex; 25 | align-items: flex-start; 26 | margin: 10px; 27 | border-radius: 50px; 28 | box-shadow: 0 0 1px 3px rgba(0,0,0,0.1); 29 | overflow: hidden; 30 | } 31 | 32 | .speed-bar { 33 | width: 100%; 34 | background: linear-gradient(-170deg, #2376ae 0%, #c16ecf 100%); 35 | text-shadow: 1px 1px 0 rgba(0,0,0,0.2); 36 | display: flex; 37 | align-items: center; 38 | justify-content: center; 39 | padding: 2px; 40 | color: white; 41 | height: 16.3%; 42 | } 43 | -------------------------------------------------------------------------------- /29 CountdownTimer/README.md: -------------------------------------------------------------------------------- 1 | --- 2 | title: 【原生javascript项目】Countdown Timer 29 3 | date: 2022-03-11 21:16:13 4 | tags: 原生javascript项目 5 | categories: 30个原生javascript项目 6 | --- 7 | 8 | > 作者:©[Iaine 万一](https://github.com/janice143?tab=repositories) 9 | > 简介:[30 Day Challenge](https://courses.wesbos.com/account)是 [Wes Bos](https://github.com/wesbos) 设计的一个 30 天原生js编程挑战。项目免费提供了 30 个视频教程、30 个挑战的起始文档和 30 个挑战解决方案源代码。 10 | > 11 | > 本项目为第28天的“倒计时计时器”项目。Have fun with the website! ♪(^∇^*) 12 | 13 | 源代码:https://github.com/janice143/JavaScript30Program/tree/master/29%20CountdownTimer/index.htm 14 | 15 | 16 | 17 | **本项目的笔记内容选自 [大史不说话](https://github.com/dashnowords)**,他本人是JS30社区里的知名参与者,他的全部笔记在[这里](https://github.com/soyaine/JavaScript30)。 18 | 19 | 本项目的笔记在[这里](https://github.com/soyaine/JavaScript30/tree/master/29%20-%20Countdown%20Timer)。 20 | 21 | 22 | 23 | 24 | 25 | ## 挑战任务 26 | 27 | 初始文档`index-start.html`中提供了一个倒计时控制器,从`html`文档的结构可以看出,顶部的按钮可以用来增加倒计时时间,常用的时间间隔已将参数绑定在`data-time`属性上;`display`类用来显示计时的结果。 28 | 本次编程挑战的任务是通过javascript代码基于当前时间生成一个倒计时,将`结束时间`和`剩余时间`分别显示在`diaplay__time-left`类标签和`display__end-time`类标签上。 29 | 30 | ## 实现效果 31 | 32 | [![结果展示](https://github.com/soyaine/JavaScript30/raw/master/29%20-%20Countdown%20Timer/effect.png)](https://github.com/soyaine/JavaScript30/blob/master/29 - Countdown Timer/effect.png) 33 | 34 | ## 编程思路 35 | 36 | 监听按点击事件`click`来为倒计时增加时间,使用`setInterval`函数每秒执行判断函数,若倒计时事件到,则清除当前计时器,若时间未到,则计算并刷新页面上应该显示的时间。 37 | 38 | ## 过程指南 39 | 40 | 1.定义变量及获取需要操作的DOM元素的引用。 41 | 42 | ``` 43 | const endTime = document.querySelector(".display__end-time"); 44 | const leftTime = document.querySelector(".display__time-left"); 45 | const buttons = document.querySelectorAll("button"); 46 | const date = new Date(); 47 | var left = 0;//剩余时间 48 | var end = 0;//结束时间 49 | var timer;//interval计时器 50 | leftTime.innerHTML = left;//未操作时,剩余时间显示0 51 | ``` 52 | 53 | 2.为button绑定点击事件,当按钮点击时执行对应的回调函数。 54 | 55 | ``` 56 | const arr = Array.from(buttons); 57 | arr.map(function(item){ 58 | item.addEventListener('click',clickAction); 59 | }); 60 | ``` 61 | 62 | 3.监听表单的提交事件,注意表单的调用方式。 63 | 64 | ``` 65 | document.customForm.addEventListener('submit',function(e){ 66 | e.preventDefault(); 67 | updateTime(this.minutes.value*60); 68 | updateTimer(); 69 | }); 70 | ``` 71 | 72 | 4.点击后的回调函数中取得点击按钮传递的秒数,调用`updateTime()`函数更新页面显示结果,并调用`updateTimer()`来更新计时器相关动作. 73 | 74 | ``` 75 | function clickAction(e){ 76 | let deltaTime; 77 | deltaTime = this.dataset.time;//取得data-time属性的值 78 | updateTime(deltaTime); 79 | 80 | //点击后更新计时器 81 | updateTimer(); 82 | } 83 | ``` 84 | 85 | 5.`updateTime()`函数用来更新和页面相关的显示信息。 86 | 87 | ``` 88 | function updateTime(delta){ 89 | left = left + parseInt(delta,0); 90 | end = date.getTime() + left*1000; 91 | leftTime.innerHTML = left; 92 | endTime.innerHTML =new Date(end).toLocaleTimeString(); 93 | } 94 | ``` 95 | 96 | 6.`updateTimer()`函数用来执行和设定每秒检查计时器是否需要继续工作的逻辑判断。 97 | 98 | ``` 99 | function updateTimer(){ 100 | //清除以前的timer,如果不清除,新生成的定时器会和以前的定时器叠加在一起,均会生效。 101 | if(timer){ 102 | clearInterval(timer); 103 | } 104 | 105 | // 设置新的Timer 106 | timer = setInterval(function(){ 107 | if(left == 0){ 108 | endTime.innerHTML = 'End'; 109 | clearInterval(timer); 110 | }else{ 111 | left -= 1; 112 | leftTime.innerHTML = left; 113 | } 114 | },1000); 115 | } 116 | ``` 117 | 118 | ## 延伸思考 119 | 120 | 本次代码中前后会定义定时器和清除定时器,另一种做法是定时器一直工作不清除,对应的按钮和表单只修改时间,不用调整定时器,当值发生变化后,下一秒定时器检测时就会开始倒计时,这样代码逻辑上会有所简化,感兴趣的朋友可以自行练习。 121 | 122 | > JS30的第29个项目圆满完成啦,感谢阅读,有问题联系我的邮箱1803105538@qq.com. 123 | -------------------------------------------------------------------------------- /29 CountdownTimer/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Countdown Timer 6 | 7 | 8 | 9 | 10 |
    11 |
    12 | 13 | 14 | 15 | 16 | 17 |
    18 | 19 |
    20 |
    21 |
    22 |

    23 |

    24 |
    25 |
    26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /29 CountdownTimer/scripts-FINISHED.js: -------------------------------------------------------------------------------- 1 | let countdown; 2 | const timerDisplay = document.querySelector('.display__time-left'); 3 | const endTime = document.querySelector('.display__end-time'); 4 | const buttons = document.querySelectorAll('[data-time]'); 5 | 6 | function timer(seconds) { 7 | // clear any existing timers 8 | clearInterval(countdown); 9 | 10 | const now = Date.now(); 11 | const then = now + seconds * 1000; 12 | displayTimeLeft(seconds); 13 | displayEndTime(then); 14 | 15 | countdown = setInterval(() => { 16 | const secondsLeft = Math.round((then - Date.now()) / 1000); 17 | // check if we should stop it! 18 | if(secondsLeft < 0) { 19 | clearInterval(countdown); 20 | return; 21 | } 22 | // display it 23 | displayTimeLeft(secondsLeft); 24 | }, 1000); 25 | } 26 | 27 | function displayTimeLeft(seconds) { 28 | const minutes = Math.floor(seconds / 60); 29 | const remainderSeconds = seconds % 60; 30 | const display = `${minutes}:${remainderSeconds < 10 ? '0' : '' }${remainderSeconds}`; 31 | document.title = display; 32 | timerDisplay.textContent = display; 33 | } 34 | 35 | function displayEndTime(timestamp) { 36 | const end = new Date(timestamp); 37 | const hour = end.getHours(); 38 | const adjustedHour = hour > 12 ? hour - 12 : hour; 39 | const minutes = end.getMinutes(); 40 | endTime.textContent = `Be Back At ${adjustedHour}:${minutes < 10 ? '0' : ''}${minutes}`; 41 | } 42 | 43 | function startTimer() { 44 | const seconds = parseInt(this.dataset.time); 45 | timer(seconds); 46 | } 47 | 48 | buttons.forEach(button => button.addEventListener('click', startTimer)); 49 | document.customForm.addEventListener('submit', function(e) { 50 | e.preventDefault(); 51 | const mins = this.minutes.value; 52 | console.log(mins); 53 | timer(mins * 60); 54 | this.reset(); 55 | }); 56 | -------------------------------------------------------------------------------- /29 CountdownTimer/scripts-START.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/janice143/JavaScript30Program/8dbb68df5bbc234f59f9ebdcffd5fbc959244571/29 CountdownTimer/scripts-START.js -------------------------------------------------------------------------------- /29 CountdownTimer/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 10px; 4 | background: #8E24AA; 5 | background: linear-gradient(45deg, #42a5f5 0%,#478ed1 50%,#0d47a1 100%); 6 | } 7 | 8 | *, *:before, *:after { 9 | box-sizing: inherit; 10 | } 11 | 12 | body { 13 | margin: 0; 14 | text-align: center; 15 | font-family: 'Inconsolata', monospace; 16 | } 17 | 18 | .display__time-left { 19 | font-weight: 100; 20 | font-size: 20rem; 21 | margin: 0; 22 | color: white; 23 | text-shadow: 4px 4px 0 rgba(0,0,0,0.05); 24 | } 25 | 26 | .timer { 27 | display: flex; 28 | min-height: 100vh; 29 | flex-direction: column; 30 | } 31 | 32 | .timer__controls { 33 | display: flex; 34 | } 35 | 36 | .timer__controls > * { 37 | flex: 1; 38 | } 39 | 40 | .timer__controls form { 41 | flex: 1; 42 | display: flex; 43 | } 44 | 45 | .timer__controls input { 46 | flex: 1; 47 | border: 0; 48 | padding: 2rem; 49 | } 50 | 51 | .timer__button { 52 | background: none; 53 | border: 0; 54 | cursor: pointer; 55 | color: white; 56 | font-size: 2rem; 57 | text-transform: uppercase; 58 | background: rgba(0,0,0,0.1); 59 | border-bottom: 3px solid rgba(0,0,0,0.2); 60 | border-right: 1px solid rgba(0,0,0,0.2); 61 | padding: 1rem; 62 | font-family: 'Inconsolata', monospace; 63 | } 64 | 65 | .timer__button:hover, 66 | .timer__button:focus { 67 | background: rgba(0,0,0,0.2); 68 | outline: 0; 69 | } 70 | 71 | .display { 72 | flex: 1; 73 | display: flex; 74 | flex-direction: column; 75 | align-items: center; 76 | justify-content: center; 77 | } 78 | 79 | .display__end-time { 80 | font-size: 4rem; 81 | color: white; 82 | } 83 | -------------------------------------------------------------------------------- /30 WhackAMole/index-FINISHED.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Whack A Mole! 6 | 7 | 8 | 9 | 10 | 11 |

    Whack-a-mole! 0

    12 | 13 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 |
    33 |
    34 | 35 | 86 | 87 | 88 | -------------------------------------------------------------------------------- /30 WhackAMole/index-START.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Whack A Mole! 6 | 7 | 8 | 9 | 10 | 11 |

    Whack-a-mole! 0

    12 | 13 | 14 |
    15 |
    16 |
    17 |
    18 |
    19 |
    20 |
    21 |
    22 |
    23 |
    24 |
    25 |
    26 |
    27 |
    28 |
    29 |
    30 |
    31 |
    32 |
    33 |
    34 | 35 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /30 WhackAMole/index-VUE.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Whack A Mole! 8 | 9 | 10 | 11 | 12 | 13 |
    14 |

    Whack-a-mole! {{score}}

    15 | 16 |
    17 |
    24 | 25 |
    26 |
    27 | 85 | 86 | -------------------------------------------------------------------------------- /30 WhackAMole/style.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | font-size: 10px; 4 | background: #ffc600; 5 | } 6 | 7 | *, *:before, *:after { 8 | box-sizing: inherit; 9 | } 10 | 11 | body { 12 | padding: 0; 13 | margin: 0; 14 | font-family: 'Amatic SC', cursive; 15 | } 16 | 17 | h1 { 18 | text-align: center; 19 | font-size: 10rem; 20 | line-height: 1; 21 | margin-bottom: 0; 22 | } 23 | 24 | .score { 25 | background: rgba(255,255,255,0.2); 26 | padding: 0 3rem; 27 | line-height: 1; 28 | border-radius: 1rem; 29 | } 30 | 31 | .game { 32 | width: 600px; 33 | height: 400px; 34 | display: flex; 35 | flex-wrap: wrap; 36 | margin: 0 auto; 37 | } 38 | 39 | .hole { 40 | flex: 1 0 33.33%; 41 | overflow: hidden; 42 | position: relative; 43 | } 44 | 45 | .hole:after { 46 | display: block; 47 | background: url(dirt.svg) bottom center no-repeat; 48 | background-size: contain; 49 | content: ''; 50 | width: 100%; 51 | height:70px; 52 | position: absolute; 53 | z-index: 2; 54 | bottom: -30px; 55 | } 56 | 57 | .mole { 58 | background: url('mole.svg') bottom center no-repeat; 59 | background-size: 60%; 60 | position: absolute; 61 | top: 100%; 62 | width: 100%; 63 | height: 100%; 64 | transition:all 0.4s; 65 | } 66 | 67 | .hole.up .mole { 68 | top: 0; 69 | } 70 | --------------------------------------------------------------------------------