从零打造 Vue 聊天组件
啥都不会
最近听说了那个 socket.io,感觉就很吊,就学习了一下
什么是 socket.io
要理解 socket.io,不得不谈谈WebSocket
在 HTML5 之前呢,因为 http 协议是无状态的,要实现浏览器与服务器的实时通讯,如果不使用 flash、applet 等浏览器插件的话,就需要定期轮询服务器来获取信息。这造成了一定的延迟和大量的网络通讯。随着 HTM5 的出现,这一情况有望彻底改观,它就是 WebSocket。
什么是 http 无状态协议
http 协议虽然是无状态的,但是使用它的应用们,为了获得状态,就必须等通过 cookie、session 等工具来获取状态
什么是轮询
轮询呢在 JavaScript 中就是说,设个定时器,过段时间呢就自动发一次请求,来获取数据,就这样反复循环下去。
WebSocket 的工作机制
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。因为 WebSocket 连接本质上就是一个 TCP 连接,所以在数据传输的稳定性和数据传输量的大小方面,和传统轮询以技术比较,具有很大的性能优势。
为了建立一个 WebSocket 连接,客户端浏览器首先要向服务器发起一个 HTTP 请求,这个请求和通常的 HTTP 请求不同,包含了一些附加头信息,其中附加头信息”Upgrade: WebSocket”表明这是一个申请协议升级的 HTTP 请求,服务器端解析这些附加的头信息然后产生应答信息返回给客户端,客户端和服务器端的 WebSocket 连接就建立起来了,双方就可以通过这个连接通道自由的传递信息,并且这个连接会持续存在直到客户端或者服务器端的某一方主动的关闭连接。
好,那么接下来就是
Codding Time
使用 vue-socket.io
自行安装
- vue-socket.io
- socket.io-client
import VueSocketIO from "vue-socket.io";
import SocketIO from "socket.io-client";Vue.use(new VueSocketIO({debug: false,connection: SocketIO(),vuex: {store,actionPrefix: "SOCKET_", //为vuex设置的两个前缀mutationPrefix: "SOCKET_",},})
);
SocketIO() 括号里啥也没写,表示使用当前端口
解决跨域问题
配置 vue.config.js 文件
module.exports = {devServer: {proxy: {"/socket.io": {target: "http://127.0.0.1:8000",// 允许跨域changeOrigin: true,ws: true,// logLevel: "debug",},"sockjs-node": {target: "http://127.0.0.1:8000",ws: false,changeOrigin: true,},},},
};
target - 服务端端口
changeOrigin - 是否允许跨域
ws - 是否代理 WebSocket
logLevel - 是否打印日志
服务端代码
const path = require("path");
const express = require("express");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server);// app.get("/", function (req, res) {// res.sendFile(__dirname + "/index.html");
// });app.use(express.static(__dirname + "/public"))let userList = [];io.on("connection", (socket) => {console.log("a user connected");updateUser();let userId = "";socket.on("login", (userID, callback) => {callback();userId = userID;userList.push(userId);// console.log(userList);updateUser();});socket.on("talk", (data) => {// 接收连接中的 talk 事件console.log(data);data.role = "your";io.emit("output", data);});socket.on("disconnect", () => {console.log("user disconnected");userList.splice(userList.indexOf(userId), 1);updateUser();});function updateUser() {io.emit("loadUser", userList);}
});var port = process.env.PORT || 8000;server.listen(port, () => {console.log("服务启动成功啦~");
});
查看 socket 连接是否渲染成功
在 vue 文件中进行查看
sockets: {//查看socket是否渲染成功connect() {console.log("连接成功");},disconnect(){console.log("断开链接");},//检测socket断开链接 reconnect(){console.log("重新链接");},
},
发送消息
this.$socket.emit("自定义事件名", 发送的数据..., ...)
接收消息
需要在 mounted 生命周期内进行监听
this.sockets.subscribe("接收的事件名", (data, ...) => {})
模拟登录
在登录后获取用户头像信息
登录时选择的头像作为聊天时的头像,跳过则使用默认头像(男生)
data() {return {show: true, // 是否显示蒙层userObj: {avatar: require("@/assets/me.png")},}
}// 点击男生头像
onCMe() {this.show = falsethis.userObj.avatar = require("@/assets/me.png")
},// 点击女生头像
onCYou() {this.show = falsethis.userObj.avatar = require("@/assets/you.png")
},// 跳过
onCOver() {this.show = false
},
现在要实现的就是上面这种效果,“你”发送的消息默认在左侧,“我”发送的消息在右侧,也就是在“我”发送消息时设一个标志,表示这是“我”发送的消息,就不需要 subscribe 后端发送的消息了。
但是,只是这样子处理的话会发现一个问题,那就是你发送的这条消息在右侧,同样也在你的对话者的右侧,这显然是不合理的,它应该在你的对话者的左侧出现。
这就需要在后端或是前端进行处理了,我是在后端进行处理的,因为在前端处理的话会比较不安全,但是我会给出在前端进行处理的方案。
在后端进行处理
后端接收到前端发送的数据对象后,对 role(角色)属性进行修改,改为 your 即是靠左的默认样式
在接收消息后进行处理
socket.on("talk", (data) => {// 接收连接中的 talk 事件console.log(data);data.role = "your";io.emit("output", data);
});
OR 在前端进行处理
在前端进行处理的话会用到对象的深拷贝,因为我们不能直接去修改原对象中的 role,因为它现在可能为 “mine”,改了他之后就会出现靠左的错误。
在发送消息之前进行处理
// 把 obj 深拷贝到 sendObj
let sendObj = Object.assign({}, obj)
// 发送 socket 消息
this.$socket.emit("talk", sendObj)
解决消息发送重复显示问题
问题描述
上面不是已经解决了消息位置问题吗,现在已经达到了我发送的消息在右侧,其他人的都在左侧,但是我发送一条消息右边显示一次,左边显示一次,这很明显不对嘛
解决方案
我需要为每条消息都加上一个 msgID,也就是在每发送一条消息时就为消息添加一个 msgID,这个 ID 为了防止重复,用了以下写法
parseInt(Date.parse(new Date()) + Math.random() * 100)
然后在 subscribe 接收服务端发送来的消息的时候进行判断,判断服务端发送过来的消息中的 msgID 是否和我发送的这条消息为同一条消息(同一个 ID),如果是,则直接 return,如果不是,那就渲染服务端发来的消息
判断写法如下参考
this.sockets.subscribe('output', (data) => {if(!!data && !!this.list && !!this.list[this.list.length - 1] && data.msgID === this.list[this.list.length - 1].msgID) {return} // 渲染服务端发来的消息this.list.push(data)// 此处省略20行...
})
这样就实现了基本的聊天功能了
接下来的就是简单的实现了一个动画表情特效
Lottie 动画表情
现在我要实现以下特效
引入文件
首先引入 lottie 库及动画表情文件
import lottie from "@/assets/lottie.min.js"
import bomb from "@/assets/3145-bomb.json"
import pumpkin from "@/assets/43215-pumpkins-sticker-4.json"
import explosion from "@/assets/9990-explosion.json"
基本用法
// 对每个表情创建 lottie 播放器
const player = lottie.loadAnimation ({container: lottieEle, // 包含lottie的DOM元素renderer: "svg",loop: true, // 循环播放autoplay: true, // 自动播放animationData: this.stickers[key].path, // 动画路径
});
发送消息时使消息面板滚动到最底部
// 滚动到最新消息
this.$nextTick(()=>{this.$refs.panel.scrollTop = this.$refs.panel.scrollHeight
})
需要操作DOM的功能一定要等到DOM加载完毕后再进行操作,否则可能出现误差,我使用的是 this.$nextTick(),也可以使用其他异步操作来完成
播放完成后,销毁爆炸相关的动画和元素
// 播放完成后,销毁爆炸相关的动画和元素
explosionPlayer.addEventListener("complete", () => {explosionPlayer.destroy();explosionEle.remove();
});
这里用到了 lottie 的 complete 动画播放完成事件
获取数组的后五项
let list = ["a", "b", "c", "d", "e", "f", "g"]
list.slice(-5)
用的是 this.$nextTick(),也可以使用其他异步操作来完成
播放完成后,销毁爆炸相关的动画和元素
// 播放完成后,销毁爆炸相关的动画和元素
explosionPlayer.addEventListener("complete", () => {explosionPlayer.destroy();explosionEle.remove();
});
这里用到了 lottie 的 complete 动画播放完成事件
获取数组的后五项
let list = ["a", "b", "c", "d", "e", "f", "g"]
list.slice(-5)
部署上线
Let’s Chat!
https://hhchat.herokuapp.com/
感谢阅读,感谢三连支持,关注我,后面还会写很多好文章的~
从零打造 Vue 聊天组件相关推荐
- 从零实现Vue的组件库(十)- Select 实现
当选项过多时,使用下拉菜单展示并选择内容. Select 组件主要特点在于: 数据双向绑定,下拉列表变动时,选中项如何回显: 单选.多选的区分,以及对应处理. 1. 实例 代码 <fat-sel ...
- 从零实现Vue的组件库(零)- 基本结构以及构建工具
今年三月份入职以来,一直在做后台运营系统,其中重复性工作有很多,其中组件库就是一项.尽管现阶段有很多优秀的开源组件库,但是为了适应产品奇奇怪怪的需求,还是要手动实现一些组件.将其沉淀下来,积累一些自己 ...
- 从零实现Vue的组件库(十二)- Table 实现
基于Table标签的展示数据组件. Table 组件主要特点在于: 组件 data 的解耦,减少重复代码: 良好的扩展性,可以通过自定义列模板来适应不同的业务场景. 1. 实例 代码 <fat- ...
- 手把手带你从零打造Vue SSR,清晰易懂!
Vue SSR,服务端渲染,优点大家都很清楚,能大大提升首屏渲染速度,优化用户体验,还有利于SEO. 但说实话,Vue SSR并不好上手.官网给的例子大而全,太复杂.而网上很多从0到1打造Vue SS ...
- (实战)Vue + Koa从零打造一个H5页面可视化编辑器——Quark-h5
作者:围的围 https://juejin.im/post/5dc81428e51d4523632ee793 前言 想必你一定使用过易企秀或百度H5等微场景生成工具制作过炫酷的h5页面,除了感叹其神奇 ...
- vue 请求在子组件加载后了_从零单排vue第九课--Vue实例及生命周期
前期回顾 上一节课我们重点学习了子组件如何与父组件通信,我们使用$emit方法在子组件中触发一个事件,然后在父组件中接收它.本节课我们来深入了解下vue实例以及它的生命周期是怎么样的? 创建一个Vue ...
- vue.js+socket.io+express+mongodb打造在线聊天[一]
vue.js+socket.io+express+mongodb打造在线聊天 在线地址观看 http://www.chenleiming.com/vuechat github地址 https://gi ...
- Vue + element从零打造一个H5页面可视化编辑器——pl-drag-template
pl-drag-template Github地址:https://github.com/livelyPeng/pl-drag-template 前言 想必你一定使用过易企秀或百度H5等微场景生成工具 ...
- vue统计组件库和ui框架
element ★13489 - 饿了么出品的Vue2的web UI工具套件 Vux ★8133 - 基于Vue和WeUI的组件库 iview ★6634 - 基于 Vuejs 的开源 UI 组件库 ...
最新文章
- [MFC] MFC编译程序,缺少MFC动态链接库的解决
- VTK:PolyData之ExtractSelectionOriginalId
- Hibernate配置数据源,数据库连接池
- 基于AJAX的自动完成
- C++ int型与char型辨析
- java国际象棋棋牌_java使用swing绘制国际象棋棋盘
- Linux上将二进制文件转化为c语言数组
- IDEA中如何配置Tomcat和项目?
- python缠论代码_缠论dll(czsc - 缠中说禅技术分析工具)
- 基于 OpenFlow 的 SDN 技术 (论文笔记)
- jq遍历子元素_jQuery 遍历子元素 遍历所有子元素
- 利用CSS3实现图片3D旋转
- r语言绘制精美pcoa图_「R」数据可视化5:PCA和PCoA图
- 谷歌浏览器怎么设置默认隐身模式启动
- 自定义treeview控件,实现右键菜单编辑功能
- 数据可视化新闻,不一样的新闻报道形式
- 技术分享 | 黑盒测试方法论—边界值
- [局域网共享批处理文件]局域网共享时看不到工作组计算机的
- win7常用工具软件记录之爱奇艺视频格式qsv转flv工具(附加下载地址)
- SOA标准化组织和SOA规范组织的区别
热门文章
- 上海交通大学计算机专业有调剂吗,上海交通大学2019计算机系考研调剂信息
- rsa java模数_RSA公私钥获取模数和质数
- linux系统在硬盘上安装程序,怎么样用硬盘上的镜象文件来安装Linux系统?我都进入安装界面了,但是那个安装程序好像找不到那几个镜象文件,请指点...
- Xception论文笔记
- Encoder-Decoder模型和Attention模型
- Mybatis的案例和接口代理开发和模板配置
- 小汤学编程之JAVA基础day11——集合框架:List/Set/Map集合、Collections集合工具类、泛型、TreeMap和TreeSet
- 15个Linux Yum命令实例--安装/卸载/更新
- 关于msi格式的程序包的安装
- Service实现文件下载