写完了Github上超火的前端50Projects,我学到了什么


theme: orange

开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第1天,点击查看活动详情

项目出处: https://github.com/bradtraversy/50projects50days

1. Expanding Cards(扩展卡)

Kapture 2022-12-15 at 18.37.10

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .panel.active {
      flex: 5;
      filter: brightness(1);
    }
    
    // js
    const ctnEl = document.querySelector('.container')
    ctnEl.addEventListener('click', function (event) {
      Array.prototype.forEach.call(this.children, (el) => {   
        el.classList.remove('active')
      })
      event.target.classList.add('active')
    })
    
  4. 项目总结

    (1)CSS:flex:1 是一种简写写法,表示在容器中占的份数;filter:brightness(1)用于控制明亮效果,也可用百分比形式,0%则为全黑效果

    (2)JS: 由于document.querySelector返回的是一个nodeList,故需要使用Array.prototype.foreach.call()来进行循环,通过监听点击事件动态添加|移除active类实现展示效果

2. Progress Steps(进度步骤)

Kapture 2022-12-15 at 18.59.48

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    :root{
      --line-border-fill: #168483;
      --line-border-empty: #e0e0e0;
    }
    .btn:disabled{
        background-color: var(--line-border-empty);
        cursor: not-allowed;
    }
    // js
    circles.forEach((item, index) => {
       if (index < currentActive) {
         item.classList.add('active')
       } else {
         item.classList.remove('active')
       }
     })
    const actives = document.querySelectorAll('.active')
    progress.style.width = (actives.length - 1) / (circles.length - 1) * 100 + '%'
    
  4. 项目总结

    (1)CSS: 使用:root定义全局变量,搭配::before、:active、:focus、:disabled等伪元素和伪类选择器实现页面效果

    (2)JS: 通过当前激活节点currentActive来动态添加|移除active类,以及控制进度条的长度

3. Rotating Navigation Animation(旋转导航动画)

Kapture 2022-12-15 at 19.01.52

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .container.show-nav .circle {
      transform: rotate(-70deg);
    }
    .container.show-nav + nav li {
      transform: translateX(0);
      transition-delay: 0.3s;
    }
    
    // js
    openBar.addEventListener('click',()=>{
        container.classList.add('show-nav')
    })
    closeBar.addEventListener('click',()=>{
        container.classList.remove('show-nav')
    })
    
  4. 项目总结

    (1)CSS: 多个类名连着写表示要同时满足才能生效,+ 为相邻兄弟选择器(相邻且同级),transform用于平移、旋转、缩放、倾斜给定元素

    (2)JS: 通过点击事件来动态添加|移除show-nav类,实现导航栏的展示或隐藏

4. Hidden Search Widget(隐藏的搜索小工具)

Kapture 2022-12-15 at 18.48.54

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .search .input{
        background-color: #fff;
        border: 0;
        font-size: 18px;
        padding: 15px;
        width: 50px;
        height: 50px;
        transition: width 0.3s ease;
        border-radius: 2px;
    }
    
    // js
    btn.addEventListener('click',()=>{
        search.classList.toggle('active')
        input.focus()
    })
    
  4. 项目总结

    (1)CSS: transition控制过渡效果,可接收四个参数(需要过渡的属性名,过渡时间,过渡曲线,过渡延迟

    (2)JS: 通过监听click点击事件,使用toggle()方法切换active类的状态,没有则加上,有则移除

5. Blurry Loading(模糊加载)

Kapture 2022-12-15 at 18.51.56

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .bg {
      background: url('xx.jpg') no-repeat center/cover;
      position: absolute;
      top: -30px;
      left: -30px;
      width: calc(100vw + 60px);
      height: calc(100vh + 60px);
      z-index: -1;
      filter: blur(0px);
    }
    
    // js
    loadText.style.opacity = scale(load, 0, 100, 1, 0) // 透明度
    bg.style.filter = `blur(${scale(load, 0, 100, 30, 0)}px)` // 高斯模糊
    
    const scale = (num, in_min, in_max, out_min, out_max) => {
      // 100 * (-1) / 100 + 1 = 0
      // 100 * (-30) / 100 + 30 = 0
      return ((num - in_min) * (out_max - out_min)) / (in_max - in_min) + out_min
    }
    
  4. 项目总结

    (1)CSS: background中的center/cover代表background-position:center;background-size:cover,且size只能跟在position后出现,用/分隔

    (2)JS: 主要通过scale()方法来控制文字的透明度和背景的blur模糊参数

6. Scroll Animation(滚动动画)

Kapture 2022-12-15 at 19.06.13

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css 
    .box:nth-of-type(even) {
      transform: translateX(-400%);
    }
    .box.show {
      transform: translateX(0);
    }
    
    // js
    function checkBoxes() {
      const triggerBottom = (window.innerHeight / 5) * 4
      boxes.forEach((box)=>{
        const boxTop =box.getBoundingClientRect().top
        if(boxTop<triggerBottom){
            box.classList.add('show')
        }else{
            box.classList.remove('show')
        } 
      })
    }
    
  4. 项目总结

    (1)CSS:nth-of-type(even)伪类选择器,表示选择所有偶数元素,translateX(-400%)表示向左平移400%(看不见了)

    (2)JS:(window.innerHeight / 5) * 4获取当前页面高度的4/5,getBoundingClientRect().top获取元素的顶部距离网页最上方的高度

7. Split Landing Page(拆分着陆页)

Kapture 2022-12-15 at 19.11.15

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .split.left::before {
      content: '';
      position: absolute;
      width: 100%;
      height: 100%;
      background-color: var(--left-bg-color);
    }
    
    // js
    left.addEventListener('mouseenter', () => container.classList.add('hover-left'))
    
    left.addEventListener('mouseleave', () =>
      container.classList.remove('hover-left')
    )
    
  4. 项目总结

    (1)CSS: 通过before伪元素选择器配合background-color给图片设置一层遮罩效果

    (2)JS: 通过监听鼠标mouseenter和mouseleave事件动态添加|移除hover-left类,实现动画效果

8. Form Wave(波形表单)

Kapture 2022-12-15 at 19.18.31

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .form-control label span {
      ...
      transition: 0.3s cubic-bezier(0.68, -0.55, 0.265, 1.55);
    }
    .form-control input:valid + label span {
      color: lightblue;
      transform: translateY(-30px);
    }
    
    // js
    labels.forEach(label => {
      label.innerHTML = label.innerText
        .split('')
        .map(
          (letter, index) =>
            `<span style="transition-delay:${index * 50}ms">${letter}</span>`
        )
        .join('')
    })
    
  4. 项目总结

    (1)CSS: 贝塞尔曲线cubic-bezier()是一种过渡线条形式,input:valid表示文本框输入的值是符合校验

    (2)JS: 将字符串通过split分割为单个字符并使用transition-delay延迟每个字符出现的时间,用map函数重新渲染,配合贝塞尔曲线的参数,实现波浪效果

9. Sound Board(音板)

Kapture 2022-12-15 at 21.17.08

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // html
    <audio id="applause" src="sounds/applause.mp3"></audio>
    <audio id="boo" src="sounds/boo.mp3"></audio>
    <audio id="gasp" src="sounds/gasp.mp3"></audio>
    
    // js
    btn.addEventListener('click', () => {
        stopSong()
        document.getElementById(sound).play()
    })
    
  4. 项目总结

    (1)HTML: audio标签中的src用于引入音频源

    (2)JS: 通过监听click事件及play()pause()方法用来控制音频的播放与暂停

10. Dad Jokes(老爹式笑话)

Kapture 2022-12-15 at 21.19.17

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .btn:active{
        transform:scale(0.98);
    }
    
    // js
    async function generateJoke(){
        const config={
            headers:{
                Accept:'application/json',
            }
        }
        const res=await fetch('https://icanhazdadjoke.com',config)
        const data =await res.json()
        jokeEl.innerHTML=data.joke
    }
    
  4. 项目总结

    (1)CSS:scale用来控制元素的缩放、放大效果。1为正常大小,>1 放大,<1 缩小

    (2)JS:async和await发送异步请求,fetch(url)可以直接发送get请求,res.json()是一个异步操作,取出所有内容,并将其转为JSON对象

11. Event KeyCodes(键盘键码)

Kapture 2022-12-15 at 21.21.05

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    body{
        justify-content:center;
        align-items:center;
        text-align:center;
        ...
    }
        
    // js
    window.addEventListener('keydown', event => {
      insert.innerHTML = `
        <div class='key'>
        ${event.key === ' ' ? 'Space' : event.key}
        <small>event.key</small>
        </div>
        ....
    })    
    
  4. 项目总结

    (1)CSS:flex弹性布局实现居中

    (2)JS: 通过监听keydown键盘事件,使用innerHTML渲染事件中的key、keyCode、code属性

12. Faq Collapse(常见问题解答)

Kapture 2022-12-15 at 21.22.53

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .faq.active:before,
    .faq.active:after {
      content: '\f075';
      font-family: 'Font Awesome 5 Free';
      ...
    }
    
    // js
    toggles.forEach(toggle=>{
        toggle.addEventListener('click',()=>{
            toggle.parentNode.classList.toggle('active')
        })
    }) 
    
  4. 项目总结

    (1)CSS: 通过beforeafter伪元素选择器搭配font-awesome字体库实现基本样式效果

    (2)JS: 监听click点击事件,配合toggle()函数,动态控制active类的添加或移除

13. Random Choice Picker(随机选择器)

Kapture 2022-12-15 at 21.25.27

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    textarea:focus{
        outline:none;
    }
    
    // js
    function createTags(input) {
      const tags = input
        .split(',')
        .filter(tag => tag.trim() !== '')
        .map(tag => tag.trim())
    
      tagsEl.innerHTML = ''
      tags.forEach(tag => {
        const tagEl = document.createElement('span')
        tagEl.classList.add('tag')
        tagEl.innerText = tag
        tagsEl.appendChild(tagEl)
      })
    }
    
    function randomSelect() {
      const times = 30
      const interval = setInterval(() => {
        const randomTag = pickerRandomTag()
        if (randomTag !== undefined) {
          highlightTag(randomTag)
          setTimeout(() => {
            unHighlightTag(randomTag)
          }, 100)
        }
      }, 100)
    
      setTimeout(() => {
        clearInterval(interval)
        setTimeout(() => {
          const randomTag = pickerRandomTag()     
          highlightTag(randomTag)
        },100)
      }, times * 100)
    }
    
  4. 项目总结

    (1)CSS:focus伪类选择器,当聚焦于文本框时,通过设置outline:none,控制轮廓属性的展示方式

    (2)JS: 通过split、filter、map对文本框输入值进行处理,document.createElement动态创建元素,setInterval和setTimeout定时器控制随机选择的动画效果

14. Animated Navigation(动画导航)

Kapture 2022-12-15 at 21.27.15

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    nav.active .icon .line1 {
      transform: rotate(-765deg) translateY(5.5px);
    }
    nav.active .icon .line2 {
      transform: rotate(765deg) translateY(-5.5px);
    }
    
    // js
    toggle.addEventListener('click', () => nav.classList.toggle('active'))
    
  4. 项目总结

    (1)CSS:rotate控制旋转角度,translateY控制在Y轴的平移像素值

    (2)JS: 监听click点击事件,通过toggle()函数动态控制active类的添加或移除

15. Incrementing Counter(递增计数器)

Kapture 2022-12-15 at 21.29.01

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // html
    <div class="counter-container">
         <i class="fab fa-twitter fa-3x"></i>
         <div class="counter" data-target="12000"></div>
         <span>Twitter Followers</span>
    </div>
    
    // js
    const updateCounter = () => {
        const target = +counter.getAttribute('data-target')
        const c = +counter.innerText
        const increment = target / 200
        if (c < target) {
          counter.innerText = `${Math.ceil(c + increment)}`
          setTimeout(updateCounter, 1)
        } else {
          counter.innerText = target
        }
      }
    
  4. 项目总结

    (1)HTML:data-target自定义目标值属性

    (2)JS: 使用 + 号把值转为隐式转为数字类型【关于加号的隐式转换】,通过setTimeout定时器实现数字的增量相加

16. Drink Water(喝水)

Kapture 2022-12-15 at 21.35.23

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .cups{
        display:flex;
        flex-wrap:wrap;
        align-items:center;
        justify-content:center;
        width:280px;
    }
    
    // js
    function highlightCups(idx) {
      // 点击两次最后一个则索引减1
      if (idx === 7 && smallCups[idx].classList.contains('full')) {
        idx--
      }
      // 当前被点一次了且后面一杯水没被点,在点一次自身就会索引-1
      else if (
        smallCups[idx].classList.contains('full') &&
        !smallCups[idx].nextElementSibling.classList.contains('full')
      ) {
        idx--
      }
      // 小于等于当前索引值的增加full类,否则移除
      smallCups.forEach((cup, idx2) => {
        if (idx2 <= idx) {
          cup.classList.add('full')
        } else {
          cup.classList.remove('full')
        }
      })
      updateBigCup()
    }
    
  4. 项目总结

    (1)CSS: 常规flex布局,flex-wrap:wrap控制换行

    (2)JS: 通过监听每个杯子的click事件,传递给highlightCups索引值,用来控制full类的添加或移除,updateBigCup()方法则通过含有full类的元素控制大水杯的高度和百分比

17. Movie App(电影应用)

Kapture 2022-12-15 at 21.43.58

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .overview{
        position:absolute;
        left:0;
        bottom:0;
        right:0;
        max-height:100%;
        transform:translateY(101%);
        ...
    }
    .movie:hover .overview {
        transform:translateY(0)
    }
    
    // js
    form.addEventListener('submit', e => {
      e.preventDefault() // 组阻止表单提交时重新加载页面
      console.log(e.preventDefault())
      const searchTerm = search.value
      if (searchTerm && searchTerm !== '') {
        getMovies(SEARCH_API + searchTerm)
        search.value = ''
      } else {
        window.location.reload()
      }
    })
    
  4. 项目总结

    (1)CSS: translateY(101%)表示向下平移,>100% 则隐藏,当触发hover伪类时,变成transformY(0)显示

    (2)JS: 监听submit提交事件,当搜索框值不为空时,触发getMovies()方法

18. Background Slider(背景滑块)

Kapture 2022-12-15 at 21.47.16

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    body::before {
      content: '';
      position: absolute;
      top: 0;
      left: 0;
      width: 100%;
      height: 100vh;
      background-color: rgba(0, 0, 0, 0.7);
      z-index: -1;
    }
    
    // js
    rightBtn.addEventListener('click', () => {
      activeSlide++
      if (activeSlide > slides.length - 1) {
        activeSlide = 0
      }
      setBgToBody()
      setActiveSlide()
    })
    
  4. 项目总结

    (1)CSS: 使用before伪元素设置body的堆叠层级z-index:-1

    (2)JS: 监听右箭头的click点击事件,如果当前激活滑块超过总滑块个数,则重新从0开始

19. Theme Clock(主题时钟)

Kapture 2022-12-15 at 21.50.39

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .center-point::after {
      content: '';
      background-color: var(--primary-color);
      width: 5px;
      height: 5px;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%);
      border-radius: 50%;
    }
    
    // js
    function setTime() {
       const time = new Date();
       const month = time.getMonth() // 月份:0-11
       const day = time.getDay() // 星期:0-6
       const date = time.getDate() // 日:1-31
       const hours = time.getHours() // 小时:0-23
       const hoursForClock = hours >= 13 ? hours % 12 : hours;
       const minutes = time.getMinutes() // 分钟:0-59
       const seconds = time.getSeconds() // 秒:0-59
       const ampm = hours >= 12 ? 'PM' : 'AM'
    
       hourEl.style.transform = `translate(-50%, -100%) rotate(${scale(hoursForClock, 0, 12, 0, 360)}deg)`
       minuteEl.style.transform = `translate(-50%, -100%) rotate(${scale(minutes, 0, 60, 0, 360)}deg)`
       secondEl.style.transform = `translate(-50%, -100%) rotate(${scale(seconds, 0, 60, 0, 360)}deg)`
    
       timeEl.innerHTML = `${hoursForClock}:${minutes < 10 ? `0${minutes}` : minutes} ${ampm}`
       dateEl.innerHTML = `${days[day]}, ${months[month]} <span class="circle">${date}</span>`
    }
    
  4. 项目总结

    (1)CSS: 通过绝对定位absolutetransform:translate(-50%,-50%)实现水平垂直居中的效果

    (2)JS: 通过new Date()日期对象获取带有格式的时间,通过调用和例5相同的scale()工具函数实现指针转动效果

20. Button Ripple Effect(按钮涟漪效应)

Kapture 2022-12-15 at 21.52.59

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    button .circle {
      ...
      animation: myScale 0.5s ease-out;
    }
    
    @keyframes myScale {
      to {
        transform: translate(-50%, -50%) scale(3);
        opacity: 0;
      }
    }
    
    // js
    button.addEventListener('click', function (e) {
        // 鼠标坐标
        const x = e.pageX
        const y = e.pageY
    
        // 按钮最左侧的边相对于有定位的父元素的位置,没有的话则相对于body
        const buttonLeft = e.target.offsetLeft
        const buttonTop = e.target.offsetTop
    
        const xInside = x - buttonLeft
        const yInside = y - buttonTop
    
        const circle = document.createElement('span')
        circle.classList.add('circle')
        circle.style.top = yInside + 'px'
        circle.style.left = xInside + 'px'
    
        this.appendChild(circle)
        setTimeout(() => circle.remove(), 500)
      })
    
  4. 项目总结

    (1)CSS: 使用@keyframes定义动画名称,在animation属性中使用,实现放大及透明度变为0隐藏的效果

    (2)JS: 通过pageX/pageYoffsetLeft/offsetTop计算坐标,以此计算出circle类的top和left偏移值

21. Drag N Drop(拖拽)

Kapture 2022-12-15 at 21.54.07

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // html
    <div class="empty">
        <div class="fill" draggable="true"></div>
    </div>
    
    // js
    // 当用户开始拖动一个元素或者一个选择文本的时候触发
    function dragStart() {
      this.className += ' hold'
      setTimeout(() => (this.className = 'invisible'), 0)
    }
    // 在可拖动的元素或者被选择的文本进入一个有效的放置目标时触发。
    function dragEnter(e) {
      e.preventDefault()
      this.className += ' hovered'
    }
    // 当元素或者选择的文本被拖拽到一个有效的放置目标上时触发
    function dragOver(e) {
       // 组织默认动作 
      e.preventDefault()
    }
    // 在拖动的元素或选中的文本离开一个有效的放置目标时被触发
    function dragLeave() {
      this.className = 'empty'
    }
    // 在元素或选中的文本被放置在有效的放置目标上时被触发。
    function dragDrop() {
      this.className = 'empty'
      this.append(fill)
    }
    // 在拖放操作结束时触发
    function dragEnd() {
      this.className = 'fill'
    }
    
  4. 项目总结

    (1)HTML:draggable 用于标识元素是否允许使用拖放操作API

    (2)JS: 通过监听dragstart、dragEnter、dragOver、dragLeave、dragDrop、dragEnd事件实现从一个可拖拽元素-->拖拽-->放置该元素的过程

22. Drawing App(绘图应用)

Kapture 2022-12-15 at 21.55.45

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .toolbox > *:last-child{
        margin-left:auto;
    }
    
    // js
    function drawLine(x1, y1, x2, y2) {
      ctx.beginPath() // 创建一个开始路径
      ctx.moveTo(x1, y1) // 开始移动位置
      ctx.lineTo(x2, y2) // 行动路线
      ctx.strokeStyle = color // 线条颜色
      ctx.lineWidth = size * 2
      ctx.stroke() // 线条绘制
    }
    
  4. 项目总结

    (1)CSS:>为子选择器,*表示全部子元素,:last-child表示最后一个子元素

    (2)JS: 通过监听鼠标事件,计算x与y的坐标,配合canvas API实现绘图功能

23. Kinrtic Loader(永动机)

Kapture 2022-12-15 at 21.58.07

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .kinetic::after {
      ...
      // 下三角形
      width: 0;
      height: 0;
      border: 50px solid transparent;
      border-bottom-color: #fff;
      animation: rotateA 2s linear infinite 0.5s;
    }
    @keyframes rotateA {
        0%,
        25% {
          transform: rotate(0deg);
        }
      
        50%,
        75% {
          transform: rotate(180deg);
        }
      
        100% {
          transform: rotate(360deg);
        }
      }
    
  4. 项目总结

    (1)CSS: 首先通过width、height、border属性实现下三角形,然后通过animationtransform实现旋转动画效果

24. Content Placeholder(骨架屏)

Kapture 2022-12-15 at 21.59.32

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .animated-bg {
      background-image: linear-gradient(
        to right,
        #f6f7f8 0%,
        #edeef1 10%,
        #f6f7f8 20%,
        #f6f7f8 100%
      );
      background-size:200% 100%;
      animation:bgPos 1s linear infinite;
    }
    
    // js
    animated_bgs.forEach(bg => bg.classList.remove('animated-bg'))
    animated_bg_texts.forEach(bg => bg.classList.remove('animated-bg-text'))
    
  4. 项目总结

    (1)CSS: 通过linear-gradientanimation实现骨架屏加载效果

    (2)JS: 通过setTimeout设置2500ms后调用getData()方法,在该方法中移除所有关于骨架屏显示的类

25. Sticky Navbar(粘性导航栏)

Kapture 2022-12-15 at 22.02.06

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .nav{
        position:sticky;
        background-color:#222;
        top:0;
        left:0;
        right:0;
        transition:all 0.3s ease-in-out;
    }
    
    // js
    function fixNav() {
      if (window.scrollY > nav.offsetHeight + 150) {
        nav.classList.add('active')
      } else {
        nav.classList.remove('active')
      }
    }
    
  4. 项目总结

    (1)CSS: 使用position:sticky实现导航栏的粘性定位

    (2)JS:滚动条高度 > 设定值时,动态给导航添加样式类active

26. Double Vertical Slider(双垂直滑块)

Kapture 2022-12-15 at 22.04.54

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .slider-container .action-buttons .down-button {
      transform: translateX(-100%);
      border-top-left-radius: 5px;
      border-bottom-left-radius: 5px;
    }
    
    // js
    const changeSlide=(direction)=>{
        // 元素内部高度
        const sliderHeight=sliderContainer.clientHeight
        if(direction==='up'){
            activeSlideIndex++
            if(activeSlideIndex > slidesLength -1){
                activeSlideIndex=0
            }
        }else if(direction==='down'){
            activeSlideIndex--
            if(activeSlideIndex<0){
                activeSlideIndex = slidesLength-1
            }
        }
        slideRight.style.transform=`translateY(-${activeSlideIndex * sliderHeight}px)`
        slideLeft.style.transform=`translateY(${activeSlideIndex * sliderHeight}px)`
    }
    
  4. 项目总结

    (1)CSS: transform中的所有属性都是相对于自身进行转换

    (2)JS: 通过监听按钮的click事件,控制activeSlideIndex的变化,实现左右两侧translateY属性的更改,实现错位效果

27. Toast Notification(吐司/消息条 通知)

Kapture 2022-12-15 at 22.06.42

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    const types = ['info', 'success', 'error']
    function getRandomType() {
      return types[Math.floor(Math.random() * types.length)]
    }
    
  4. 项目总结

    (1)JS: 通过getRandomType方法使用Math.floor()Math.random()函数随机从types数组中返回一个类型,然后增加到notif类中,实现文字颜色的变化

28. Github Profiles(Github 简介)

Kapture 2022-12-15 at 22.11.12

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    async function getUser(username) {
      try {
        const { data } = await axios(APIURL + username)
        createUserCard(data)
        getRepos(username)
      } catch (err) {
        if (err.response.status === 404) {
          createUserCard('No profile with this username')
        }
      }
    }
    
  4. 项目总结

    (1)JS: 使用async/await配合axios处理异步请求,通过调用getUser()方法,接收用户输入要搜索的用户名,搭配github API获取数据进行页面渲染

29. Double Click Heart(双击爱心)

Kapture 2022-12-15 at 22.13.30

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .loveMe .fa-heart {
      position: absolute;
      animation: grow 0.6s linear;
      transform: translate(-50%, -50%) scale(0);
    }
    @keyframes grow {
      to {
        transform: translate(-50%, -50%) scale(10);
        opacity: 0;
      }
    }
    
    // js
    loveMe.addEventListener('click', e => {
      if (clickTime === 0) {
        clickTime = new Date().getTime()
      } else {
        if (new Date().getTime() - clickTime < 800) {
          createdHeart(e)
          clickTime = 0
        } else {
          clickTime = new Date().getTime()
        }
      }
    })
    
  4. 项目总结

    (1)CSS: 使用font-awesome图标字体库中的类名.fa-heart实现爱心图标,配合transform和animation实现动画效果

    (2)JS: 通过监听click点击事件,判断点击的间隔时间戳是否小于800,如果满足条件表示双击了,则调用createdHeart()方法创建爱心

30. Auto Text Effect(打字机效果)

Kapture 2022-12-15 at 22.15.34

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    function writeText() {
      textEl.innerText = text.slice(0, idx)
      idx++
      if (idx > text.length) {
        idx = 1
      }
      setTimeout(writeText, speed)
    }
    
  4. 项目总结

    (1)JS: 通过定时器setTimeout设置每speed秒调用一次writeText()方法,利用slice()方法进行字符的截取

31. Password Generator(密码生成器)

Kapture 2022-12-15 at 22.22.53

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    #result::-webkit-scrollbar {
      width: 6px;
      height:6px;
      background:#fff;
    }
    
    // js
    function generatePassword(lower, upper, number, symbol, length) {
      let generatePassword = ''
      const typesCount = lower + upper + number + symbol
      
      const typeArr=[{lower},{upper},{number},{symbol}].filter(item=>Object.values(item)[0])
    
      if(typesCount===0) return ''
    
      for(let i=0;i<length;i+=typesCount){
        typeArr.forEach(type=>{
            const funcName=Object.keys(type)[0]
            generatePassword +=randomFunc[funcName]()
        })
      }
      const finalPassword=generatePassword.slice(0,length)
      return finalPassword
    }
    
  4. 项目总结

    (1)CSS: 使用::-webkit-scrollbar伪元素选择器去修改基于webkit的浏览器的滚动条样式

    (2)JS: 通过Object.values()Object.keys()分别获取到值数组typeArr属性数组,循环遍历取出数组的第一项funcName并将其作为参数传给randomFunc()方法,得到随机值

32. Good Cheap Fast(好、便宜、快)

Kapture 2022-12-15 at 22.26.10

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .toggle:checked + .label .ball{
        animation:slideOn 0.3s linear forwards;
    }
    
    @keyframes slideOn{
        0%{
            transform:translateX(0) scale(1);
        }
        50%{
            transform:translateX(20px) scale(1.2);
        }
        100%{
            transform:translateX(40px) scale(1);
        }
    }
    
    // js
    toggles.forEach(toggle =>
      toggle.addEventListener('change', e => doTheTrick(e.target))
    )
    
  4. 项目总结

    (1)CSS: 通过:checked伪类选择器和+兄弟选择器,配合animation动画实现选中效果

    (2)JS: 通过forEach遍历每一个toggle开关,通过监听change改变,传递触发事件的对象doTheTrick()方法

33. Notes App(笔记应用)

Kapture 2022-12-15 at 22.30.47

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    // getItem() 接收一个key返回value
    const notes = JSON.parse(localStorage.getItem('notes'))
    ...
    textArea.addEventListener('input',(e)=>{
            const {value} =e.target
            main.innerHTML=marked(value)
            updateLS()
    })
    ...
    function updateLS(){
        const notesText=document.querySelectorAll('textarea')
        const notes=[]
        notesText.forEach(note=>notes.push(note.value))
        // 接受一个键名和值作为参数,将会把键名添加到给定的 Storage 对象中
        localStorage.setItem('notes',JSON.stringify(notes))
    }
    
  4. 项目总结

    (1)JS: 使用marked.js在线编译Markdown代码为Html并直接显示,通过localStorage.setItem()localStorage.getItem()进行值的存储和获取

34. Animated Countdown(动画倒计时)

Kapture 2022-12-15 at 22.32.28

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .nums span.in {
      transform: translate(-50%, -50%) rotate(0deg);
      animation: goIn 0.5s ease-in-out;
    }
    @keyframes goIn {
      0% {
        transform: translate(-50%, -50%) rotate(120deg);
      }
      30% {
        transform: translate(-50%, -50%) rotate(-20deg);
      }
      60% {
        transform: translate(-50%, -50%) rotate(10deg);
      }
      100% {
        transform: translate(-50%, -50%) rotate(0deg);
      }
    }
    
    // JS
    function runAnimation() {
      nums.forEach((num, idx) => {
        const nextToLast = nums.length - 1
        num.addEventListener('animationend', e => {
          // 当前使用goIn动画且不是最后一个元素
          if (e.animationName === 'goIn' && idx !== nextToLast) {
            num.classList.remove('in')
            num.classList.add('out')
          }
          // 当前使用goOut动画且下一个不为空
          else if (e.animationName === 'goOut' && num.nextElementSibling) {
            num.nextElementSibling.classList.add('in')
          }
          // 最后一个元素
          else {
            counter.classList.add('hide')
            finalMessage.classList.add('show')
          }
        })
      })
    }
    
  4. 项目总结

    (1)CSS: 使用translate(-50%,-50%)实现元素的水平垂直居中,使用goIn动画实现元素倒计时时的抖动效果

    (2)JS: nextToLast拿到所有计时数字的个数,通过监听animationend事件判断当前元素使用的动画名,进行类的添加或元素的隐藏与显示

35. Image Carousel(轮播图)

Kapture 2022-12-15 at 22.36.49

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    function changeImage() {
      // 如果为最后一张,则设置idx为0,从头开始;
      if (idx > img.length - 1) {
        idx = 0
      }
      // 如果idx为0,则设置idx为img.length-1,从最后开始
      else if (idx < 0) {
        idx = img.length - 1
      }
      imgs.style.transform = `translateX(${-idx * 500}px)`
    }
    
  4. 项目总结

    (1)JS: 通过idx变量判断当前图片为第几张,根据idx计算出translateX()的值,实现图片的切换

36. Hoverboard(悬浮板)

Kapture 2022-12-15 at 22.38.48

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // JS
    function setColor(element) {
      const color = getRandomColor()
      element.style.background = color
      element.style.boxShadow = `0 0 2px ${color},0 0 10px ${color}`
    }
    
    function removeColor(element) {
      element.style.background = '#1d1d1d'
      element.style.boxShadow = '0 0 2px #000'
    }
    
    function getRandomColor() {
      return colors[Math.floor(Math.random() * colors.length)]
    }
    
  4. 项目总结

    (1)JS: 通过监听鼠标的mouseover()mouseout()事件执行setColor()方法和removeColor()方法,设置鼠标悬浮时方块颜色的变化

37. Pokedex(图鉴)

Kapture 2022-12-15 at 22.41.35

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    const createPokemonCard = pokemon => {
      // 创建 div 并添加 pokemon类  
      const pokemonEl = document.createElement('div')
      pokemonEl.classList.add('pokemon')
      // 获取 name 和 id
      const name = pokemon.name[0].toUpperCase() + pokemon.name.slice(1)
      const id = pokemon.id.toString().padStart(3, '0')
      // 背景颜色
      const poke_types = pokemon.types.map(type => type.type.name)
      const type = main_types.find(type => poke_types.indexOf(type) > -1)
      const color = colors[type]
      pokemonEl.style.backgroundColor = color
      // 生成元素内容
      const pokemonInnerHTML = `
        <div class="img-container">
            <img src="https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/${pokemon.id}.png"" alt="${name}">
        </div>
        <div class="info">
            <span class="number">#${id}</span>
            <h3 class="name">${name}</h3>
            <small class="type">Type: <span>${type}</span> </small>
        </div>
        `
      pokemonEl.innerHTML = pokemonInnerHTML
      // 添加子节点
      poke_container.appendChild(pokemonEl)
    }
    
  4. 项目总结

    (1)JS: 通过fetch()请求获取值后传递给createPokemonCard()方法,通过slice、padStart、map、find等一系列API渲染出图鉴列表,具体使用方法可查阅MDN

38. Mobile Tab Navigation(手机导航)

Kapture 2022-12-15 at 22.44.06

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    listItems.forEach((item, index) => {
      item.addEventListener('click', () => {
        hideAllContents()
        hideAllItems()
        item.classList.add('active')
        contents[index].classList.add('show')
      })
    })
    
  4. 项目总结

    (1)JS: 循环遍历listItems,监听click事件,首先调用hideAllContents()hideAllItems()方法清空所有元素的active和show类,然后分别给被点击的元素添加active类和show类

39. Password Strength Background(密码长度背景变化)

Kapture 2022-12-15 at 22.49.36

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // html
    <div class="my-4 text-left">
    <label for="email" class="text-gray-900">Email:</label>
    <input class="border block w-full p-2 mt-2 rounded" type="email" name="email" id="email" placeholder="Enter Email">
    </div>
    
    // js
    password.addEventListener('input', e => {
      const input = e.target.value
      const len = input.length
      const blurValue = 20 - len * 2
      background.style.filter=`blur(${blurValue}px)`
    })
    
  4. 项目总结

    (1)HTML: 使用 TailWindCss 简化样式编写,👉官网👈

    (2)JS: 监听密码输入框input事件,获取输入值的长度,改变filter:blur()属性值

40. 3d Background Boxes(3D背景框)

Kapture 2022-12-15 at 22.51.26

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .box::before {
      content: '';
      background-color: #f9ca24;
      position: absolute;
      bottom: -15px;
      left: 8px;
      height: 15px;
      width: 100%;
      transform: skewX(45deg);
    }
    
    // js
    function createBoxes() {
      for (let i = 0; i < 4; i++) {
        for (let j = 0; j < 4; j++) {
          const box = document.createElement('div')
          box.classList.add('box')
          box.style.backgroundPosition = `${-j * 125}px ${-i * 125}px`
          boxesContainer.appendChild(box)
        }
      }
    }
    
  4. 项目总结

    (1)CSS: transform:skewX()将元素在二维平面上进行水平转换

    (2)JS: 通过双层for循环生成16个box,并使用backgroundPosition为每一个背景图片设置初始位置

41. Verify Account Ui(验证账户用户界面)

Kapture 2022-12-15 at 22.53.53

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .code::-webkit-outer-spin-button,
    .code::-webkit-inner-spin-button {
      -webkit-appearance: none;
      margin: 0;
    }
    
    // js
    codes.forEach((code, idx) => {
      code.addEventListener('keydown', e => {
        if (e.key >= 0 && e.key <= 9) {
          codes[idx].value = ''
          setTimeout(() => codes[idx + 1].focus(), 10)
        } else if (e.key === 'Backspace') {
          setTimeout(() => codes[idx - 1].focus(), 10)
        }
      })
    })
    
  4. 项目总结

    (1)CSS: -webkit-appearance: none 设置输入框不应用任何特殊样式,此处为隐藏数字输入框的增加/减少按钮

    (2)JS: 通过监听键盘的keydown事件,当用户输入一个数字时,会自动清空当前输入框,然后跳到下一个输入框,按下Backspace(回退)键时,可跳到上一个输入框

42. Live User Filter(实时用户过滤器)

Kapture 2022-12-15 at 22.56.56

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .user-list li:not(:last-of-type) {
      border-bottom: 1px solid #eee;
    }
    
    // js
    filter.addEventListener('input', e => filterData(e.target.value))
    function filterData(searchTerm){
        listItems.forEach(item=>{
            if(item.innerText.toLowerCase().includes(searchTerm.toLowerCase())){
                item.classList.remove('hide')
            }else{
                item.classList.add('hide')
            }
        })
    }
    
  4. 项目总结

    (1)CSS: li:not(:last-of-type)设置不是最后一个li类型的元素有下边框线

    (2)JS: 监听输入框的input事件,接受文本框输入的值,通过forEach循环每一个li元素中的文本,筛选出匹配项,如匹配成功则移除hide类,反之添加hide类

43. Feedback Ui Design(用户反馈界面设计)

Kapture 2022-12-15 at 22.58.40

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // JS
    ratingsContainer.addEventListener('click', e => {
      // 点击图片:检查是否有下一个兄弟元素
      if (
        e.target.parentNode.classList.contains('rating') &&
        e.target.nextElementSibling
      ) {
        removeActive()
        e.target.parentNode.classList.add('active')
        selectedRating = e.target.nextElementSibling.innerHTML
      }
      // 点击文字:检查是否有上一个兄弟元素且兄弟元素为IMG
      else if (
        e.target.parentNode.classList.contains('rating') &&
        e.target.previousSibling &&
        e.target.previousElementSibling.nodeName === 'IMG'
      ) {
        removeActive()
        e.target.parentNode.classList.add('active')
        selectedRating = e.target.innerHTML
      }
    })
    
  4. 项目总结

    (1)JS: 监听click点击事件,判断用户点击的是图片还是文字,执行对应操作:给点击元素的父元素添加active类,获取文字值赋给selectedRating变量

44. Custom Range Slider(自定义范围滑块)

Kapture 2022-12-15 at 23.00.07

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    /* 设置滑块可滑动区域样式 */
    input[type='range']::-webkit-slider-runnable-track{
        background: purple;
        border-radius: 4px;
        width: 100%;
        height: 10px;
        cursor: pointer;
    }
    /* ::-webkit-slider-thumb 设置滑块样式 */
    input[type='range']::-webkit-slider-thumb{
        -webkit-appearance: none;
        height: 24px;
        width: 24px;
        background:#fff;
        border-radius: 50%;
        border: 1px solid purple;
        margin-top: -7px;
        cursor: pointer;
    }
    
    // js
    range.addEventListener('input', e => {
      const value = +e.target.value
      const label = e.target.nextElementSibling
      
      // 获取滑动区域宽度:300px(带单位)
      const range_width = getComputedStyle(e.target).getPropertyValue('width')
      console.log('range_width: ', range_width);
      // 获取label宽度:80px
      const label_width = getComputedStyle(label).getPropertyValue('width')
    
      // 去掉px,获取单纯数字
      const num_width = +range_width.substring(0, range_width.length - 2)
      const num_label_width = +label_width.substring(0, label_width.length - 2)
    
      const max = +e.target.max
      const min = +e.target.min
      
      // label元素位置
      const left =  value * (num_width / max) -  num_label_width / 2 +  scale(value, min, max, 10, -10)
      label.style.left = `${left}px`
      
      label.innerHTML = value
    })
    
  4. 项目总结

    (1)CSS: 通过伪类::-webkit-slider-runnable-track和::-webkit-slider-thumb自定义在Chrom 和 Safari 内核中滑块区域和滑块的样式

    (2)JS: 监听input事件,分别获取到滑动区域宽度值、label宽度值、滑块最大值、滑块最小值,通过scale()方法计算出label元素的left值,实现位置偏移效果

45. Netfix Mobile Navigation(Netfix 移动导航)

Kapture 2022-12-15 at 23.01.30

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .nav-black {
      background-color: rgb(34, 31, 31);
      width: 60%;
      max-width: 480px;
      min-width: 320px;
      transition-delay: 0.4s;
    }
    .nav-black.visible {
      transition-delay: 0s;
    }
    
    // js
    open_btn.addEventListener('click',()=>{
        navs.forEach(nav=>nav.classList.add('visible'))
    })
    
    close_btn.addEventListener('click',()=>{
        navs.forEach(nav=>nav.classList.remove('visible'))
    })
    
  4. 项目总结

    (1)CSS: 通过transition-delay设置导航显示的延迟时间,实现黑、红、白三色的展示

    (2)JS: 监听鼠标点击click事件,给导航卡片动态添加/移除visible类

46. Quiz App(测验应用)

Kapture 2022-12-16 at 09.12.07

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    submitBtn.addEventListener('click', () => {
      const answer = getSelected()
      if (answer) {
        if (answer === quizData[currentQuiz].correct) {
          score++
        }
        currentQuiz++
        if (currentQuiz < quizData.length) {
          loadQuiz()
        } else {
          quiz.innerHTML = `
                <h2>You answered ${score}/${quizData.length} questions correctly</h2>
                <button onClick="location.reload()">Reload</button>
                `
        }
      }
    })
    
  4. 项目总结

    (1)JS: 监听click事件,通过getSelected()方法拿到用户选择的答案,然后和正确答案比较,答对的话成绩+1,然后通过loadQuiz()渲染出下一题的数据

47. Testimonial Box Switcher(推荐盒切换)

Kapture 2022-12-16 at 09.19.14

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .content {
      line-height: 28px;
      text-align: justify;
    }
    .progress {
      background-color: #fff;
      height: 4px;
      width: 100%;
      animation: grow 10s linear infinite;
      transform-origin: left;
    }
    
    // js
    let index = 1
    function updateTestimonial() {
      const { name, position, photo, text } = testimonials[index]
      content.innerHTML = text
      userImage.src = photo
      username.innerHTML = name
      role.innerHTML = position
    
      index++
      if (index > testimonials.length) {
        index = 0
      }
    }
    setInterval(updateTestimonial,10000);
    
  4. 项目总结

    (1)CSS:text-align:justify设置文字两侧对齐,使用animationtransform-origin实现进度条效果

    (2)JS: 通过ES6{}解构出对应数据,使用定时器setInterval实现每10s轮询一次内容

48. Random Image Feed(随机图)

Kapture 2022-12-16 at 09.22.16

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    const urls = 'https://source.unsplash.com/random/'
    const rows = 5
    for (let i = 0; i < rows * 3; i++) {
      const img = document.createElement('img')
      img.src = `${urls}${getRandomSize()}`
      container.appendChild(img)
    }
    
  4. 项目总结

    (1)JS: 使用image的src属性配合getRandomSize()方法,通过for循环实现每次重新加载便会随机渲染出15张

49. Todo List(待办事项)

Kapture 2022-12-16 at 09.28.11

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // css
    .input:focus{
        outline-color: rgb(179,131,226);
    }
    
    // js
    function addTodo(todo) {
      let todoText = input.value
      if (todo) {
        todoText = todo.text
      }
      if (todoText) {
        const todoEl = document.createElement('li')
        // 对storage中的数据进行判断
        if (todo && todo.completed) {
          todoEl.classList.add('completed')
        }
        todoEl.innerText = todoText
        // 监听鼠标左键点击事件
        todoEl.addEventListener('click', () => {
          todoEl.classList.toggle('completed')
          updateLS()
        })
        // 监听鼠标点击右键或者按下键盘上的菜单键
        todoEl.addEventListener('contextmenu', e => {
          e.preventDefault()
          todoEl.remove()
          updateLS()
        })
        // 元素追加
        todosUL.appendChild(todoEl)
        input.value = ''
        updateLS()
      }
    }
    
    function updateLS() {
      todosEl = document.querySelectorAll('li')
      const todos = []
      todosEl.forEach(item => {
        todos.push({
          text: item.innerText,
          completed: item.classList.contains('completed'),
        })
      })
      localStorage.setItem('todos', JSON.stringify(todos))
    }
    
  4. 项目总结

    (1)CSS: 通过:focus伪类和outline-color设置文本框聚焦时外边框线的颜色

    (2)JS: 通过localstoragesetItem()getItem()方法对文本框数据进行存储与获取,通过监听左键click()点击事件和右键contextmenu()事件进行类的添加/移除及元素的删除

50. Insect Catch Game(昆虫捕捉游戏)

Kapture 2022-12-16 at 09.32.35

  1. 👉项目源码👈

  2. 👉在线演示👈

  3. 代码片段

    // js
    // 点击昆虫开始游戏
    choose_insect_btns.forEach(btn => {
      btn.addEventListener('click', () => {
        const img = btn.querySelector('img')
        const src = img.getAttribute('src')
        const alt = img.getAttribute('alt')
        selected_insect = { src, alt }
        screen[1].classList.add('up')
        setTimeout(createInsect, 1000)
        startGame()
      })
    })
    // 创建昆虫
    function createInsect() {
      const insect = document.createElement('div')
      insect.classList.add('insect')
      const { x, y } = getRandomLocation()
      insect.style.top = `${y}px`
      insect.style.left = `${x}px`
      insect.innerHTML = `
        <img 
            src="${selected_insect.src}" 
            alt="${selected_insect.alt}"
            style="transform:rotate(${Math.random() * 360}deg)"
        >`
      insect.addEventListener('click', catchInsect)
      game_container.appendChild(insect)
    }
    // 捕捉昆虫
    function catchInsect() {
      increaseScore()
      this.classList.add('caught')
      setTimeout(() => this.remove(), 2000)
      addInsects()
    }
    
    // 增加昆虫
    function addInsects() {
      setTimeout(createInsect, 1000)
      setTimeout(createInsect, 1500)
    }
    
  4. 项目总结

    (1)JS: 通过监听click点击事件,存储点击”昆虫”的srcalt并在创建时使用,在通过getRandomLocation()方法在屏幕中随机位置出现昆虫,每次”捕捉“到1个昆虫时,调用addInsects()方法,使用setTimeout()定时器创建两个”昆虫”

Tips:50Projects中有些项目所用API需要科学上网🛝才可以正常使用,文中注释可能有一些理解不到位的地方,欢迎批评指正☀️

© 版权声明
THE END
喜欢就支持一下吧
点赞7 分享
评论 抢沙发
头像
欢迎您留下宝贵的见解!
提交
头像

昵称

取消
昵称表情代码图片

    暂无评论内容