websocket以及聊天室的实现

HTTP keep-alive参考博文
socketio跨域 socketio跨域问题自己去了解

WebSocket

websocket可以实现客户端与服务端之间的数据实时通信。(长连接)

网络通信过程中的长连接与短连接

短连接: 客户端发起连接请求,请求建立连接,服务端接受请求,完成连接的创建,而后客户端通过该连接发送数据,服务端处理请求,返回响应,最后连接断开。这种通讯模式称为短连接。

特点:这种通讯模式将会及时的释放服务端资源,所以可以为更多的客户端提供服务。但是也有缺点:无状态。(每个请求都是独立的,无法保存客户端状态)

**长连接:**客户端发起连接请求,请求建立连接,服务端接受请求,完成连接的创建,而后客户端通过该连接发送数据,服务端处理请求,返回响应;服务端也可以主动的向客户端发送数据,客户端接收。这个过程中持续连接不断开。这种通讯模式称为长连接。

特点:支持客户端与服务端之间的实时通信。服务端可以随时随地向客户端发消息。缺点就是浪费服务端资源。

什么是websocket

websocket是一种在单TCP连接上进行的支持全双工通信(通信的过程中允许两个方向上同时完成数据传输)的一种协议。是一款长连接协议。

如何在网页中建立websocket连接?

服务端接收接收该连接?

客户端与服务端如何通过该websocket连接进行通信呢?

socket.io

https://socketio.bootcss.com/

socket.io是一个为浏览器与服务端之间提供实时的、双向的、基于事件通信的网络通信库。底层封装了websocket协议。提供了一些简单的API接口,方便的建立连接,收发消息,抹平了一些技术细节与浏览器兼容性问题。

建立连接的伪代码:

socket(套接字)就是一个javascript对象,对象的本质就是封装了属性与方法。socket中就封装了用于与对方通信时所需要的资源。

// 客户端 导入socket.io后
let socket = io('ws://localhost:3000/');

// 服务端 安装socket.io后
let socketio = require('socket.io');
socketio.on('connection', (socket)=>{
    连接建立成功后执行
});

建立websocket连接

服务端代码:

  1. 新建项目(socketserver文件夹)

安装express。安装socket.io

cd socketserver
npm init   // 一路回车  生成package.json
npm install --save express   // 安装express
npm install --save socket.io   // 安装socket.io
  1. 基于socket.io提供的API,接收客户端的连接请求,建立websocket连接。
// index.js   服务端核心js文件
const express = require('express');
const server = express();
// 调用server.get()  server.post() 注册路由 接收请求

// 配置websocket
// 获取express底层所使用的http模块
const http = require('http').createServer(server);
// websocket需要把通信相关路由注册到该底层http模块
const socketio = require('socket.io')(http);
socketio.on('connection', (socket)=>{
  console.log('有客户端进来了:'+socket.id)
});

// 设置静态资源托管目录 public
server.use(express.static('public'));

// 启动服务 不能再使用server.listen  而应该调用http.listen
// 因为socketio将通信相关路由注册到了http模块,而非express
// 所以http才可以接收到websocket请求,express则只用于处理http服务
http.listen(3000, function(){
  console.log('server is running...')
});

客户端代码:

为了避免跨域问题,设置静态资源托管目录public,把网页写在public中即可。

<script src="socket.io.js"></script>
<script>
btnConn.addEventListener('click', ()=>{
    let socket = io('ws://localhost:3000/');
    console.log(socket);
});
</script>

客户端与服务端之间进行通信

客户端发消息给服务端

客户端:

let socket = io('ws://localhost:3000/');
// 发消息    (emit 翻译为触发)
socket.emit('textmsg', '你瞅啥?!');  

服务端:

socketio.on('connection', (socket)=>{
    // 监听客户端发过来的消息
    socket.on('textmsg', (data)=>{
        console.log(data)
    });
});

服务端发消息给客户端

服务端代码:

socket.on('textmsg', (data)=>{
    console.log(data)
    // 回复
    socket.emit("textmsg", '瞅你咋地~~');
});

客户端代码:

let socket = io('ws://localhost:3000/');
socket.on('textmsg', (data)=>{
    alert('xxxx');
});

服务端向所有客户端广播消息

socketio.on('connection', (socket)=>{
    socket.emit();   // 针对当前客户端发消息
    socketio.emit('textmsg', '兄弟们!');   // 向所有客户端都发消息
});

例子:文件夹目录为
在这里插入图片描述

客户端:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Document</title>
</head>
<body>
    <h2>websockt示例</h2>
    <button id="btnConn">建立连接</button>
    <button id="btnSend">向服务器端发消息</button>
    <button id="btnSend3">向服务器端发消息:请服务器端准备群发</button>
    <script src="socket.io.js"></script>
    <script>
        let socket;
        // 建立连接
        btnConn.addEventListener("click", ()=>{
            // socket = io('ws//localhost:3000/');
        socket = io('ws://localhost:3000/');
            console.log(socket);
            // 因为接收信息是在建立完连接之后就监听,所以接收服务器的信息是在连接之后就开启接收
            socket.on("textmsg", (data)=>{
                window.alert(data);
            });
        });
        // 客户端主动发消息
        btnSend.addEventListener("click", ()=>{
            // textmsg 是代表在服务器端接收时要用该textmsg去接收,只跟此次发送和接收相关,
            // 即每次接收和发送的要匹配即可
            socket.emit("textmsg", "向服务器发送信息,单发");
        });
        btnSend3.addEventListener("click", ()=>{
            // textmsg 是代表在服务器端接收时要用该textmsg去接收,只跟此次发送和接收相关,
            // 即每次接收和发送的要匹配即可
            socket.emit("textmsg", "向服务器端发消息:请服务器端准备群发");
        });
    </script>
    
</body>
</html>

服务端:

//index.js 服务端核心js文件
const express = require('express');
const server = express();

// 调用server.get() serve.post()注册路由,接收请求
// const socketio = require("socket.io");
// 建立服务端和socketio连接
// 获取express底层所使用的http模块,因为要通过express底层的http去发送请求,建立连接是用http,所以要获得http
const http = require('http').createServer(server);
// 要将express底层的http和socketio建立联系
const socketio = require('socket.io')(http);
// 监听连接事件,连接就触发回调函数
socketio.on("connection", (socket)=>{
    console.log(socket.id);

    // 1.服务器端主动发送消息
    // 1.1针对当前客户端发送消息
    // socket.emit("textmsg", "连接建立成功");
    // 1.2向所有客户端发送消息,通过socktio发送

    // 2.服务器端接收消息并发送消息
    // 通过socketio连接的是长连接,所以接收信息要在长链接接听中接收和处理
    socket.on('textmsg', (data)=>{//接收textmsg类型的消息,即可以理解为监听textmsg的信息,有该信息则进行回调函数调用
        console.log(data);
        // 回复客户端
        if(/向服务器发送信息,单发/.test(data)){
            socket.emit("textmsg", "回复客户端信息");
        }else{
            socketio.emit("textmsg", "给客户端们都群发信息");
        }
    });
});
// 如果之后要写接口,那么是用serve.get来接收,因为serve上绑定了socketio,所以可以直接用
// 页面有自动重连的功能,如果没有连接成功

// 设置静态资源托管目录 public,即可以通过服务器地址直接访问该文件夹内的资源
server.use(express.static('public'));

// 启动服务 不能再使用server.listen,而应该调用http.listen
// 因为sokectio将通信相关的路由注册到了http模块,而非express
// 所以http才可以接收到websockt请求,express则用于处理http服务
http.listen(3000, function(){
    console.log("serve is running");
})

实现群聊天室

需求如下:

  1. 在聊天界面中建立websocket连接。

  2. 当客户端点击发送按钮时,使用已经建立好的连接向服务端发送消息。服务端接收该消息后,将这个消息内容向所有已经连接的客户端再发一遍即可。

  3. 每个客户端需要监听服务端发回来的消息,控制台打印。

  4. 消息上屏。把接收到的消息显示在聊天记录区。

  5. 优化业务细节。

  6. 实时更新聊天室的人数。

    1. 在服务端实时维护一个表达在线人数的变量。count
    2. 一旦有客户端连接,count++
    3. 一旦有客户端断开连接,count--
    4. 无论count是递增了还是递减了,只要count有变化,就需要给所有客户端发消息emit('count_updated', count)
    5. 客户端监听并接收该消息类型,更新页面中的在线人数即可。
  7. 实现登录业务。

    1. index.html点击登录时,需要获取昵称(文本框里输入)与头像(随机生成文件名)。带着这两个参数一起跳转到chart.html

      // window.location.href = "chart.html"
      location = "chart.html?name=xxx&avatar=xx.jpg"
      
    2. chart.html中获取nameavatar,更新用户信息。

    3. 发消息时,不能只发送内容,而是需要发送一个对象:

      {content:'xxxx', name:'xx', avatar:'xxx.jpg'}

    4. 服务端接收后将原封不动的把对象发给所有客户端。一旦客户端接收到返回回来的对象,需要解析对象的属性,上屏。

聊天室文件夹结构为:

在这里插入图片描述

群聊完成了,私聊该如何解决?

每当客户端与服务端建立了一个websocket连接,客户端将会得到一个socket对象,服务端也会得到一个socket对象。这些socket对象用于与对方进行数据通信。所以服务端保存了与每个客户端通信所需要的的socket对象。

所以如何实现私聊?

亮亮发消息时,不仅需要发送消息内容,还需要发送对方的ID(也可以是name,总之得传递对方的唯一标识符)。服务端接收到该消息后,需要通过ID(也可以是name)找到与对方建立起来的socket连接对象,直接调用socket.emit()方法发消息即可。
但是这里依然存在着不足,这里在用户退出之后,没有清除对应的用户socket,所以这里依然要去不断完善。

index.html

<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>微信聊天室</title>
        <link rel="stylesheet" href="styles/normalize.css">
        <link rel="stylesheet" href="styles/reset.css">
        <link rel="stylesheet" href="styles/chart.css">
    </head>
    <body>
        <div id="login-container">
            <div class="login-title">微信聊天室</div>
            <div class="login-user-icon">
                <img src="images/login.png" alt="">
            </div>
            <div>
                <input type="text" id="username" class="login-item login-username" placeholder="请输入聊天昵称">
            </div>
            <div>
                <input type="button" id="login" class="login-item login-button" value="登录">
            </div>
        </div>
        <script>
            // 为登录按钮绑定事件,点击之后跳转到chart.html页面,并且要把用户名和头像带过去
            // 通过location.href带过去
            login.addEventListener("click", ()=>{
                // 拿到用户名
                let name = username.value;
                // 头像的随机值
                let avatar = Math.floor(Math.random()*100);
                // 这里是相对路径,所以直接替换最后一个斜杆的内容
                let url = `chart.html?name=${name}&navatar=${avatar}.jpg`;
                console.log(url);
                // window.location = location
                // location == location.href
                location.href = url;
            }); 

        
        </script>
    </body>
</html>

chart.html

chart.html

```cpp
<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>微信聊天室</title>
        <link rel="stylesheet" href="styles/normalize.css">
        <link rel="stylesheet" href="styles/reset.css">
        <link rel="stylesheet" href="styles/chart.css">
    </head>
    <body>
        <div id="chart-container">
            <div class="chart-user-list" id="chart-user-list">
                <div class="user-item"  id="userinfo">
                    <!-- <img src="images/avatar/15.jpg" alt="">
                    未知名 -->
                </div>
            </div>
            <div class="chart-main-area">
                <div class="chart-main-title">
                    <h1>微信聊天室(<span id="userNumber"></span>)-<span id="currentUser"></span></h1>
                </div>
                <div class="chart-list" id="chart-list">
                    <div class="user-logined" id="user-logined"><span id="logined-user"></span>上线了</div>
                    <!-- <div class="chart-item">
                        <div class="user-face"><img src="images/avatar/11.jpg" alt=""></div>
                        <div class="user-message">111</div>
                    </div> -->
                </div>
                <div class="chart-form">
                    <div><textarea class="chart-form-message" id="message" onkeydown="sendto(event.keyCode)"></textarea></div>
                    <div><input type="button" id="send" class="chart-form-send" value="发表"></div>
                </div>
            </div>
        </div>
        <script src="scripts/socket.io.js"></script>
        <script>
            let str = window.location.search;
            if(!str){//如果没有参数,则不能跳转到chart页面
                location = "index.html";
            }
            // 这里要接收index传过来的参数,并且用于更新界面
            // 解析请求资源路径,获取name与avator
            let name = str.split("&")[0].split("=")[1];
            let avatar = str.split("&")[1].split("=")[1];
            name = decodeURI(name);
            // 更新页面中的头像与昵称:下面要注意头像的随机不是每个头像都有,因为文件夹内不连续
            userinfo.innerHTML = `
                    <img src="images/avatar/${avatar}" alt="">
                    ${name}
            `;
            // 如果客户端和服务端的socket版本不一样。一个新旧,则会导致俩者无法正常交涉,会断开再连接
            let socket = io('ws://localhost:3000/');
            console.log(socket);


            // 登录并将用户名发送到服务端,让客户端根据名字将每个用户的socket保存起来
            socket.emit("login", name);

            // 更新群聊人数
            // 连接建立成功后,直接开始监听服务端发过来的count_updated消息
            socket.on("count_updated", (data)=>{
                // 把data更新到span中即可
                userNumber.innerHTML = data;
            });

            // 服务端上线,则界面通知
            socket.on("person", (data)=>{
                var person = document.getElementById("logined-user");
                person.innerHTML = data;
                // 过了一段时间之后,让通知消失
                var notice = document.getElementById("user-logined");
                setTimeout(()=>{
                    notice.style.display = "none";
                }, 2000);
            });


            //版本一: 点击按钮发消息。头像是固定的
            // send.addEventListener("click", function(){
            //     // 获取textarea中输入的文本
            //     let msg = document.getElementById("message").value;
            //     // 发送给客户端消息
            //     if(msg.trim() && msg.length<30){//符合发送信息的要求,才可以发送给服务器
            //         // string.trim()是去除字符串左右俩端的字符串
            //         socket.emit("textmsg", msg);
            //         // 发送完毕之后让消息清空
            //         document.getElementById("message").value = "";
            //     }
            // });

            // 版本一:监听发送的消息
            // 连接建立成功后就直接开始监听服务端发过来的消息
            // socket.on("textmsg", (data)=>{
            //     console.log(data);
            //     // 将收到的消息添加到显示屏上
            //     // 构造一个聊天记录列表div,追加到聊天记录列表
            //     let div = document.createElement('div');
            //     div.className = 'chart-item';
            //     div.innerHTML = `<div class="user-face">
            //                         <img src="images/avatar/11.jpg" alt="">
            //                      </div>
            //                     <div class="user-message">${data}</div>`;
            //     // 把div追加到chart-list子元素的末尾位置
            //     let list = document.getElementById("chart-list");
            //     list.appendChild(div);
            //     // 将list元素的滚动条始终定位在最底部
            //     list.scrollTop = list.scrollHeight;
            // });

            

            //版本二: 点击按钮发消息。头像是变化的
            // send.addEventListener("click", function(){
            //     // 获取textarea中输入的文本
            //     let msg = document.getElementById("message").value;
            //     // 发送给客户端消息
            //     if(msg.trim() && msg.length<30){//符合发送信息的要求,才可以发送给服务器
            //         // string.trim()是去除字符串左右俩端的字符串
            //         // 这里将用户的发送内容和头像发送过服务端
            //         let allmsg = {content: msg, name: name, avatar: avatar};
            //         socket.emit("textmsg", allmsg);
            //         // 发送完毕之后让消息清空
            //         document.getElementById("message").value = "";
            //     }
            // });

            // 版本二(动态头像):监听发送的消息
            // 连接建立成功后就直接开始监听服务端发过来的消息
            socket.on("textmsg", (data)=>{
                console.log(data);
                // 将收到的消息添加到显示屏上
                // 构造一个聊天记录列表div,追加到聊天记录列表
                let div = document.createElement('div');
                div.className = 'chart-item';
                div.innerHTML = `<div class="user-face">
                                    <img src="images/avatar/${data.avatar}" alt="">
                                 </div>
                                 <p style="font-size: 15px">${data.name}</p>
                                <div class="user-message">${data.content}</div>`;
                // 把div追加到chart-list子元素的末尾位置
                let list = document.getElementById("chart-list");
                list.appendChild(div);
                // 将list元素的滚动条始终定位在最底部
                list.scrollTop = list.scrollHeight;
            });

            // 封装发送信息的函数
            function sendmsg(){
                // 获取textarea中输入的文本
                let msg = document.getElementById("message").value;
                // 发送给客户端消息
                if(msg.trim() && msg.length<30){//符合发送信息的要求,才可以发送给服务器
                    // string.trim()是去除字符串左右俩端的字符串
                    // 这里将用户的发送内容和头像发送过服务端                 这里先写死私发给亮亮
                    let allmsg = {content: msg, name: name, avatar: avatar, to:"亮亮"};
                    socket.emit("textmsg", allmsg);
                    // 发送完毕之后让消息清空
                    document.getElementById("message").value = "";
                }
            }
            //版本三: 点击按钮发消息。头像是变化的,且回车会触发发送事件
            send.addEventListener("click", function(){
                // 点击按钮发送信息
                sendmsg();
            });
            // 回车触发的事件函数
            function sendto(key){
                if(key == 13){//回车
                    sendmsg();
                } 
            }

        </script>
    </body>
</html>

app.js

const express = require('express');
const server = express();

// 取出serve里的http底层
const http = require('http').createServer(server);
// serve和io建立联系
const socketio = require('socket.io')(http);

// 用于保存实时在线人数
let count = 0;
// 保存用户们的{名字:socket}
let clients = {};

socketio.on("connection", (socket)=>{
    console.log(`客户端${socket.id}访问`);
    // 有人连接服务器,就更新在线人数,把最新的count值发送给所有客户端
    count++;
    socketio.emit('count_updated', count);
    // 登录消息,通知上线
    // socket.emit('person', socket.id);

    // 捕获断开连接事件,count--
    socket.on("disconnect", ()=>{//因为是事件
        count--;
        socketio.emit('count_updated', count);
    });
    // 因为是事件处理函数,所以不能保证放在外面一定会执行,所以要放到事件的回调函数中
    // socketio.emit('count_updated', count);

    // 监听客户端发过来的消息做不同的处理
    socket.on("textmsg", (data)=>{//监听textmsg类型的数据,把数据放到data中
        // 1.群聊:将收到的信息群发给每个用户
        // socketio.emit("textmsg", data);//向所有客户端发送接收的信息
        // 2.私聊:将收到的信息,发送给前端指定发送的用户,这里的指定用户是写死的
        clients[data.to].emit("textmsg", data);

    });

    // 这里在每个用户连接之后,根据用户名保存每个socket对象
    socket.on("login", (data)=>{
        // 保存每个用户
        clients[data] = socket;
        console.log(clients);
        // 登录成功,通知上线
        socket.emit("person", data);
    });
});

// 何时建立好连接,当客户端加载完页面之后就建立连接
// 群里思路是每个人发送给服务端,服务端将每个人发送的信息都转发给全部客户端

// 设置公共目录
server.use(express.static('public'));
// 监听
http.listen(3000, function(){
    console.log("serve is running");
})


style/chart.css

body{
    background-color: #fff;
    font:14px/1.5 'Microsoft Yahei';
}
::-webkit-scrollbar {
    width:12px;
    }
    /* 滚动槽 */
    ::-webkit-scrollbar-track {
    -webkit-box-shadow:inset006pxrgba(0,0,0,0.3);
    border-radius:10px;
    }
    /* 滚动条滑块 */
    ::-webkit-scrollbar-thumb {
    border-radius:10px;
    background:rgba(0,0,0,0.1);
    -webkit-box-shadow:inset006pxrgba(0,0,0,0.5);
    }
    ::-webkit-scrollbar-thumb:window-inactive {
    background:rgba(255,0,0,0.4);
    }
#login-container{
    width:285px;
    min-height:400px;
    border-radius: 4px;;
    margin:50px auto 0 auto;
    background-color: #F5F5F5;
    border:1px solid #ccc;
}
#login-container .login-title{
    height: 40px;
    line-height: 40px;
    padding-left:10px;
    border-bottom:1px solid #ddd;
}
#login-container .login-user-icon{
    width:120px;
    height: 120px;
    margin: 50px auto;;
}
#login-container .login-item{
    display: block;
    margin:0 auto 10px auto;
}
#login-container .login-username{
    width:190px;
    height: 30px;
    line-height: 30px;
    padding:0 5px;
    text-align: center;
}
#login-container .login-button{
    width:204px;
    background-color:#1AAD19;
    border: none;
    line-height: 40px;
    color:#fff;
}
#chart-container::before,#chart-container::after{
    content: '.';
    display: block;
    height: 0;
    visibility: hidden;
    clear: both;
}
#chart-container{
    width:900px;
    height: 500px;
    margin: 50px auto;
    border:1px solid #ccc;
}
#chart-container .chart-user-list{
    float:left;
    width:250px;
    height: 500px;
    background-color: #EAE8E7;
}
#chart-container .chart-user-list .user-item{
    padding:10px;
    border-bottom:1px solid #cccc;
}
#chart-container .chart-user-list .user-item img{
    margin-right: 5px;
    width: 40px;
    height: 40px;
    border-radius: 5px;
    vertical-align: middle;
}
#chart-container .chart-main-area{
    float:left;
    width:650px;
    height: 500px;
    background-color: #F5F5F5;
}
#chart-container .chart-main-area .chart-main-title{
    margin-bottom: 10px;;
    height: 60px;
    border-bottom:1px solid #cccc;
}
#chart-container .chart-main-area .chart-main-title h1{
    margin-left:20px;
    line-height: 60px;
    font-size:18px;
    
}
#chart-container .chart-list{
    position: relative;
    height: 295px;
    overflow-Y: auto; 
    overflow-X:hidden;
}
#chart-container .chart-list  .user-logined{
    position: absolute;
    bottom: 10px;
    width: 250px;
    padding: 10px;
    text-align: center;
    background-color: #DADADA;
    color: #000;
    left:190px;
    transition:all 0.8s ease-in-out;  
}
#chart-container .chart-list .chart-item::after,#chart-container .chart-list .chart-item::before{
    content: '.';
    display: block;
    height: 0;
    visibility: hidden;
    clear: both;
}
#chart-container .chart-list .chart-item{
    margin-bottom: 10px;
}
#chart-container .chart-list .chart-item .user-face{
    float:left;
    margin:0 15px;
}
#chart-container .chart-list .chart-item .user-face img{
    width:40px;
    height: 40px;
    border-radius: 5px;
}
#chart-container .chart-list .chart-item .user-message{
    position: relative;
    float:left;
    padding:10px;
    min-width: 100px;
    max-width: 520px;
    background: #fff;
    font-size: 12px;
    border-radius: 3px;
}
#chart-container .chart-list .chart-item .user-message::before{
    content: ' ';
    position: absolute;
    width:10px;
    height:10px;
    background: #fff;
    top:10px;
    left:-5px;
    z-index: 222;
    transform: rotate(45deg);
    
}
#chart-container .chart-form{
    height: 134px;
    background: #fff;
}
#chart-container .chart-form .chart-form-message{
    width:630px;
    padding:5px 10px;
    height: 95px;
    line-height: 1.5;
    resize:none;
    border: none;
}

#chart-container .chart-form .chart-form-send{
    float: right;
    width: 70px;
    height: 24px;
    margin-right: 10px;
    background-color: #F5F5F5;
    color: #606060;
    border: 1px solid #ccc;
    cursor: pointer;
}

style/normalize.css

/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */

/* Document
   ========================================================================== */

/**
 * 1. Correct the line height in all browsers.
 * 2. Prevent adjustments of font size after orientation changes in iOS.
 */

 html {
    line-height: 1.15; /* 1 */
    -webkit-text-size-adjust: 100%; /* 2 */
  }
  
  /* Sections
     ========================================================================== */
  
  /**
   * Remove the margin in all browsers.
   */
  
  body {
    margin: 0;
  }
  
  /**
   * Render the `main` element consistently in IE.
   */
  
  main {
    display: block;
  }
  
  /**
   * Correct the font size and margin on `h1` elements within `section` and
   * `article` contexts in Chrome, Firefox, and Safari.
   */
  
  h1 {
    font-size: 2em;
    margin: 0.67em 0;
  }
  
  /* Grouping content
     ========================================================================== */
  
  /**
   * 1. Add the correct box sizing in Firefox.
   * 2. Show the overflow in Edge and IE.
   */
  
  hr {
    box-sizing: content-box; /* 1 */
    height: 0; /* 1 */
    overflow: visible; /* 2 */
  }
  
  /**
   * 1. Correct the inheritance and scaling of font size in all browsers.
   * 2. Correct the odd `em` font sizing in all browsers.
   */
  
  pre {
    font-family: monospace, monospace; /* 1 */
    font-size: 1em; /* 2 */
  }
  
  /* Text-level semantics
     ========================================================================== */
  
  /**
   * Remove the gray background on active links in IE 10.
   */
  
  a {
    background-color: transparent;
  }
  
  /**
   * 1. Remove the bottom border in Chrome 57-
   * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
   */
  
  abbr[title] {
    border-bottom: none; /* 1 */
    text-decoration: underline; /* 2 */
    text-decoration: underline dotted; /* 2 */
  }
  
  /**
   * Add the correct font weight in Chrome, Edge, and Safari.
   */
  
  b,
  strong {
    font-weight: bolder;
  }
  
  /**
   * 1. Correct the inheritance and scaling of font size in all browsers.
   * 2. Correct the odd `em` font sizing in all browsers.
   */
  
  code,
  kbd,
  samp {
    font-family: monospace, monospace; /* 1 */
    font-size: 1em; /* 2 */
  }
  
  /**
   * Add the correct font size in all browsers.
   */
  
  small {
    font-size: 80%;
  }
  
  /**
   * Prevent `sub` and `sup` elements from affecting the line height in
   * all browsers.
   */
  
  sub,
  sup {
    font-size: 75%;
    line-height: 0;
    position: relative;
    vertical-align: baseline;
  }
  
  sub {
    bottom: -0.25em;
  }
  
  sup {
    top: -0.5em;
  }
  
  /* Embedded content
     ========================================================================== */
  
  /**
   * Remove the border on images inside links in IE 10.
   */
  
  img {
    border-style: none;
  }
  
  /* Forms
     ========================================================================== */
  
  /**
   * 1. Change the font styles in all browsers.
   * 2. Remove the margin in Firefox and Safari.
   */
  
  button,
  input,
  optgroup,
  select,
  textarea {
    font-family: inherit; /* 1 */
    font-size: 100%; /* 1 */
    line-height: 1.15; /* 1 */
    margin: 0; /* 2 */
  }
  
  /**
   * Show the overflow in IE.
   * 1. Show the overflow in Edge.
   */
  
  button,
  input { /* 1 */
    overflow: visible;
  }
  
  /**
   * Remove the inheritance of text transform in Edge, Firefox, and IE.
   * 1. Remove the inheritance of text transform in Firefox.
   */
  
  button,
  select { /* 1 */
    text-transform: none;
  }
  
  /**
   * Correct the inability to style clickable types in iOS and Safari.
   */
  
  button,
  [type="button"],
  [type="reset"],
  [type="submit"] {
    -webkit-appearance: button;
  }
  
  /**
   * Remove the inner border and padding in Firefox.
   */
  
  button::-moz-focus-inner,
  [type="button"]::-moz-focus-inner,
  [type="reset"]::-moz-focus-inner,
  [type="submit"]::-moz-focus-inner {
    border-style: none;
    padding: 0;
  }
  
  /**
   * Restore the focus styles unset by the previous rule.
   */
  
  button:-moz-focusring,
  [type="button"]:-moz-focusring,
  [type="reset"]:-moz-focusring,
  [type="submit"]:-moz-focusring {
    outline: 1px dotted ButtonText;
  }
  
  /**
   * Correct the padding in Firefox.
   */
  
  fieldset {
    padding: 0.35em 0.75em 0.625em;
  }
  
  /**
   * 1. Correct the text wrapping in Edge and IE.
   * 2. Correct the color inheritance from `fieldset` elements in IE.
   * 3. Remove the padding so developers are not caught out when they zero out
   *    `fieldset` elements in all browsers.
   */
  
  legend {
    box-sizing: border-box; /* 1 */
    color: inherit; /* 2 */
    display: table; /* 1 */
    max-width: 100%; /* 1 */
    padding: 0; /* 3 */
    white-space: normal; /* 1 */
  }
  
  /**
   * Add the correct vertical alignment in Chrome, Firefox, and Opera.
   */
  
  progress {
    vertical-align: baseline;
  }
  
  /**
   * Remove the default vertical scrollbar in IE 10+.
   */
  
  textarea {
    overflow: auto;
  }
  
  /**
   * 1. Add the correct box sizing in IE 10.
   * 2. Remove the padding in IE 10.
   */
  
  [type="checkbox"],
  [type="radio"] {
    box-sizing: border-box; /* 1 */
    padding: 0; /* 2 */
  }
  
  /**
   * Correct the cursor style of increment and decrement buttons in Chrome.
   */
  
  [type="number"]::-webkit-inner-spin-button,
  [type="number"]::-webkit-outer-spin-button {
    height: auto;
  }
  
  /**
   * 1. Correct the odd appearance in Chrome and Safari.
   * 2. Correct the outline style in Safari.
   */
  
  [type="search"] {
    -webkit-appearance: textfield; /* 1 */
    outline-offset: -2px; /* 2 */
  }
  
  /**
   * Remove the inner padding in Chrome and Safari on macOS.
   */
  
  [type="search"]::-webkit-search-decoration {
    -webkit-appearance: none;
  }
  
  /**
   * 1. Correct the inability to style clickable types in iOS and Safari.
   * 2. Change font properties to `inherit` in Safari.
   */
  
  ::-webkit-file-upload-button {
    -webkit-appearance: button; /* 1 */
    font: inherit; /* 2 */
  }
  
  /* Interactive
     ========================================================================== */
  
  /*
   * Add the correct display in Edge, IE 10+, and Firefox.
   */
  
  details {
    display: block;
  }
  
  /*
   * Add the correct display in all browsers.
   */
  
  summary {
    display: list-item;
  }
  
  /* Misc
     ========================================================================== */
  
  /**
   * Add the correct display in IE 10+.
   */
  
  template {
    display: none;
  }
  
  /**
   * Add the correct display in IE 10.
   */
  
  [hidden] {
    display: none;
  }

style/reset.css

/* http://meyerweb.com/eric/tools/css/reset/
   v2.0-modified | 20110126
   License: none (public domain)
*/

html, body, div, span, applet, object, iframe,
h1, h2, h3, h4, h5, h6, p, blockquote, pre,
a, abbr, acronym, address, big, cite, code,
del, dfn, em, img, ins, kbd, q, s, samp,
small, strike, strong, sub, sup, tt, var,
b, u, i, center,
dl, dt, dd, ol, ul, li,
fieldset, form, label, legend,
table, caption, tbody, tfoot, thead, tr, th, td,
article, aside, canvas, details, embed,
figure, figcaption, footer, header, hgroup,
menu, nav, output, ruby, section, summary,
time, mark, audio, video {
  margin: 0;
	padding: 0;
	border: 0;
	font-size: 100%;
	font: inherit;
	vertical-align: baseline;
}

/* make sure to set some focus styles for accessibility */
:focus {
    outline: 0;
}

/* HTML5 display-role reset for older browsers */
article, aside, details, figcaption, figure,
footer, header, hgroup, menu, nav, section {
	display: block;
}

body {
	line-height: 1;
}

ol, ul {
	list-style: none;
}

blockquote, q {
	quotes: none;
}

blockquote:before, blockquote:after,
q:before, q:after {
	content: '';
	content: none;
}

table {
	border-collapse: collapse;
	border-spacing: 0;
}

input[type=search]::-webkit-search-cancel-button,
input[type=search]::-webkit-search-decoration,
input[type=search]::-webkit-search-results-button,
input[type=search]::-webkit-search-results-decoration {
    -webkit-appearance: none;
    -moz-appearance: none;
}

input[type=search] {
    -webkit-appearance: none;
    -moz-appearance: none;
    -webkit-box-sizing: content-box;
    -moz-box-sizing: content-box;
    box-sizing: content-box;
}

textarea {
    overflow: auto;
    vertical-align: top;
    resize: vertical;
}

/**
 * Correct `inline-block` display not defined in IE 6/7/8/9 and Firefox 3.
 */

audio,
canvas,
video {
    display: inline-block;
    *display: inline;
    *zoom: 1;
    max-width: 100%;
}

/**
 * Prevent modern browsers from displaying `audio` without controls.
 * Remove excess height in iOS 5 devices.
 */

audio:not([controls]) {
    display: none;
    height: 0;
}

/**
 * Address styling not present in IE 7/8/9, Firefox 3, and Safari 4.
 * Known issue: no IE 6 support.
 */

[hidden] {
    display: none;
}

/**
 * 1. Correct text resizing oddly in IE 6/7 when body `font-size` is set using
 *    `em` units.
 * 2. Prevent iOS text size adjust after orientation change, without disabling
 *    user zoom.
 */

html {
    font-size: 100%; /* 1 */
    -webkit-text-size-adjust: 100%; /* 2 */
    -ms-text-size-adjust: 100%; /* 2 */
}

/**
 * Address `outline` inconsistency between Chrome and other browsers.
 */

a:focus {
    outline: thin dotted;
}

/**
 * Improve readability when focused and also mouse hovered in all browsers.
 */

a:active,
a:hover {
    outline: 0;
}

/**
 * 1. Remove border when inside `a` element in IE 6/7/8/9 and Firefox 3.
 * 2. Improve image quality when scaled in IE 7.
 */

img {
    border: 0; /* 1 */
    -ms-interpolation-mode: bicubic; /* 2 */
}

/**
 * Address margin not present in IE 6/7/8/9, Safari 5, and Opera 11.
 */

figure {
    margin: 0;
}

/**
 * Correct margin displayed oddly in IE 6/7.
 */

form {
    margin: 0;
}

/**
 * Define consistent border, margin, and padding.
 */

fieldset {
    border: 1px solid #c0c0c0;
    margin: 0 2px;
    padding: 0.35em 0.625em 0.75em;
}

/**
 * 1. Correct color not being inherited in IE 6/7/8/9.
 * 2. Correct text not wrapping in Firefox 3.
 * 3. Correct alignment displayed oddly in IE 6/7.
 */

legend {
    border: 0; /* 1 */
    padding: 0;
    white-space: normal; /* 2 */
    *margin-left: -7px; /* 3 */
}

/**
 * 1. Correct font size not being inherited in all browsers.
 * 2. Address margins set differently in IE 6/7, Firefox 3+, Safari 5,
 *    and Chrome.
 * 3. Improve appearance and consistency in all browsers.
 */

button,
input,
select,
textarea {
    font-size: 100%; /* 1 */
    margin: 0; /* 2 */
    vertical-align: baseline; /* 3 */
    *vertical-align: middle; /* 3 */
}

/**
 * Address Firefox 3+ setting `line-height` on `input` using `!important` in
 * the UA stylesheet.
 */

button,
input {
    line-height: normal;
}

/**
 * Address inconsistent `text-transform` inheritance for `button` and `select`.
 * All other form control elements do not inherit `text-transform` values.
 * Correct `button` style inheritance in Chrome, Safari 5+, and IE 6+.
 * Correct `select` style inheritance in Firefox 4+ and Opera.
 */

button,
select {
    text-transform: none;
}

/**
 * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
 *    and `video` controls.
 * 2. Correct inability to style clickable `input` types in iOS.
 * 3. Improve usability and consistency of cursor style between image-type
 *    `input` and others.
 * 4. Remove inner spacing in IE 7 without affecting normal text inputs.
 *    Known issue: inner spacing remains in IE 6.
 */

button,
html input[type="button"], /* 1 */
input[type="reset"],
input[type="submit"] {
    -webkit-appearance: button; /* 2 */
    cursor: pointer; /* 3 */
    *overflow: visible;  /* 4 */
}

/**
 * Re-set default cursor for disabled elements.
 */

button[disabled],
html input[disabled] {
    cursor: default;
}

/**
 * 1. Address box sizing set to content-box in IE 8/9.
 * 2. Remove excess padding in IE 8/9.
 * 3. Remove excess padding in IE 7.
 *    Known issue: excess padding remains in IE 6.
 */

input[type="checkbox"],
input[type="radio"] {
    box-sizing: border-box; /* 1 */
    padding: 0; /* 2 */
    *height: 13px; /* 3 */
    *width: 13px; /* 3 */
}

/**
 * 1. Address `appearance` set to `searchfield` in Safari 5 and Chrome.
 * 2. Address `box-sizing` set to `border-box` in Safari 5 and Chrome
 *    (include `-moz` to future-proof).
 */

input[type="search"] {
    -webkit-appearance: textfield; /* 1 */
    -moz-box-sizing: content-box;
    -webkit-box-sizing: content-box; /* 2 */
    box-sizing: content-box;
}

/**
 * Remove inner padding and search cancel button in Safari 5 and Chrome
 * on OS X.
 */

input[type="search"]::-webkit-search-cancel-button,
input[type="search"]::-webkit-search-decoration {
    -webkit-appearance: none;
}

/**
 * Remove inner padding and border in Firefox 3+.
 */

button::-moz-focus-inner,
input::-moz-focus-inner {
    border: 0;
    padding: 0;
}

/**
 * 1. Remove default vertical scrollbar in IE 6/7/8/9.
 * 2. Improve readability and alignment in all browsers.
 */

textarea {
    overflow: auto; /* 1 */
    vertical-align: top; /* 2 */
}

/**
 * Remove most spacing between table cells.
 */

table {
    border-collapse: collapse;
    border-spacing: 0;
}

html,
button,
input,
select,
textarea {
    color: #222;
}


::-moz-selection {
    background: #b3d4fc;
    text-shadow: none;
}

::selection {
    background: #b3d4fc;
    text-shadow: none;
}

img {
    vertical-align: middle;
}

fieldset {
    border: 0;
    margin: 0;
    padding: 0;
}

textarea {
    resize: vertical;
}

.chromeframe {
    margin: 0.2em 0;
    background: #ccc;
    color: #000;
    padding: 0.2em 0;
}

图片文件夹里面的图片为多张命名为数字的图片,0-99命名,其中有些缺少了。


版权声明:本文为weixin_45847735原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。