新手小白的js之路---事件(案例)

68 阅读6分钟

一、事件对象e(event)

  • 所有的标签添加点击事件之后,这个事件的执行函数,会默认接收一个参数我们一殷会将这个参数命名为event;但是名字有点麻烦,所以有人会写ev;现在更加习惯写e;
  • 这个形参e是一个对象,里面有很多属性,其中一个叫target;
  • 这个属性的值就是你点击的标签;
    box.onclick = function(e) {
        console.log(e.target)//打印出的内容是-你点击的标签
    }

二、扩展

1、反引号

  • 字符串需要使用''或者""包裹,但是不能换行,内部也不能书写变量;如果想要书写换行或者书写变量,那么需要使用反引号(``)
  • 如果需要在反引号内书写换行,那么可以直接换行
  • 如果需要在反引号内书写变量,需要使用${}将变量包裹起来
    return `
        <li>
            <img src="${item.imgurl}" alt="">
            <p>${item.title}</p>
        </li>
        `

2、Object

  • Object.keys() 获取对象的全部属性名组成的数组
  • Object.values() 获取对象的全部属性值组成的数组

3、事件冒泡

  • 当你点击了一个元素的时候,相当于触发了这个元素的点击事件;当一个元素的点击事件被触发的时候,那么按照js中事件冒泡的特性会将这个事件的触发也传递给自己的父级
  • 冒泡会从点击的元素开始, 一直到页面最顶层的元素;哪怕你的元素没有绑定点击事件, 也会触发事件冒泡
    // CSS代码
    <style>
    .box1 {
        width: 500px;
        height: 500px;
        background-color: skyblue;
    }
    .box2 {
        width: 300px;
        height: 300px;
        background-color: pink;
    }
    .box3 {
        width: 100px;
        height: 100px;
        background-color: green;
    }
    .box4 {
        width: 60px;
        height: 60px;
        background-color: rgb(226, 82, 21);
    }
    </style>
    
    // html
    <div class="box1">
        <div class="box2">
            <div class="box3">
                <div class="box4"></div>
            </div>
        </div>
    </div>
    // js代码
    var box1 = document.querySelector('.box1')
    var box2 = document.querySelector('.box2')
    var box3 = document.querySelector('.box3')
    var myBody = document.querySelector('body')
    // box1.onclick = function () {
        //     console.log('点击了蓝色盒子')
    // }
    box2.onclick = function () {
        console.log('点击了粉色盒子')
    }
    myBody.onclick = function () {
        console.log('body')
    }
    box3.onclick = function () {
        console.log('点击了绿色盒子')
    }

4、事件委托

  • 件委托就是利用事件冒泡的原理:将所有子元素的一个事件(点击事件), 委托给共同的父级

    <style>
    .box1 {
        width: 500px;
        height: 500px;
        background-color: skyblue;
    }

    .box2 {
        width: 300px;
        height: 300px;
        background-color: pink;
    }

    .box3 {
        height: 100px;
        background-color: green;
    }

    .box4 {
        width: 60px;
        height: 60px;
        background-color: rgb(226, 82, 21);
    }

    ul {
        width: 500px;
        background-color: rgb(43, 238, 13);
    }

    li {
        width: 100px;
        background-color: salmon;
        margin-top: 10px;
    }
    </style>
    <!-- 事件冒泡对应代码 -->
    <!-- <div class="box1">
         <div class="box2">
             <div class="box3">
                 <div class="box4"></div>
             </div>
         </div>
         <div class="box2000">
             <div class="box3000">
                 <div class="box4000"></div>
             </div>
         </div>
     </div> -->
    
    <ul>
        <li class="item">1</li>
        <li class="item">2</li>
        <li class="item">3</li>
        <li class="item">4</li>
    </ul>
    // js代码
    // 1. 绑定事件
    // var lis = document.querySelectorAll('li')
    // for (var i = 0; i < lis.length; i++) {
    //     lis[i].onclick = function () {
    //         console.log('事件触发')
    //     }
    // }
    // 2. 新增一个 li
    // var myLi = document.createElement('li')
    // myLi.innerHTML = '我是通过 JS 创建的, 请测试我是否具有点击事件'
    // document.querySelector('ul').appendChild(myLi)

    /**
      *  上述代码中 因为是 先获取的 li , 然后绑定完事件之后, 又新增了li
      * 
      *  所以新增的 li 就没有事件
      * 
      *  所以解决上述问题的最好的方案就是 利用 事件委托
      *  将 所有 li 的事件 委托给 共同的 父级
      */

    var myUl = document.querySelector('ul')
    myUl.onclick = function (e) {
        if (e.target.className === 'item') {
            console.log('触发事件')
        }
    }

    // 2. 新增一个 li
    var myLi = document.createElement('li')
    myLi.className = 'item'
    myLi.innerHTML = '我是通过 JS 创建的, 请测试我是否具有点击事件'
    document.querySelector('ul').appendChild(myLi)

三、元素的偏移量

1、获取元素偏移量

  • 就是元素在页面上相对于参考父级的左边和上边的距离

1)offsetParent

  • 获取元素的偏移量参考父级
  • 其实就是假设你要给一个元素 绝对定位 的时候
  • 它是根据谁来进行定位的, 那么这个元素的偏移量参考父级就是谁
    //获取元素的参考父级
    console.log(box_s.offsetParent)//div.box
    console.log(box.offsetParent)//body

2)offsetLeft / offsetTop

微信截图_20230830110705.png

  • 获取的事元素左边的偏移量和上边的偏移量

    • offsetLeft 该元素相对于参考父级的左侧偏移量
    • offsetTop 该元素相对于参考父级的上侧偏移量
    var box = document.querySelector('.box')
    var box_s = document.querySelector('.box_s')

    //获取元素的偏移量 获取到的是一个数字,没有px单位
    console.log('left: ', box_s.offsetLeft)  // 32
    console.log('top: ', box_s.offsetTop)    // 31
    console.log(box_s.offsetTop + 'px')      // 31px

2、获取元素尺寸

  • 获取元素的占地面积

微信截图_20230830110407.png

1)offsetWidth 和 offsetHeight

  • offsetWidth: 获取元素内容 + padding + border 的宽度
  • offsetHeight: 获取元素内容 + padding + border 的高度
    var div1 = document.querySelector('.div1')
    console.log('offsetWidth',div1.offsetWidth)   // 360
    console.log('offsetHeight',div1.offsetHeight) // 360

2)clientWidth 和 clientHeight

  • clientWidth 获取元素内容 + padding 的宽度
  • clientHeight 获取元素内容 + padding 的高度
    var div2 = document.querySelector('.div2')
    console.log('offsetWidth',div2.clientWidth)   // 340
    console.log('offsetHeight',div2.clientHeight) // 340

注意

  • 获取到的尺寸是没有单位的数字

  • 当元素在页面中不占位置的时候, 获取到的是 0

    • display: none 元素在页面不占位
    • visibility: hidden 元素在页面占位

3、获取浏览器窗口尺寸

  • 可视区域的尺寸
  • document.documentElement.clientWidth: 浏览器可视窗口的宽度
  • document.documentElement.clientHeight: 浏览器可视窗口的高度

四、js分页案例

1、思路

    /* 分页的功能
     * 1. 刚打开页面
     *   1.1 截取部分数据, 渲染页面
     *   1.2 调整页码
     *   1.3 调整上下页按钮的样式
     
     * 2. 点击上一页
     *   2.1 判断能不能去上一页
     *   2.2 截取部分数据, 渲染页面
     *   2.3 调整页码
     *   2.4 调整上下页按钮的样式
     
     * 3. 点击下一页
     *   3.1 判断能不能去下一页
     *   3.2 截取部分数据, 渲染页面
     *   3.3 调整页码
     *   3.4 调整上下页按钮的样式
     
     * 4. 切换每页展示数据的数量
     *   4.1 调整每页展示的数量
     *   4.2 截取部分数据, 渲染页面
     *   4.3 调整页码
     *   4.4 调整上下页按钮的样式
     
     * 逻辑:
     * 0. 创建一个渲染函数
     *   0.1 截取部分数据
     *   0.2 调整页码
     *   0.3 调整按钮样式
     
     * 1. 初次打开页面
     *   1.1 直接调用
     
     * 2. 点击上一页按钮
     *   2.1 判断能不能去上一页
     *   2.2 调整当前页
     *   2.3 调用渲染函数
     
     * 3. 点击下一页按钮
     *   3.1 判断能不能去下一页
     *   3.2 调整当前页
     *   3.3 调用渲染函数
     
     * 4. 切换每页展示数量
     *   4.1 切换展示的数量
     *   4.2 调用渲染函数

2、实现


    // 0. 获取标签
    var total = document.querySelector('.total')
    var prev = document.querySelector('.prev')
    var next = document.querySelector('.next')
    var select = document.querySelector('select')

    // 0. 创建全局变量 (在当前 JS 中, 任何一个地方都能使用)
    var currentNum = 1  // 默认在 第一页
    var pageSize = 4    // 默认每页展示 4 条数据
    var totalNum = 0    // 计算总页码

    // 0. 创建一个渲染函数
    function bindHtml() {
        /**
          *  0.1 截取部分数据, 渲染页面
          *  假设 当前是第 1 页  每页展示 4 条
          *  页码 === 1          =>      [0]~[3]
          *  页码 === 2          =>      [4]~[7]
          *  页码 === 3          =>      [8]~[11]
          *   
          *  我们假设 当前页的数字存储在 currentNum 中, 每页展示多少条的数字存储在 pageSize 中
          *  
          *  第一版
          *  开始下标: (currentNum - 1) * pageSize
          *  结束下标: currentNum * pageSize - 1
          *  
          *  但是 我们用的截取的方法参数有一个特点: 包前不包后, 所以开始下标不变, 结束下标 需要 + 1
          *  
          *  第二版
          *  开始下标: (currentNum - 1) * pageSize
          *  结束下标: currentNum * pageSize - 1 + 1
          *  
          *  所以最终的优化版本
          *  
          *  第三版
          *  开始下标: (currentNum - 1) * pageSize
          *  结束下标: currentNum * pageSize
          */
        var newArr = list.slice((currentNum - 1) * pageSize, currentNum * pageSize)

        var htmlStr = ""
        for (var i = 0; i < newArr.length; i++) {
            htmlStr += `
                <li>
                    <img src="${newArr[i].pic}" alt="">
                    <p>${newArr[i].name}</p>
                    <p>城市: ${newArr[i].city}</p>
                    <p>地址: ${newArr[i].address}</p>
                    <p>价格: ${newArr[i].price}</p>
                    <p>时间: ${newArr[i].showTime}</p>
                </li>
                `
        }
        document.querySelector('ul').innerHTML = htmlStr

        // 0.2 调整页码
        totalNum = Math.ceil(list.length / pageSize)
        total.innerHTML = currentNum + ' / ' + totalNum

        // 0.3 调整按钮样式
        prev.className = currentNum === 1 ? 'prev disable' : 'prev'
        next.className = currentNum === totalNum ? 'next disable' : 'next'
    }

    // 1. 初次打开页面 直接调用
    bindHtml()

    // 2. 点击上一页
    prev.onclick = function () {
        // 2.1 判断能不能去上一页
        if (currentNum === 1) return

            // 2.2 调整页码
            currentNum--

            // 2.3 重新渲染
            bindHtml()
        }

        // 3. 点击下一页
        next.onclick = function () {
            // 3.1 判断能不能去下一页
            if (currentNum === totalNum) return

            // 3.2 调整页码
            currentNum++

            // 3.3 重新渲染
            bindHtml()
        }

        // 4. 切换每页展示多少条
        select.onchange = function () {
            // console.log('选择框的内容改变了', select.value - 0)

            currentNum = 1
            
            // 修改每页展示多少条
            pageSize = select.value - 0

            // 重新渲染页面
            bindHtml()
        }

五、js瀑布流案例

1、思路

瀑布流: 是目前主流的一个前端分页方式

    1. 打开页面渲染一套数据
      
    2. 当页面滚动到某一个位置的时候, 重新请求新的数据
      
    逻辑:
        封装一个渲染函数
        首次打开页面的时候调用
        当页面滚动到某一个位置的时候, 重新调用渲染函数, 拼接上新的数据
      
      
        我们规定 每页固定展示 8 条数据, 因为要首次渲染的时候, 撑满首页

2、实现

    var myUl = document.querySelector('ul')
    var loadding = document.querySelector('.loadding')

    // 0. 全局变量
    // 表示当前页
    var currentNum = 1  
    // 表示每页展示多少条 (这个变量不会被更改)
    var pageSize = 8    
    // 计算总页码
    var totalNum = Math.ceil(list.length / pageSize)  
    // 作为一个开关变量, 用于控制是否请求新数据
    var flag = true                                         

    // 0. 封装渲染函数
    function bindHtml() {
        var newArr = list.slice((currentNum - 1) * pageSize, currentNum * pageSize)

        var htmlStr = ""
        for (var i = 0; i < newArr.length; i++) {
        htmlStr += `
            <li>
                <img src="${newArr[i].pic}" alt="">
                <p>${newArr[i].name}</p>
                <p>城市: ${newArr[i].city}</p>
                <p>地址: ${newArr[i].address}</p>
                <p>价格: ${newArr[i].price}</p>
                <p>时间: ${newArr[i].showTime}</p>
            </li>
            `
        }

        // 因为瀑布流需要的是拼接数据, 所以此处不应该使用 = , 而是使用 +=
        myUl.innerHTML += htmlStr
    }

    bindHtml()

    // 2. 给页面添加滚动事件
    window.onscroll = function () {
    // 2.1 如果当前页 === 总页码    代表没有下一页了, 所以这个事件可以不执行了
    if (currentNum === totalNum) return

    /**
      *  2.2 如果代码能够执行到这个位置, 说明还有数据
      
      *  但是需要在 UL 的底边到达 页面的可视区域的时候, 在加载新数据
      *  
      *  
      *  页面卷去的高度 + 浏览器可视区域的高度 > UL 顶部偏移量 + UL 的高度       满足这个条件 加载新数据
      *  
      *  页面卷去的高度 + 浏览器可视区域的高度 <= UL 顶部偏移量 + UL 的高度      满足这个条件 不需要加载新数据 直接 return
      */
    // 页面卷去的高度
    var docHeight = document.documentElement.scrollTop
    // 浏览器可视区域的高度
    var winHeight = document.documentElement.clientHeight
    // UL 顶部偏移量
    var ULTop = myUl.offsetTop
    // UL 的高度
    var ULHeight = myUl.offsetHeight
    if (docHeight + winHeight < ULTop + ULHeight) return

    // 2.3 如果代码执行到这里, 说明后续还有数据, 并且到了加载新数据的时候了


    // 2.3.1 查看开关变量, 是否允许我们请求
    if (!flag) return
    // 2.3.2 开始请求数据前, 关闭开关变量, 直到这一次请求完毕的时候, 才能重新发起请求
    flag = false

            
    // 数据请求前 打开 loadding
    loadding.style.display = 'flex'
    console.log('此时请求了 1 次新数据')
            
    setTimeout(function () {
        currentNum++
        bindHtml()
        loadding.style.display = 'none'

        // 2.3.3 此时这一次请求已经完毕了, 可以打开开关, 允许我们发送第二次请求, 加载新的数据
        flag = true
        }, 2000)
            
    }

六、渲染表格

    <script>
    var data = JSON.parse(window.localStorage.getItem('data')) || [{
        status: true,
        id: 1,
        name: '张三',
        age: 25,
        city: '北京',
    },
    {
        status: false,
        id: 2,
        name: '李四',
        age: 30,
        city: '上海',
    },
    {
        status: true,
        id: 3,
        name: '王五',
        age: 22,
        city: '杭州',
    }]

    // 页面打开时调用渲染函数
    bindHtml()

    // 创建一个渲染函数
    function bindHtml() {
    // 在页面渲染前, 先清空之前的页面
    document.querySelector('table').innerHTML = ""
    // 在页面渲染前, 先计算出有多少个数据被选中, 用于决定是否选中全选
    // 存储当前数据中 有多少个数据被选中了
    var checkedNum = 0  
    for (var q = 0; q < data.length; q++) {
        // 如果当前对象的status属性为 true, 那么代表当前对象被选中了, 所以计数器 + 1
        data[q].status && checkedNum++     
    }


    // =========创建表头内容开始=========
    var headerArr = ['选择', '编号', '姓名', '年龄', '城市']
    // 当前tr只需要创建一个, 内部放一些 th 也就是 表头
    var headerTr = document.createElement('tr')      
    // 根据数据批量创建表头
    for (var j = 0; j < headerArr.length; j++) {      
        // 创建一个表头
        var headerTh = document.createElement('th')
        // 如果 j === 0, 代表循环第一次执行, 那么这个单元格的内容, 应该是一个多选框
        if (j === 0) {                   
            // 创建一个 input 标签, 注意: 默认为 输入框
            var inp = document.createElement('input')
            // 如果没有这一行那么创建的默认是一个 单行文本输入框
            inp.type = 'checkbox'
            // 因为一会需要添加事件, 所以这里提前给这个选择框添加一个类名标识
            inp.className = 'check_all'
            // 根据 现有选中的数据数量和总数据数量对比, 如果相同代表所有数据被选中, 那么选中全选按钮
            inp.checked = checkedNum === data.length
            // 将 多选框 添加到 th 中
            headerTh.appendChild(inp)                   
        } else {
            // 给表头单元格赋值一个文本
            headerTh.innerHTML = headerArr[j]           
        }
        // 将 th 单元格 放到 tr 单元行 内
        headerTr.appendChild(headerTh)                  
    }
    document.querySelector('table').appendChild(headerTr)
    // =========创建表头内容结束===========


    // =========创建表格内容开始===========
    for (var i = 0; i < data.length; i++) {
        var myTr = document.createElement('tr')
        var dataKeys = Object.keys(data[i])
        for (var k = 0; k < dataKeys.length; k++) {
            var myTd = document.createElement('td')
            // 如果 展示的内容是一个 布尔值, 那么应该展示一个多选框
            if (data[i][dataKeys[k]] === true || data[i][dataKeys[k]] === false) {
                // 创建一个 input 标签, 注意: 默认为单行输入框
                var inpItems = document.createElement('input')
                // 更改类型为 多选框
                inpItems.type = 'checkbox'
                // 给多选框添加类名, 因为一会需要添加事件
                inpItems.className = 'check_item'
                // 给多选框 添加一个默认的选中状态, 需要根据数据提供的来展示
                inpItems.checked = data[i][dataKeys[k]]
                // 用于给 标签 添加一个 标识, 能够知道将来点的是哪个
                inpItems.dataset.id = i + 1
                // 将 多选框 添加到 td 单元格中
                myTd.appendChild(inpItems)
            } else {
                // 如果 展示的内容不是 布尔值, 那么正常展示文本
                myTd.innerText = data[i][dataKeys[k]]           
            }
            myTr.appendChild(myTd)
        }

         document.querySelector('table').appendChild(myTr) 
    }
    // ========创建表格内容结束===============

    // ========持久化数据==================
    window.localStorage.setItem('data', JSON.stringify(data))
}



    // =========全选功能 (事件委托) 开始============
    var myTable = document.querySelector('table')
    myTable.onclick = function (e) {
        if (e.target.className === 'check_all') {
            // 1. 修改数据
            for (var i = 0; i < data.length; i++) {
                data[i].status = e.target.checked
            }

            // 2. 重新渲染页面
            bindHtml()
        }
        if (e.target.className === 'check_item') {
            // 1. 修改数据
            for (var k = 0; k < data.length; k++) {
                if (e.target.dataset.id - 0 === data[k].id) {
                    data[k].status = e.target.checked
            }
        }

        // 2. 重新渲染页面
        bindHtml()
    }
}
    // ==========全选功能 (事件委托) 结束==========

    // ==========排序功能开始=============
    var sortBtn = document.querySelector('#sort_btn')
    var num = 0
    sortBtn.onclick = function () {
        // 每次点击的时候 修改计数器的数字
        num++
        if (num === 1) {
            // 1. 第一次点击 按照 年龄的从小到大
            data.sort(function (a, b) { return a.age - b.age })
        } else if (num === 2) {
            // 2. 第二次点击 按照 年龄的从大到小
            data.sort(function (a, b) { return b.age - a.age })
        } else {
            // 3. 第三次点击 恢复默认排序(按照ID从小到大)
            data.sort(function (a, b) { return a.id - b.id })
            // 清零计数器, 下次可以重新执行逻辑
            num = 0 
        }
        // 上述的 if 语句内 处理完数据后, 重新渲染页面
        bindHtml()
    }
    // ==========排序功能结束===========

    // ==========新增功能开始===========
    // 1. 点击 "新增按钮" 打开 遮罩层 和 信息框
    var addBtn = document.querySelector('#add_btn')
    var overlay = document.querySelector('.overlay')
    var addUserBox = document.querySelector('.add_user_box')
    addBtn.onclick = function () {
        overlay.classList.remove('close')
        addUserBox.classList.remove('close')
    }
    // 2. 点击 "新增用户按钮" 收集用户输入的信息, 然后创建一个对象, 追加到原数组的末尾, 最后重新渲染页面
    var addUserBtn = document.querySelector('.add_user_btn')
    addUserBtn.onclick = function () {
        // 2.1 获取到用户输入的信息
        var nameEl = document.querySelector('.username')
        var ageEl = document.querySelector('.userage')
        var cityEl = document.querySelector('.usercity')

        var nameValue = nameEl.value
        var ageValue = ageEl.value
        var cityValue = cityEl.value

        // 2.2 安全校验 (非空校验, 规则校验/正则校验)
        if (nameValue === '' || ageValue === '' || cityValue === '') return alert('您的输入框有一个为空, 请补全输入框')

        // 2.3 如果代码能够正常执行到这里, 说明输入框的的内容不是空的, 所以可以组装一个对象, 然后追加到原数组的末尾
        var obj = {
            status: false,
            id: data.length + 1,
            name: nameValue,
            age: ageValue - 0,
            city: cityValue,
        }
        data.push(obj)

        // 2.4 调用渲染函数
        bindHtml()

        // 2.5 清空弹框数据
        nameEl.value = ''
        ageEl.value = ''
        cityEl.value = ''

        // 2.6 关闭弹框
        closeBoxFn()
    }
    // 3. 点击 "取消按钮" 关闭 遮罩层和信息框
    var closeBoxBtn = document.querySelector('.close_box_btn')
    closeBoxBtn.onclick = closeBoxFn
    function closeBoxFn() {
        overlay.classList.add('close')
        addUserBox.classList.add('close')
    }
    // ==========新增功能结束===========


    // =========获取平均年龄开始==========
    var getAge = document.querySelector('#get_age')
    getAge.onclick = function () {
    // 1. 获取到年龄的总和
        var sum = 0
        for (var i = 0; i < data.length; i++) {
            sum += data[i].age
        }

        // 2. 总和 / 总数量 === 年龄的平均值
        sum = sum / data.length

        // 3. 提示给用户年龄的平均值
        alert(sum.toFixed(1))
    }
    // =========获取平均年龄结束========
    </script>