├── 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 | 
19 |
20 | 网页主体为一个时钟,具有表盘(12个数字)和三个指针(时针、分针、秒针)。
21 |
22 | #### 2实现原理
23 |
24 | 一、 html代码
25 |
26 | 使用一个类名为clock为的div容器,里面包含时针.hour-hand,分针.minute-hand,秒针second-hand,以及12个数字。
27 |
28 | ```html
29 |
元素
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 |
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 | 
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 |
15 | your browser does not support the video tag
16 |
17 |
18 |
26 |
27 |
28 |
29 |
« 10s
30 |
25s »
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 |
14 | your browser does not support the video tag
15 |
16 |
17 |
20 |
►
21 |
22 |
23 |
« 10s
24 |
25s »
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 | 
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 |
16 | Loading Tapas...
17 |
18 |
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 | 
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 | 
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 | 
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 |
11 |
12 | Video 1
13 |
14 |
15 | Video 2
16 |
17 |
18 | Video 3
19 |
20 |
21 | Video 4
22 |
23 |
24 | Video 5
25 |
26 |
27 | Video 6
28 |
29 |
30 | Video 7
31 |
32 |
33 | Video 8
34 |
35 |
36 | Video 9
37 |
38 |
39 | Video 10
40 |
41 |
42 | Video 11
43 |
44 |
45 | Video 12
46 |
47 |
48 | Video 13
49 |
50 |
51 | Video 14
52 |
53 |
54 | Video 15
55 |
56 |
57 | Video 16
58 |
59 |
60 | Video 17
61 |
62 |
63 | Video 18
64 |
65 |
66 | Video 19
67 |
68 |
69 | Video 20
70 |
71 |
72 | Video 21
73 |
74 |
75 | Video 22
76 |
77 |
78 | Video 23
79 |
80 |
81 | Video 24
82 |
83 |
84 | Video 25
85 |
86 |
87 | Video 26
88 |
89 |
90 | Video 27
91 |
92 |
93 | Video 28
94 |
95 |
96 | Video 29
97 |
98 |
99 | Video 30
100 |
101 |
102 | Video 31
103 |
104 |
105 | Video 32
106 |
107 |
108 | Video 33
109 |
110 |
111 | Video 34
112 |
113 |
114 | Video 35
115 |
116 |
117 | Video 36
118 |
119 |
120 | Video 37
121 |
122 |
123 | Video 38
124 |
125 |
126 | Video 39
127 |
128 |
129 | Video 40
130 |
131 |
132 | Video 41
133 |
134 |
135 | Video 42
136 |
137 |
138 | Video 43
139 |
140 |
141 | Video 44
142 |
143 |
144 | Video 45
145 |
146 |
147 | Video 46
148 |
149 |
150 | Video 47
151 |
152 |
153 | Video 48
154 |
155 |
156 | Video 49
157 |
158 |
159 | Video 50
160 |
161 |
162 | Video 51
163 |
164 |
165 | Video 52
166 |
167 |
168 | Video 53
169 |
170 |
171 | Video 54
172 |
173 |
174 | Video 55
175 |
176 |
177 | Video 56
178 |
179 |
180 | Video 57
181 |
182 |
183 | Video 58
184 |
185 |
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 = `
`;
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 | Take Photo
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 = `
`;
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 |
128 | This is my
link collection :
129 |
133 |
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 | 
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 | 
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 |
65 |
72 |
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 |
102 | Select A Voice
103 |
104 |
105 | Rate:
106 |
107 | Rate:
108 |
109 |
110 |
111 | Stop
112 | Speak
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 | 
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 |
17 |
18 |
44 |
56 |
57 |
--------------------------------------------------------------------------------
/26 stripAlongNav/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
stripAlongNav
8 |
9 |
10 |
11 |
Cool
12 |
13 |
14 |
15 |
16 |
17 |
18 | About Me
19 |
20 |
21 |
22 |
Wes Bos sure does love web development. He teaches things like JavaScript, CSS and BBQ. Wait. BBQ isn't part of web development. It should be though!
23 |
24 |
25 |
26 |
27 | Courses
28 |
66 |
67 |
68 | Other Links
69 |
75 |
76 |
77 |
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/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 |
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/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 | 20 Secs
13 | Work 5
14 | Quick 15
15 | Snack 20
16 | Lunch Break
17 |
20 |
21 |
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 |
Start!
13 |
14 |
15 |
18 |
21 |
24 |
27 |
30 |
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 |
Start!
13 |
14 |
15 |
18 |
21 |
24 |
27 |
30 |
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 |
Start!
16 |
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 |
--------------------------------------------------------------------------------