地理定位, 拖放 API, Web Storage

259 阅读6分钟

1. 地理定位

地理定位: Geolocation, 使用 JS 获取浏览器所在的地理坐标信息, 实现 LBS( Location Based Service, 基于定位服务 ) 应用, Ex: 送餐, 打车, 导航等

  • 具体地理坐标信息包括:

    • 经度: longitude
    • 纬度: latitude
    • 海拔: altitude
    • 速度: speed
1.1. 如何获取浏览器所在的定位数据
1.1.1. 手机
  • 可以使用内置的 GPS 芯片获取卫星数据, 精度为米( m )
  • 也可以通过手机通信基站, 反向推出用户位置, 精度为千米( km )
1.1.2. PC
  • 可以通过用户的 IP 地址反向推导出所在位置( 必需有很大的 IP 地址库 ), 精度在千米( km )
1.1.3. H5 中提供了新的对象, 专用于获取用户所在的位置数据
  • 语法:

    window.navigator.geolocation {
      getCurrentPosition: fn, // 用于获取当前的位置信息
      watchPosition: fn, // 监视用户位置的改变
      clearWatch: fn // 清除定位监视
    }
    ​
    // 获取用户定位信息
    window.navigator.geolocation.getCurrentPosition(
      function (pos) { ...... }, // 定位成功后的回调
      function (err) { ...... } // 定位失败后的回调
    )
    
  • 提示: PC 中的 ChromeFirefox 都需要到 www.googleapis.com 进行 IP 地址方向解析, 由于国内无法访问该网站, 所以目前在 PC 中此方法使用价值不大

2. 拖放 API

Drag & Drop : 拖放, H5 提供的拖放 API, 可以实现类似于桌面软件中的拖放效果, 无需像以前那样使用鼠标事件来代替

2.1. 拖动的源对象 source ( 可能发生移动的 ) 可以触发的事件: 3 个
  1. dragstart: 拖动开始
  2. drag: 拖动中
  3. dragend: 拖动结束
  • 整个拖动过程的组成: dragstart * 1 + drag * n + dragend * 1

  • Ex: 实现一个可以随鼠标移动而移动的小飞机

    <style>
      body{
        position: relative; margin: 0;
      }
      body:before{
        content: ''; display: table;
      }
      .dragTest{
        position: absolute;
      }
    </style>
    <body>
      <img src="images/dragTest.png" class="dragTest" id="dragTest" />
      
      <script type="text/javascript">
        let dragTest = document.getElementById('dragTest')
        // 拖动刚开始事件相对于飞机的偏移量
        let startX = 0
        let startY = 0
        // 拖动开始
        dragTest.addEventListener('dragstart', function (e) {
          // 拖动事件相对于事件源的偏移量
          startX = e.offsetX
          startY = e.offsetY
        })
        // 拖动中
        dragTest.addEventListener('drag', function (e) {
          let x = e.pageX
          let y = e.pageY
          // 屏蔽鼠标在即将松手时触发的(0, 0) 坐标
          if (0 == x && 0 == y) {
            return ;
          }
          // 修改小飞机的位置
          dragTest.style.left = x - startX + 'px'
          dragTest.style.top = y - startY + 'px'
        })
        // 拖动完毕
        dragTest.addEventListener('dragend', function (e) {
          console.log(e.pageX)
          console.log(e.pageY)
          console.log('拖动完毕')
        })
      </script>
    </body>
    
  • 提示: drag 事件中获取拖动事件相对于页面的坐标( e.pageX, e.pageY ), 及时修改事件源的位置( left, top ), 事件源绝对定位, 父容器相对定位

2.2. 拖放的目标对象 target ( 不会发生移动 ) 可以触发的事件: 4 个
  1. dragenter: 拖动着进入
  2. dragover: 拖动着悬停
  3. dragleave: 拖动着离开
  4. drop: 释放
  • 整个拖动过程的组成1: dragenter * 1 + dragover * n + dragleave * 1
  • 整个拖动过程的组成2: dragenter * 1 + dragover * n + drop * 1

注意: dragover 事件的默认行为是必须触发 dragleave, 若不阻止默认行为, drop 无法触发

  • Ex: 初始化垃圾桶半透明, 若有飞机拖动着进入, 垃圾桶变为不透明, 飞机离开 / 释放, 垃圾桶变为不透明, 若飞机在垃圾桶释放, 则删除该飞机

    <style>
      body{
        text-align: center;
      }
      div.container{
        min-height: 200px; padding: 10px;
        border: 1px solid #aaa;
      }
      .trash{
        opacity: 0.3;
      }
    </style>
    <body>
      <!-- 垃圾桶 -->
      <img src="images/trash.png" id="trash" class="trash" />
      <!-- 飞机选择 -->
      <div class="container" id="container">
        <img src="images/planRed.png" id="planRed" />
        <img src="images/planBlue.png" id="planBlue"/>
        <img src="images/planYellow.png" id="planYellow" />
      </div>
      
      <script type="text/javascript">
        let trash = document.getElementById('trash')
        let planContainer = document.getElementById('container')
        // 全局变量: 记录拖动的是哪个飞机
        let draggedPlanId = null
        // 给飞机添加事件
        let planList = document.querySelectorAll('div.container > img')
        for(let i = 0; i < planList.length; i++) {
          let planItem = planList[i]
          planItem.addEventListener('dragstart', function () {
            draggedPlanId = this.id
          })
          planItem.addEventListener('drag', function () {})
          planItem.addEventListener('dragend', function () {})
        }
        // 给拖动目标(垃圾桶) 添加事件
        trash.addEventListener('dragenter', function () {
          this.style.opacity = 1
        })
        trash.addEventListener('dragover', function (e) {
          e.preventDefault()
        })
        trash.addEventListener('dragleave', function () {
          this.style.opacity = 0.3
        })
        trash.addEventListener('drop', function () {
          // 修改垃圾桶的透明度
          this.style.opacity = 0.3
          // 根据 ID 删除飞机
          let planId = document.getElementById(draggedPlanId)      
          planContainer.removeChild(planId)
        })
      </script>
    </body>
    
2.3. 如何在拖动的源对象 source 和 目标对象 target 间传递数据
  • 如何在 7 个事件之间传递参数

    • 源对象事件: 3 个
    • 目标对象事件: 4 个
2.3.1. 方法一: 使用全局变量 -> 全局对象污染
2.3.2. 方法二: 使用拖动对象的 dataTransfer 属性

说明: dataTransfer, 用作数据传递 / 转移, 看做"拖拉机"

  • 在拖动源对象事件中使用 e.dataTransfer 属性保存数据

    • e.dataTransfer.setData('key', 'value')
  • 在拖动目标对象事件中使用 e.dataTransfer 属性读取数据

    • e.dataTransfer.getData('key')
  • Ex: 实现英雄选择

    <style>
      *{
        box-sizing: border-box;
      }
      body{
        text-align: center;
      }
      div.chosen, div.planList{
        padding: 5px;
        border: 1px solid #aaa;
      }
      div.chosen{
        width: 200px; height: 115px; margin: 0 auto;
      }
      div.chosen > img{
        width: 100%;
      }
    </style>
    <body>
      <div class="chosen" id="chosen">
        <img src="images/planInit.png" id="planInit" />
      </div>
      <div class="planList" id="planList">
        <img src="images/planRed.png" id="planRed" />
        <img src="images/planBlue.png" id="planBlue" />
        <img src="images/planYellow.png" id="planYellow" />
      </div>
      
      <script type="text/javascript">
        /* 拖动飞机到 chosen 选择区 planInit 飞机隐藏 拖动的飞机进入选择区
        */
        let planList = document.querySelectorAll('div.planList > img')
        let planListId = document.getElementById('planList')
        for(let i = 0; i < planList.length; i++) {
          let planItem = planList[i]
          // 飞机项添加拖动事件
          planItem.addEventListener('dragstart', function (e) {
            // 在源对象事件中保存数据
            e.dataTransfer.setData('planId', this.id)
          })
          planItem.addEventListener('drag', function (e) {
            console.log('拖动中')
          })
          planItem.addEventListener('dragend', function (e) {
            console.log('拖动结束')
          })
        }
        // 拖放目标事件
        let chosen = document.getElementById('chosen')
        chosen.addEventListener('dragenter', function (e) {
          console.log('拖动着进入')
        })
        chosen.addEventListener('dragover', function (e) {
          e.preventDefault()
          console.log('拖动着悬停')
        })
        chosen.addEventListener('dragleave', function (e) {
          console.log('拖动着离开')
        })
        chosen.addEventListener('drop', function (e) {
          let planInit = document.getElementById('planInit')
          // 隐藏初始化飞机
          planInit.style.display = 'none'
          // 若初始化飞机此时有兄弟元素, 将该兄弟元素放回原处
          if (planInit.nextElementSibling) {
            planListId.appendChild(planInit.nextElementSibling)                                      
          }
          // 将当前拖动放到选择区
          let planId = e.dataTransfer.getData('planId')
          let plan = document.getElementById(planId)
          chosen.appendChild(plan)
        })
      </script>
    </body>
    
2.4. 如何在服务器下载的网页中显示客户端图片
  • 一般情况下, 网页只能显示服务器上的图片

  • HTML5 中, 可以实现用户拖拽一张本地图片显示在服务器端下载的网页中

    container.addEventListener('drop', function (e) {
      let filesOne = e.dataTransfer.files[0]
      let reader = new FileReader()
      reader.readAsDataURL(filesOne)
      reader.addEventListener('load', function () {
        // 读取完成, 数据在 reader.result 中
      })
    })
    
2.4.1. H5 中提供的用于文件输入 / 输出( I / O ) 的对象
  • File: 代表一个文件 / 目录对象
  • FileList: 代表一个文件列表( 类数组对象 )
  • FileReader: 用于从文件中读取内容
  • FileWriter: 用于向文件中写入内容

3. Web Storage

3.1. Web 项目中项目数据的存储位置
  1. 服务器端存储: 业务数据, Ex: 商品, 用户, 订单, 帖子, 评论, 储户等

  2. 客户端存储: 客户端专有数据, Ex: 历史记录, 内容定制, 样式偏好等

    • Cookie 技术: 兼容性好, 大小不能超过 4 KB, 操作复杂
    • Flash 存储: 不推荐使用
    • IndexedDB 技术: 可以直接存取对象, 不是 H5 标准
    • WebStorage 技术: 大小不能超过 8 MB, 操作简单, H5 特性
3.2. H5WebStorage 技术具体分为两个对象:
3.2.1. window.sessionStorage
  • Session: 会话, 自从浏览器第一次打开某网站的某个页面开始, 不断的请求 - 响应, 直到最后关闭浏览器 -> 整个过程称为浏览器和 Web 服务器之间的一次会话 -> 一次会话可能出现任意多个页面的请求 - 响应
  • sessionStorage: 会话级数据存储, 本质就是一个存储在浏览器内存中的类数组对象; 专用于存储当前会话中需要保存的数据, 会话结束则数据删除
  • 保存数据: sessionStorage['key'] = value

  • 读取数据: let val = sessionStorage['key']

  • 查看保存的数据个数: let count = sessionStorage.length

  • 保存数据: sessionStorage.setItem('key', value)

  • 读取数据: let val = sessionStorage.getItem('key')

  • 删除数据: sessionStorage.removeItem('key')

  • 清除所有数据: sessionStorage.clear()

  • 获取第 ikey: let key = sessionStorage.key(i)

  • Ex: 使用 sessionStorage 保存登录用户的 uNameuId, 供后续的页面使用

    // index.html
    <style>
      div.welcome{
        text-align: right;
      }
    </style>
    <body>
      <div class="welcome" id="welcome">
        <a href="login.html">登录</a>
      </div>
      <h3>首页</h3>
      
      <script type="text/javascript">
        let welcome = document.getElementById('welcome')
        // 读取 sessionStorage 存储的数据
        let uName = window.sessionStorage.getItem('uName')
        if (uName) {
          welcome.innerHTML = `
            欢迎回来: ${ uName }
            <a href="logout.html">退出登录</a>
          `
        }
      </script>
    </body>
    
    // login.html
    <body>
      <form action="">
        用户名: <input type="text" name="uName" id="uName" />
        密码: <input type="password" name="uPwd" id="uPwd" />
        <input type="button" value="提交登录消息" id="loginBtn" />
      </form>
      
      <script type="text/javascript">
        let loginBtn = document.getElementById('loginBtn')
        let uNameEl = document.getElementById('uName')
        let uPwdEl = document.getElementById('uPwd')        
        // 使用 sessionStorage 存储用户登录信息
        loginBtn.addEventListener('click', function () {
          let uName = uNameEl.value
          let upwd = uNameEl.value
          window.sessionStorage.setItem('uName', uName)
          window.sessionStorage['uPwd'] = uPwd
          // 跳转页面
          alert('登录成功! 3 s后将自动跳转到首页')
          window.setTimeout(function () {
            window.location.href = 'index.html'
          }, 3000)
        })    
      </script>
    </body>
    
    // logout.html
    <body>
      <h3>退出登录</h3>
      <h4>你已退出登录!</h4>
      <p>3 s后将自动跳转到首页</p>
      
      <script type="text/javascript">
        // 清除 sessionStorage 存储的数据
        // sessionStorage.removeItem('uName')
        window.sessionStorage.clear()
        // 跳转页面
        window.setTimeout(function () {
          window.location.href = 'index.html'
        }, 3000)
      </script>
    </body>
    
3.2.2. window.localstorage

localStorage: 跨会话级数据存储, 本质就是一个在浏览器硬盘 / 文件中的类数组对象 专用于永久存储当前网站在当前浏览器中的专用数据, 即使关闭浏览器 / 重启电脑数据也不会删除

  • 保存数据: localStorage['key'] = value

  • 读取数据: let val = localStorage['key']

  • 查看保存的数据个数: let count = localStorage.length

  • 保存数据: localStorage.setItem('key', value)

  • 读取数据: let val = localStorage.getItem('key')

  • 删除数据: localStorage.removeItem('key')

  • 清除所有数据: localStorage.clear()

  • 获取第 ikey: let key = localStorage.key(i)

  • window.onstorage 事件: 当 localStorage 中数据发生改变时自动触发.

    注意: 该事件只能用于监听 localStorage 中的数据变化, 而不能监听 sessionStorage 中的数据改变

  • Ex: 使用 localStorage 存储用户的样式偏好

    1. 创建 index.html, select 选择你喜欢的风格

      // index.html
      <style>
        .blue{
          color: #33a; background: #ccf;
        }
        .pink{
          color: #a0a; background: #faf;
        }
        .dark{
          color: #eee; background: #333;
        }
      </style>
      <body>
        <h3>首页</h3>
        <select name="selUserHobby" id="selUserHobby">
          <option vlaue="">请选择你喜欢的主题</option>
          <option value="blue">蓝色天空</option>
          <option value="pink">芭比公主</option>
          <option value="dark">暗黑主题</option>
        </select>
        <p>
          <span>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Assumenda dolor dolores impedit minima perspiciatis qui repellendus sint tempora veritatis vitae! Animi eligendi expedita facilis impedit iusto magnam nihil nobis pariatur?
          </span>
        </p>
        <a href="userCenter.html">跳转到用户中心</a>
        
        <script type="text/javascript">
        	let selUserHobby = document.getElementById('selUserHobby')
          selUserHobby.addEventListener('change', function () {
            let hobbyTheme = this.value
            if (0 == hobbyTheme) {
              return false
            }
            // 为 body 添加 class 类
            document.body.className = hobbyTheme
            // 使用 localStorage 存储用户喜好
            window.localStorage.setItem('userHobby', hobbyTheme)
          })
          // 读取 localStorage
          let userHobby = window.localStorage['userHobby']
          document.body.className = userHobby
        </script>
      </body>
      
    2. 创建 userCenter.html, 自动呈现首页中选择的样式风格

      // userCenter.html
      <style>
        .blue{
          color: #33a; background: #ccf;
        }
        .pink{
          color: #a0a; background: #faf;
        }
        .dark{
          color: #eee; background: #333;
        }
      </style>
      <body>
        <h3>用户中心</h3>
        <p>
          <span>
            Lorem ipsum dolor sit amet, consectetur adipisicing elit. Assumenda dolor dolores impedit minima perspiciatis qui repellendus sint tempora veritatis vitae! Animi eligendi expedita facilis impedit iusto magnam nihil nobis pariatur?
          </span>
        </p>
        <a href="index.html">返回首页</a>
        
        <script type="text/javascript">
          // 读取 localStorage
          let userHobby = window.localStorage.getItem('userHobby')
          document.body.className = userHobby
          // 监听 localStorage 中的数据变化
          window.addEventListener('storage', function () {
            // 读取存储的数据
            let userHobby = window.localStorage['userHobby']
            document.body.className = userHobby
          })
        </script>
      </body>
      

4. 网页游戏如何按照特定顺序绘制没有特定加载顺序的图片

// 全局的变量: 记录所有图片的总加载进度
let progress = 0
// 加载图片
let imgPan = new Image()
imgPan.src = 'img/pan.png'
imgPan.onload = function () {
  console.log('1 - 圆盘加载成功')
  progress += 75
  if (100 == progress) { // 查看总权重是否已满
    startDraw()    
  }
}
// 加载图片
let imgPin = new Image()
imgPin.src = 'img/pin.png'
imgPin.onload = function () {
  console.log('2 - 指针加载完成')
  progress += 25
  if (100 == progress) {
    startDraw()
  }
}