wesbos 的30天纯 JS 挑战:javascript30-github,适合有一定基础的前端FED学习,跟着做下来再自己实现,可以巩固JS知识。
Day1: Drum-Kit
Day2: Clock
Day3: Update Photo Setting
Day4: Array Cardio Day
Day5: Click and Expand Gallery
素材:
键盘码-每个按键都有一个对应的keyCode。
关键点:按键后触发声音和改变画面上的样式。
步骤分解:
-
获取键码
展开答案
document.querySelector(`audio[data-key] = "${e.keyCode}"`) -
点击的键不在网页显示的键中,会报错
展开答案
```js if(audio) audio.play() if(dom) dom.classList.add('playing') ```
-
快速点击一个键,让它连续播放
展开答案
音频播放前,设置 currentTime 为0。
-
如果一段动画中有多个属性,transitionend 事件会重复触发!
展开答案
```js if(e.propertyName === 'transform'){ e.currentTarget.classList.remove('playing') } ```
思考点:
const keys = Array.from(document.querySelectorAll('.key'))其中的Array.from是否可以省略?
I:querySelectorAll返回的是一个NodeList,类数组。
- HTML 中用
<kbd>标签定义键盘上键入的文本。 getElementsByClassName和querySelectorAll:前者是获取动态集合,后者是静态。静态就是只要你不重新获取一次就不会变。
-
指针
.second-hand::after{ position: absolute; content: ""; top: 50%; left: 50%; display: block; width: 10px; height: 40%; background-color: red; transform: translate(-50%, 0%); /* transform: rotate(0deg); 增加度数实现秒针的顺时针旋转 */ }
-
指针圆点
.clock-face::after{ position: absolute; top: 50%; left: 50%; content: ""; display: block; width: 15px; height: 15px; border-radius: 100%; background-color: #fff; transform: translate(-50%, -50%); }
-
时分秒换算
// 表盘1格(1小时)是30° 1小格(1分钟)是6° let secDeg = date.getSeconds() * 6 let minDeg = date.getMinutes() * 6 + date.getSeconds() * 6 / 60 let hourDeg = date.getHours() * 30 + date.getMinutes() * 30 / 60
-
计时器选择setInterval、setTimeout、requestAnimationFrame
// setInterval 隔...执行一次 再隔...再执行 按照间隔时间持续执行 setInterval(setClock, 1000) // 持续动作 比如时钟或者轮播图 // setTimeout 延迟 执行一次 function timeoutHandler(){ setClock() setTimeout(timeoutHandler, 1000) } setTimeout(timeoutHandler, 1000) // 如果直接调用setClock 动一次就不会再动了 //requestAnimationFrame function animationHandler(){ setClock() window.requestAnimationFrame(animationHandler) } window.requestAnimationFrame(animationHandler); // 根据屏幕设备性能去调用函数更新画面 适用于canvas动画
Q: 指针旋转到12点的时候 会闪回跳跃的bug ,原因是指针是90° - 96°- 102° - ...
解决办法1: 将特殊点的transition过程瞬间完成
解决办法2:只在页面第一次加载时 new 一次 Date 对象,此后每秒直接更新角度值。
该方法存在的小问题:角度值可能存在有效范围,页面一直跑的话,数值就能无限大了应该会有问题。
重点:
获取页面中 input 元素,用for-each 给每个input添加监听事件(change/mouseover),使其值变动,触发更新操作。
Q: 绑定change事件后,可以看出鼠标在输入框中间滑动的时候,值不会随之改变(触发)
A:添加mousemove监听鼠标移动事件。
如果只用mousemove,不用change事件的话,像颜色选取就无法生效。
如何用 JavaScript 改变 CSS 属性值?
// document.documentElement.style['opacity'] 使用[]获取所有带有--blur属性的style
// 但是这个案例里--base不行 这里推荐使用的是setProperty
document.documentElement.style.setProperty(`--${this.name}`, this.value + suffix);
// ('--base', #A0BEEE);
// const suffix = this.dataset.sizing || '';css variable: 先声明全局变量,再使用。
<style>
:root{
--base: #eee;
}
div {
color: var(--base);
}
</style>:root 选择器等同于 html,以及
document.querySelector(":root") === document.documentElement
自定义变量
<input data-emoji="😏" >
<script>
const face = this.dataset.emoji; //😏
</script>const inventors = [
{first: 'Adam', last: 'Einstein', born: 1879, passed: 1955},
{first: 'Isaac', last: 'Newton', born: 1900, passed: 1965},
{first: 'Max', last: 'Lovelace', born: 1573, passed: 1620},
{first: 'Lisa', last: 'Hubert', born: 1750, passed: 1835},
{first: 'Rose', last: 'Daughton', born: 1522, passed: 1535}
]Filter:返回一个新数组,其中包含符合条件的所有元素。(文案废,看代码你就知道怎么用了)
// Filter the list of inventors for those who were born in the 1500's 筛选16世纪出生的
let res = inventors.filter(inventor => inventor.born >=1500 &inventor.born < 1600);
// console.table(res);Map: 返回一个新数组,结果是数组的每个元素调用一次提供的方法后的返回值。
// Give us an array of the inventors first and last names 合并firstName和lastName
let res = inventors.map(inventor => `${inventor.first} ${inventor.last}`)
// 🌰 let ans = [1, 2, 3].map(num => num*2);forEach 和 map 不同的是,前者不会返回新数组,只会执行函数,你如果要返回数组,需要定义一个数组然后把上述返回值 push 进去。
Sort:
// Sort the inventors by birthdate, oldest to youngest 排序从年长到年轻
// let res = inventors.sort((a, b) => a.born > b.born ? 1 : -1);
// 上面这样写的话 没有处理a=b的情况 所以刷新可能年龄相同的会排序不同
let res = inventors.sort((a, b) => a.born - b.born);Reduce:
// How many years did all the inventors live all together?
let res = inventors.reduce((total, inventor) => {return total + inventor.passed - inventor.born}, 0)综合:
// 从网页中筛选书本名字包含"鲁迅"的 并返回 https://book.douban.com/tag/novel
let res = Array.from(document.querySelectorAll('.subject-item h2 a'))
.map(book => book.title)
.filter(title => title.includes('鲁迅'));注意: querySelectorAll() 获取到的是一个 NodeList ,它并非是 Array 类型的数据,所以并不具有 map 和 filter 这样的方法,所以如果要进行筛选操作则需要把它转化成 Array 类型,或者直接使用forEach。
Ⅰ.让图片均匀地占据空间:
展开答案
将`.panels` 设置为 `display: flex;`,子元素设置属性为`flex:1;` ,实现四张图竖列4等分占据画面。
Ⅱ.设置默认状态下首尾字母的状态和点击之后文字状态
.panel>*:first-child {
transform: translateY(-100%);
}
.panel.open-active>*:first-child {
transform: translateY(0);
}Ⅲ.js部分
1.获取所有.panel的元素,用forEach遍历panel
querySelectorAll获取到的元素集合不是Array,所以不能用map()。
2.给panel添加click监听事件(给触发的 DOM 元素添加样式,实现拉伸/压缩的效果)
查看代码
```js const panels = document.querySelectorAll(".panel"); function toggleOpen() { this.classList.toggle('open'); } panels.forEach(panel => panel.addEventListener('click', toggleOpen)) ```
3.添加 transitionend 事件监听,实现扇叶开了之后文字飞回来的效果
JS部分
```js function transitionHandler(e){ if(e.propertyName.indexOf('flex')!==-1){ this.classList.toggle('font-active') } } panels.forEach(panel => panel.addEventListener('transitionend', transitionHandler)) /* Safari transitionend event.propertyName === flex */ /* Chrome + FF transitionend event.propertyName === flex-grow */ ```
CSS部分
```css .panel.font-active>*:first-child, .panel.font-active>*:last-child{ transform: translateY(0); } ```
classList.toggle在元素中切换类名
输入作者或者词语关键词,展示匹配到的古诗词
①获取数据
fetch(url) // 获取Promise对象
.then(blob=>blob.json()) //解析JSON数据
.then(data => poetries.push(...data)); //存入数组②查找匹配de函数
function findMatch(wordToMatch, poetries) {
return poetries.filter(poet => {
// filter() 返回一个新的、由通过测试的元素组成的数组
const regex = new RegExp(wordToMatch, 'gi');
return poet.title.match(regex);
})
}③展示匹配内容de函数
-
获取匹配数据
const matchArray = findMatch(this.value, poetries); // 匹配数组
const regex = new RegExp(this.value, 'gi'); //关键字
-
替换关键词放入高亮的标签
const title = poet.title.replace(regex,
<span class="hlight">${ this.value }</span>) -
将匹配值的 HTML 标签放入
<ul>中
④绑定事件
获取两个主要 HTML 元素(<input>,<ul>),给 <input> 添加事件监听(change, keyup)
ctx.lineJoin = "round"; // 相连部分如何连接在一起 扇形拐角 || miter菱形(默认) || bevel矩形拐角ctx.lineCap = "round"; //线段末端以圆形结束 || butt方形获取所有的checkbox
const checkboxes = document.querySelectorAll('.inbox input[type="checkbox"]');遍历并给每个checkbox添加事件
checkboxes.forEach(checkbox => checkbox.addEventListener('click', handleCheck));