五:以理论结合实践方式梳理前端 HTML ——— HTML 新增特性

HTML 新增标签

在学习画布标签之前,需要对 CSS 样式,和 JavaScript 脚本有一定的基本了解,否则无法读懂 Canvas 画布 API

元素拖放

在 HTML5 中,任何元素都能够被拖放

要使得一个元素是可拖动的,必须将它的 draggable 属性设置为 true

<div draggable="true">我是可以被拖动的</div>

在拖动元素的过程中,会触发相关的事件,与源对象(被拖动元素)相关的事件如下:

  • ondragstart:开始拖动元素时触发
  • ondrag:元素正在拖动时触发
  • ondragend:完成元素拖动后触发
<!DOCTYPE html>
<html>
<head>
    <title>Drag Demo</title>
    <head>
        <style>
            #source-object {
                width: 50px;
                height: 50px;
                border: 1px solid red;
                background-color: red;
            }
        </style>
        <script>
            function dragstart(event) { console.log('dragstart') }
            function drag(event) { console.log('drag') }
            function dragend(event) { console.log('dragend') }
        </script>
    </head>
</head>
<body>
    <div id="source-object"
         draggable="true"
         ondragstart="dragstart(event)"
         ondrag="drag(event)"
         ondragend="dragend(event)" >
    </div>
</body>
</html>

另外,我们还能监听在元素释放时触发的事件,与目标对象(其实就是释放区域)相关的事件如下:

  • ondragenter:当被拖动元素进入释放区域时触发
  • ondragover:当被拖动元素在释放区域移动时触发
  • ondragleave:当被拖动元素离开释放区域时触发
  • ondrop:当被拖动元素在释放区域释放时触发
<!DOCTYPE html>
<html>
<head>
    <title>Drop Demo</title>
    <head>
        <style>
            #source-object {
                width: 50px;
                height: 50px;
                border: 1px solid red;
                background-color: red;
            }
            #target-object {
                width: 100px;
                height: 100px;
                border: 1px solid blue;
                background-color: blue;
            }
        </style>
        <script>
            function dragenter(event) { console.log('dragenter') }
            function dragover(event) {
                // dragover 事件的默认行为是阻止数据或元素放置到其他元素中
                // 如果想要避免这种情况,就必须阻止 dragover 的默认行为
                // 只有这样,才能触发 drop 事件
                event.preventDefault()
                console.log('dragover')
            }
            function dragleave(event) { console.log('dragleave') }
            function drop(event) {
                // 对于图片等元素,drop 事件的默认行为是以链接的形式打开
                // 如果想要避免这种情况,就必须阻止 drop 的默认行为
                event.preventDefault()
                console.log('drop')
            }
        </script>
    </head>
</head>
<body>
    <div>请将红色方框移入蓝色方框</div>
    <br />
    <div id="source-object" draggable="true"></div>
    <br />
    <div id="target-object"
         ondragenter="dragenter(event)"
         ondragover="dragover(event)"
         ondragleave="dragleave(event)"
         ondrop="drop(event)" >
    </div>
</body>
</html>

拖放事件还有一个新的属性 dataTransfer,用于在源对象和目标对象之间传递数据

dataTransfer 它是一个对象,常用的方法有两个:

  • setData(key, value):在源对象设置数据
  • getData(key):在目标对象获取数据
<!DOCTYPE html>
<html>
<head>
    <title>Drag And Drop Demo</title>
    <head>
        <style>
            #source-object {
                width: 50px;
                height: 50px;
                border: 1px solid red;
                background-color: red;
            }
            #target-object {
                width: 100px;
                height: 100px;
                border: 1px solid blue;
                background-color: blue;
            }
        </style>
        <script>
            // 源对象事件
            function dragstart(event) {
                event.dataTransfer.setData('elemId', event.target.id)
            }
            // 目标对象事件
            function dragover(event) {
                event.preventDefault()
            }
            function drop(event) {
                event.preventDefault()
                var elemId = event.dataTransfer.getData('elemId')
                var elem = document.getElementById(elemId)
                event.target.appendChild(elem)
            }
        </script>
    </head>
</head>
<body>
    <div>请将红色方框移入蓝色方框</div>
    <br />
    <div id="source-object"
         draggable="true"
         ondragstart="dragstart(event)" >
    </div>
    <br />
    <div id="target-object"
         ondragover="dragover(event)"
         ondrop="drop(event)">
    </div>
</body>
</html>

最后来一个拖放在实际应用中的例子,实现文件拖拽上传功能

<!DOCTYPE html>
<html>
<head>
    <title>Drag And Drop Demo</title>
    <head>
        <style>
            #file-input {
                width: 300px;
                height: 100px;
                border-radius: 50px;
                border: 1px dashed black;
                display: flex;
                flex-direction: row;
                justify-content: center;
                align-items: center;
            }
        </style>
        <script>
            window.onload = function() {
                var fileInput = document.getElementById('file-input')
                fileInput.ondragover = function(event) {
                    event.preventDefault()
                }
                fileInput.ondrop = function(event) {
                    event.preventDefault()
                    // 每次只获取第一个文件对象
                    var file = event.dataTransfer.files[0]
                    // 检查
                    let isRightType = checkFileType(file)
                    let isRightSize = checkFileSize(file)
                    // 提示
                    if (isRightType && isRightSize) {
                        alert('上传文件成功')
                    } else if (!isRightType) {
                        alert('只支持 DOCX、TXT、MD 文件格式')
                    } else if (!isRightSize) {
                        alert('文件不能大于 2 M')
                    }
                }
                function checkFileType(file) {
                    let allowedTypes = ['DOCX', 'TXT', 'MD']
                    let fileInfo = file.name.split('.')
                    let fileType = fileInfo[fileInfo.length - 1].toUpperCase()
                    return allowedTypes.includes(fileType)
                }
                function checkFileSize(file) {
                    let maxSize = 2 * 1024 * 1024
                    let fileSize = file.size
                    return fileSize <= maxSize
                }
            }
        </script>
    </head>
</head>
<body>
    <div id="file-input">请将文件拖动到此处</div>
</body>
</html>

本地缓存

Web Storage 允许在浏览器保存用户数据,具体分为两种,一种是 localStorage,一种是 sessionStorage

对于客户端的存储方式,类似的还有早期乃至现在还广泛使用的 cookie,它们之间的区别如下:

cookielocalStoragesessionStorage
与服务端的通信在每次请求中都会携带不与服务端通信不与服务端通信
数据的生命周期设置的失效时间前有效永久有效,除非主动删除在浏览器关闭前有效
数据的作用范围设置的域名及其子域名在所有同源窗口之间共享不能在不同窗口之间共享
储存的数据大小一般不超过 4KB一般在 5MB 左右一般在 5MB 左右

Web Storage API 继承 window 对象,并提供两个新属性,window.localStoragewindow.sessionStorage

它们都是一个对象,常见的属性和方法如下:

  • length:保存的数据条数
  • setItem(key, value):保存数据
  • getItem(key):获取指定数据
  • removeItem(key):删除指定数据
  • clear():删除所有数据
  • key(index):根据索引获取键名
if (window.localStorage) {
    var storage = window.localStorage;
    storage.setItem('username', 'admin');
    storage.setItem('password', '12345');
    let username = storage.getItem('username');
    let password = storage.getItem('password');
    console.log(username); // admin
    console.log(password); // 12345
    console.log(storage.length); // 2
    storage.removeItem('password');
    console.log(storage.length); // 1
    storage.clear();
}

最后顺便来补充一下 cookie 和 session 的相关知识

我们知道,HTTP 协议是无状态的,也就是说每次发出的请求都是独立的,这个时候就会造成很多的不便

比如,用户在一个请求中登陆之后,对于他发出的另外一个请求,服务器还是无法识别用户的身份

而 cookie 和 session 的出现都是为了记录用户的信息,从而在多次请求中识别用户的身份

cookie 的运作机制如下:

  • 服务器在响应中通过 set-cookie 头部要求浏览器设置 cookie
  • 浏览器在收到响应后,如果该浏览器支持使用 cookie,那么就会将 cookie 保存到文件
  • 浏览器在以后每次发出请求时,都会在请求中通过 cookie 头部带上 cookie 信息
  • 服务器在收到请求后,根据 cookie 信息识别用户身份

session 的运作机制如下:

  • 服务器创建一个唯一的标识,并将这个标识和对应的 session 信息保存下来,然后将这个标识发给浏览器

    保存 session 信息的方式常见的有三种,一种是保存在内存中,一种是保存在文件中,一种是保存在数据库中

  • 浏览器在收到这个标识后,在以后每次发出请求时,都会带上这个标识

    带上标识的方式常见的有两种,一种是附加在 cookie 中,一种是附加在 URL 参数中

  • 服务器在收到请求后,根据标识找到对应的 session 信息识别用户身份

cookie 数据存放在浏览器上,session 数据存放在服务器上,所以相对而言使用 session 更为安全

但是,过多的 session 数据保存在服务器上,会影响服务器的性能,所以需要权衡选择

线程环境

Web Worker 为 JavaScript 创建多线程环境

主线程可以创建 Worker 线程,并将一些高延迟的任务分配给 Worker 线程在后台运行

而主线程只会负责渲染和交互,从而保证页面的流畅性

Web Worker 有两种类型,分别是 Dedicated Web Worker 和 Shared Web Worker

  • Dedicated Web Worker:只有一个页面可以使用这个 Web Worker
  • Shared Web Worker:多个同源页面可以共享一个 Web Worker,为跨页面通信提供一种解决方案

下面介绍 Dedicated Web Worker

主线程通过构造函数 Worker(),创建一个 Worker 线程,并在参数中指定需要执行的脚本文件

var worker = new Worker('worker.js')

构造函数返回一个 Worker 对象,便于主线程与 Worker 线程通信,Worker 对象常用的属性和方法如下:

  • onerror:用于指定 error 事件的监听函数,当 Worker 发生错误时触发
  • onmessage:用于指定 message 事件的监听函数,当接收到传递的数据时触发
  • onmessageerror:用于指定 messageerror 事件的监听函数,当收到的数据无法进行反序列化时触发
  • postMessage():传递数据给 Worker 线程
  • terminate():终止 Worker 线程
<!DOCTYPE html>
<html>
<head>
    <script>
        var worker;

        function submit() {
            if (typeof(Worker) === 'undefined') {
                return
            }
            if (typeof(worker) === "undefined") {
                worker = new Worker('worker.js')
            }
            let input = document.getElementById('data').value
            worker.postMessage(input)
            worker.onmessage = function(event) {
                let result = event.data
                document.getElementById('result').innerText = result
                worker.terminate()
                worker = undefined
            }
            worker.onerror = function(event) {
                console.log('出错的信息', event.message)
                console.log('出错的文件', event.filename)
                console.log('出错的行数', event.lineno)
                console.log('出错的列数', event.colno)
                worker.terminate()
                worker = undefined
            }
        }
    </script>
</head>
<body>
    <input type="text" id="data" name="data" placeholder="Please Enter Something">
    <button onclick="submit()">Reverse String</button>
    <span id="result"></span>
</body>
</html>

Worker 线程有一个自己的全局对象,selfthis 都提供对该对象的引用,这个对象常用的属性和方法如下:

  • onmessage:用于指定 message 事件的监听函数,当接收到传递的数据时触发
  • onmessageerror:用于指定 messageerror 事件的监听函数,当收到的数据无法进行反序列化时触发
  • importScripts():用于加载其它脚本
  • postMessage():传递数据给主线程
  • close():关闭 Worker 线程
// worker.js
this.onmessage = function(event) {
    let data = event.data;
    let result = reverse(data);
    this.postMessage(result);
    this.close();
}

function reverse(content) {
    return content.split('').reverse().join('');
}

使用 Web Worker 需要注意以下的点:

  • 主线程分配给 Worker 线程的脚本文件,不能从本地读取,它必须来自网络

  • 主线程分配给 Worker 线程的脚本文件,必须与主线程的脚本文件同源,准确来说应该是与页面文档同源

  • Worker 线程不能访问 window、document、parent 对象,但是可以使用 navigator、location 对象

  • Worker 线程不能使用 alert、confirm,但是可以使用 setTimeout、setInterval、XMLHttpRequest

  • 主线程和 Worker 线程不能直接通信,必须通过消息传递,消息传递有两种方式,一种是拷贝,一种是转让

    拷贝:拷贝原始数据,产生一份副本,将副本传递给目标线程,这种方法在传递较大的数据时比较消耗性能

    转让:将原始数据的所有权转让给目标线程,原来线程不再具有这个数据

    在默认的实现中,除 ArrayBuffer 外都是拷贝传递

通讯协议

WebSocket 是一个建立在 TCP 之上进行全双工通讯的应用层协议

我们知道,HTTP 协议采用的是请求/响应模式,服务端是不能主动向客户端推送数据的

若要实现推送功能,一般都是采用 Ajax 轮询,但这样频繁的请求会给服务端带来极大的压力

WebSocket 协议就是为了解决这种类似的场景而出现的,它允许服务端可以主动向客户端推送数据

浏览器和服务器通过一次握手,就能在两者之间创建持续性的连接,进行双向数据传输,直至任意一方将其关闭

目前,基于 SHA 加密方式的握手协议是使用最为广泛的,具体的过程如下:

  • 浏览器向服务器发起一个 HTTP 请求,但是这个请求与普通的请求不同,它会附加一些头部信息
  • 服务器解析头部信息,返回一个响应,这样就能建立起 WebSocket 连接

一个典型的客户端请求如下:

GET ws://localhost:5000 HTTP/1.1
Host: localhost
Origin: http://localhost:5000
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: 随机字符串
Sec-WebSocket-Version: 13
  • Connection:必须设置为 Upgrade,表示客户端希望升级协议
  • Upgrade:必须设置为 websocket,表示希望将协议升级为 Websocket
  • Sec-WebSocket-Key:随机字符串,与响应中 Sec-WebSocket-Accept 的值做对比验证

一个典型的服务端响应如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 根据 Sec-WebSocket-Key 计算得到
Sec-WebSocket-Location: ws://localhost:5000
  • Connection:如果值为 Upgrade,表示服务端同意升级协议

  • Upgrade:如果值为 websocket,表示同意将协议升级为 Websocket

  • Sec-WebSocket-Accept:经过服务器确认并加密的 Sec-WebSocket-Key

    Sec-WebSocket-Key 的值拼上一段特殊的字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11

    然后使用 SHA1 对这个字符串做哈希,再进行 Base64 编码后,才能得到 Sec-WebSocket-Accept 的值

在理解了原理之后,我们下面再来看一下具体要怎么使用 WebSocket

在客户端浏览器中,可以通过构造函数 WebSocket() 创建一个对象,该对象能提供使用 WebSocket 的相关 API

其中,参数 url 用于指定连接地址,参数 protocol 用于指定可接受的子协议

WebSocket(url, [protocol])

构造函数返回一个 WebSocket 对象,该对象常用的属性和方法如下:

  • readyState:只读属性,表示连接状态

    0 表示连接尚未建立,1 表示连接已经建立,可以进行通信

    2 表示连接正在关闭,3 表示连接已经关闭,或者不能打开

  • bufferedAmount:只读属性,在缓存中等待传输的字节数

  • onopen:指定 open 事件的监听函数,在连接建立时触发

  • onmessage:指定 message 事件的监听函数,在收到数据时触发

  • onerror:指定 error 事件的监听函数,在发生错误时触发

  • onclose:指定 close 事件的监听函数,在连接关闭时触发

  • send():发送数据

  • close():关闭连接

var ws = new WebSocket('ws://localhost:5000');

ws.onopen = function(event) { 
    console.log('open');
};

ws.onmessage = function(event) {
    console.log('message', event.data);
    ws.send('Goodbye');
    ws.close();
};

ws.onclose = function(event) {
	console.log('close');
}; 

ws.onerror = function(event) {
	console.log('error');
};

而对于服务器而言,WebSocket 只是一种协议而已,不同的语言和不同的框架会有不同的实现

下面我们以 Node.js 为例,写一个简单的 Demo

var express = require('express');
var expressWs = require('express-ws');

var app = express();

expressWs(app);

app.ws('/', function (ws, req){
    // ws:  WebSocket 实例
    // req:Request 实例
    ws.send('Hello');
    ws.on('message', function (msg) {
        console.log(msg);
    });
});

var server = app.listen(5000, '127.0.0.1', function() {
    console.log('server running at http://127.0.0.1:5000');
});
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值