天我們將為大家介紹一個令網頁性能大幅提升的神奇技術——CSS硬件加速。隨著移動互聯網的蓬勃發展和網頁設計越發復雜,如何優化網頁性能成為了前端開發者們亟待解決的問題。在這篇文章中,我們將深入了解CSS硬件加速的原理,并通過一個生動的案例來展示它如何幫助我們改善網頁的渲染性能。
一、什么是CSS硬件加速
在傳統的網頁渲染中,瀏覽器使用中央處理器(CPU)來處理CSS樣式和頁面渲染。然而,隨著網頁變得越來越復雜,例如包含大量動畫、過渡效果或復雜的變換,CPU可能會承擔較重的負擔,導致頁面加載緩慢或卡頓。CSS硬件加速是一種解決方案,它充分利用了計算機的圖形處理單元(GPU)來加快CSS樣式的處理和渲染,從而提高頁面性能和流暢度。
1.1 CPU
CPU 即中央處理器。
CPU是計算機的大腦,它提供了一套指令集,我們寫的程序最終會通過 CPU 指令來控制的計算機的運行。它會對指令進行譯碼,然后通過邏輯電路執行該指令。整個執行的流程分為了多個階段,叫做流水線。指令流水線包括取指令、譯碼、執行、取數、寫回五步,這是一個指令周期。CPU會不斷的執行指令周期來完成各種任務。
1.2 GPU
GPU 即圖形處理器。
GPU,是Graphics ProcessingUnit的簡寫,是現代顯卡中非常重要的一個部分,其地位與CPU在主板上的地位一致,主要負責的任務是加速圖形處理速度。GPU是顯卡的“大腦”,它決定了該顯卡的檔次和大部分性能,同時也是2D顯示卡和3D顯示卡的區別依據。2D顯示芯片在處理3D圖像和特效時主要依賴CPU的處理能力,稱為“軟加速”。3D顯示芯片是將三維圖像和特效處理功能集中在顯示芯片內,也即所謂的“硬件加速”功能。
二、CSS硬件加速原理
CSS硬件加速的原理涉及到瀏覽器的渲染引擎、GPU以及優化渲染的過程。
2.1 瀏覽器的渲染流程
一個完整的渲染步驟大致可總結為如下:
2.2 CSS硬件加速觸發
在傳統的渲染過程中,布局和繪制是由CPU來完成的,而在CSS硬件加速下,GPU參與了渲染的處理,從而提高了性能。
CSS 中的以下幾個屬性能觸發硬件加速:
1.transform屬性:該屬性用于應用2D或3D變換效果,如旋轉、縮放、平移等。當使用transform屬性時,瀏覽器會將變換任務交給GPU處理,從而實現硬件加速。
2.opacity屬性:該屬性用于設置元素的不透明度。雖然它主要用于控制透明度,但是一個不為1的值(例如0.99)也可以觸發硬件加速。
3.will-change屬性:will-change屬性用于提示瀏覽器一個元素將要發生的變化,以便瀏覽器在渲染過程中做出優化。
一旦CSS硬件加速被觸發,相關的渲染任務將被GPU處理。GPU在處理圖形和動畫方面通常比CPU更快和更高效。對于復雜的CSS動畫和變換,GPU可以并行處理多個任務,從而提高性能和流暢度。
請注意,CSS硬件加速并不是適用于所有情況。雖然它在許多情況下可以帶來顯著的性能提升,但有時也可能導致額外的GPU資源占用,從而影響其他應用程序的性能。因此,在使用CSS硬件加速時,我們應該進行性能測試和優化,確保在特定情況下確實能獲得性能的提升。
三、CSS硬件加速案例
現在,我們來看一個實際的案例,通過啟用CSS硬件加速來改善網頁性能。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<style>
.app {
position: relative;
width: 400px;
height: 400px;
}
.box {
position: absolute;
left: 0;
top: 0;
width: 100px;
height: 100px;
background-color: yellowgreen;
}
.box-run1 {
-webkit-animation: run1 4s infinite;
animation: run1 4s infinite;
}
.box-run2 {
-webkit-animation: run2 4s infinite;
animation: run2 4s infinite;
}
@keyframes run1 {
0% {
top: 0;
left: 0;
}
25% {
top: 0;
left: 200px;
}
50% {
top: 200px;
left: 200px;
}
75% {
top: 200px;
left: 0;
}
}
@keyframes run2 {
0% {
transform: translate(0, 0);
}
25% {
transform: translate(200px, 0);
}
50% {
transform: translate(200px, 200px);
}
75% {
transform: translate(0, 200px);
}
}
</style>
</head>
<body>
<div class="app">
<div class="box"></div>
</div>
<button class="btn1">循環轉換</button>
<button class="btn2">硬件加速</button>
<script>
let box = document.querySelector(".box");
let btn1 = document.querySelector(".btn1");
let btn2 = document.querySelector(".btn2");
btn1.addEventListener("click", function (e) {
box.classList.remove("box-run2");
box.classList.add("box-run1");
});
btn2.addEventListener("click", function (e) {
box.classList.remove("box-run1");
box.classList.add("box-run2");
});
</script>
</body>
</html>
此時我們可以運行代碼,在頁面上可以看到,2個按鈕均能使box在app當中循環移動。但對于這兩種方式的移動,他們的效率卻有著很大的差異。我們可以使用開發者工具里的Performance去查看。
當我們點擊btn1時,此時box盒子通過定位的left和top進行循環移動時。
此時我們可以看到細節模塊的記錄詳情。
藍色(Loading):網絡通信和HTML解析
黃色(Scripting):Javascript執行
紫色(Rendering):樣式計算和布局,即重排
綠色(Painting):重繪
灰色(Other):其他事件花費的時間
白色(Idle):空閑時間
細節模塊有4個面板,Summary面板每個事件都會有,其他三個只針對特定事件會有。
當我們點擊btn2時,此時box盒子通過transform屬性進行css硬件加速后進行循環移動時。
通過對比我們不難發現,當啟用硬件加速時,方塊的變換會更加流暢,其樣式計算和布局、重繪的時間都會減少。因為GPU參與了渲染過程。
總結
CSS硬件加速是一個強大的前端技術,可以顯著提高網頁的性能和流暢度。通過啟用硬件加速,我們可以將一些渲染任務交給GPU來處理,減輕CPU的負擔,從而優化網頁的渲染性能。然而,我們需要注意不要濫用硬件加速,避免觸發不必要的GPU渲染,以確保真正獲得性能提升。在日常的網頁開發中,我們可以靈活運用CSS硬件加速,為用戶帶來更好的瀏覽體驗。
篇主要講websocket的服務端實現,主要基于Tomcat9.0,關于登錄,用戶列表下篇再講,
前端可以借鑒上篇文章,地址如下:
自己動手實現基于websocket的聊天web聊天功能高仿仿qq
首先創建一個javaweb項目(此處以eclipse開發工具為例)
然后選中項目build path
假如tomcat的依賴jar包
websocket-api.jar
tomcat-websocket.jar
這樣就可以了
下面是websocket的簡單的實現:
此處“imchat”為訪問路徑,“{id}”為需傳遞的參數,是OnOpen時接收的參數這兩處變量名需要寫一樣,如果是多參數可以后面繼續追加如/imchat/{id}/{name}
@ServerEndpoint(value="/imchat/{id}")
public class ImSocket {
//靜態變量,用來記錄當前在線連接數。應該把它設計成線程安全的。
private static int onlineCount = 0;
//concurrent包的線程安全Set,用來存放每個客戶端對應的MyWebSocket對象。若要實現服務端與單一客戶端通信的話,可以使用Map來存放,其中Key可以為用戶標識
private static CopyOnWriteArraySet<ImSocket> webSocketSet = new CopyOnWriteArraySet<ImSocket>();
//與某個客戶端的連接會話,需要通過它來給客戶端發送數據
private Session session;
/**
* 連接建立成功調用的方法,只在建立連接時調用
* @param session 可選的參數。session為與某個客戶端的連接會話,需要通過它來給客戶端發送數據
*/
@OnOpen
public void onOpen(@PathParam("id") String id,Session session){
this.session = session;
webSocketSet.add(this); //加入set中
addOnlineCount(); //在線數加1
System.out.println("有新連接加入!當前在線人數為" + getOnlineCount());
}
/**
* 連接關閉調用的方法
*/
@OnClose
public void onClose(){
webSocketSet.remove(this); //從set中刪除
subOnlineCount(); //在線數減1
System.out.println("有一連接關閉!當前在線人數為" + getOnlineCount());
}
/**
* 收到客戶端消息后調用的方法,連接后所有交互數據都在此處理
* @param message 客戶端發送過來的消息
* @param session 可選的參數
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("來自客戶端的消息:" + message);
//群發消息
for(ImSocket item: webSocketSet){
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
/**
* 發生錯誤時調用
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error){
System.out.println("發生錯誤");
error.printStackTrace();
}
/**
* 這個方法與上面幾個方法不一樣。沒有用注解,是根據自己需要添加的方法。
* @param message
* @throws IOException
*/
public void sendMessage(String message) throws IOException{
this.session.getBasicRemote().sendText(message);
//this.session.getAsyncRemote().sendText(message);
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
ImSocket.onlineCount++;
}
public static synchronized void subOnlineCount() {
ImSocket.onlineCount--;
}
}
<!DOCTYPE html>
<html>
<head>
<title>Java后端WebSocket的Tomcat實現</title>
</head>
<body>
Welcome<br/><input id="text" type="text"/>
<button onclick="send()">發送消息</button>
<hr/>
<button onclick="closeWebSocket()">關閉WebSocket連接</button>
<hr/>
<div id="message"></div>
</body>
<script type="text/javascript">
var websocket = null;
//判斷當前瀏覽器是否支持WebSocket
if ('WebSocket' in window) {
websocket = new WebSocket("ws://localhost:8080/項目名/imchat/id123");
}
else {
alert('當前瀏覽器 Not support websocket')
}
//連接發生錯誤的回調方法
websocket.onerror = function () {
setMessageInnerHTML("WebSocket連接發生錯誤");
};
//連接成功建立的回調方法
websocket.onopen = function () {
setMessageInnerHTML("WebSocket連接成功");
}
//接收到消息的回調方法
websocket.onmessage = function (event) {
setMessageInnerHTML(event.data);
}
//連接關閉的回調方法
websocket.onclose = function () {
setMessageInnerHTML("WebSocket連接關閉");
}
//監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉窗口,server端會拋異常。
window.onbeforeunload = function () {
closeWebSocket();
}
//將消息顯示在網頁上
function setMessageInnerHTML(innerHTML) {
document.getElementById('message').innerHTML += innerHTML + '<br/>';
}
//關閉WebSocket連接
function closeWebSocket() {
websocket.close();
}
//發送消息
function send() {
var message = document.getElementById('text').value;
websocket.send(message);
}
</script>
</html>
以上是簡單的websocket后端與前端的連接與交互,以上測試通過后咱們寫一下我們聊天的簡單實現
基于上面的認識我們已經能夠實現簡單的前端與websocket服務端的交互,下面我們接著上篇的自己動手實現基于websocket的聊天web聊天功能高仿仿qq
首先我們看一下qq的邏輯,簡單的講就這 三步,登錄---》獲取好友列表---》發送消息給好友;
第一步登錄和獲取好友列表,這個我們有兩種方式去實現,一種是http請求,一種是websocket去實現,考慮到這樣請求websocket會使處理流程變的復雜,所以我們采用http的方式實現,這樣我們得websocket主要用來處理消息轉發 服務,
public class ImMsgModel { private boolean system;//消息類型 private String key;//事件類型// offline離線消息 online在線消息 private String avatar; private String id; private String sign; private String status; private String username; private String name; private String type; private String content; private long timestamp; private String fromid; public boolean isSystem() { return system; } public void setSystem(boolean system) { this.system = system; } public String getKey() { return key; } public void setKey(String key) { this.key = key; } public String getId() { return id; } public void setId(String id) { this.id = id; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } public String getFromid() { return fromid; } public void setFromid(String fromid) { this.fromid = fromid; } public void setAvatar(String avatar) { this.avatar = avatar; } public String getAvatar() { return avatar; } public void setSign(String sign) { this.sign = sign; } public String getSign() { return sign; } public void setStatus(String status) { this.status = status; } public String getStatus() { return status; } public void setUsername(String username) { this.username = username; } public String getUsername() { return username; } public void setName(String name) { this.name = name; } public String getName() { return name; } public void setType(String type) { this.type = type; } public String getType() { return type; } public void setContent(String content) { this.content = content; } public String getContent() { return content; }
@ServerEndpoint(value="/imchat/{id}") public class WebsocketsListener { private static final Set<WebsocketsListener> connections = new CopyOnWriteArraySet<WebsocketsListener>(); private Session session; private String userid; Logger log = null; public WebsocketsListener() { log = Logger.getGlobal(); } @OnOpen public void start(@PathParam(value="id") String id,Session session) { // TODO Auto-generated method stub //log.log(Level.INFO, "打開監聽onOpen"); this.session=session; this.userid=id; connections.add(this); // Redis.use().hmset("userid="+id, hash); System.out.println(id+"用戶session:"+session); } @OnMessage public void incoming(String message) { log.info("------------------"+message); try { ImMsgModel m = JSON.parseObject(message, ImMsgModel.class); System.out.println(m.getFromid()+"發送給"+m.getId()); Session s = getSessionByID(m.getId());//接收者id //消息接收方掉線 if(s==null){ Session s1 = getSessionByID(m.getFromid()); //發送方也不在線 if(s1==null) { log.info( "用戶已掉線線"); }else { //發送消息給消息發送方提示消息接收方掉線 ImMsgModel msg = new ImMsgModel(); msg.setKey("offline"); msg.setSystem(true); msg.setId(m.getId()); msg.setType("friend"); msg.setContent("對方已掉線"); log.info( "對方已掉線"); send2user(JSON.toJSONString(msg), s1); } }else{ //發送消息給消息接收方 m.setId(m.getFromid()); m.setKey("online"); send2user(JSON.toJSONString(m), s); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } private Session getSessionByID(String id) { System.out.println("連接用戶數"+connections.size()); for (WebsocketsListener wb : connections) { //接收對象id對應的session if(id.equals(wb.userid)) { return wb.session; } } return null; } @OnClose public void end() { connections.remove(this); } @OnError public void onError(Throwable t) throws Throwable { log.info( "發生錯誤onError"); t.printStackTrace(); } private void send2user(String msg,Session session){ try { session.getBasicRemote().sendText(msg); } catch (IOException e) { e.printStackTrace(); } } public static void sendAll(String string) { for (WebsocketsListener wb : connections) { try { wb.session.getBasicRemote().sendText(string); } catch (Exception e) { e.printStackTrace(); connections.remove(wb); try { wb.session.close(); } catch (IOException e1) { e1.printStackTrace(); } } } } }
以上完成了websocket的服務前端消息轉發代碼(數據持久化與 redis后面繼續進行補充)
下面我貼一下上篇中講的前端代碼的完整版
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title>layui</title> <meta name="renderer" content="webkit"> <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <link rel="stylesheet" href="js/css/modules/layui.css" media="all"> <!-- 注意:如果你直接復制所有代碼到本地,上述css路徑需要改成你本地的 --> </head> <body> <script src="js/layui.js" charset="utf-8"></script> <!-- 注意:如果你直接復制所有代碼到本地,上述js路徑需要改成你本地的 --> <style> /* img */ i{font-style:normal;} .qq-login{width:430px;height:330px;margin:0 0 -165px -215px;bottom:50%;left:50%;position:fixed;z-index:9999;border-radius:3px;overflow:hidden;box-shadow:0 0 5px #333;background:#ebf2f9 url(js/images/bj/qq-login-bg.jpg) center top no-repeat;display:block;} .login-menu{width:90px;height:30px;top:0;right:0;position:absolute;} .login-menu span{float:left;width:30px;height:30px;background-image:url(js/images/bj/qq-login-bg.jpg);} .login-menu span:hover{background-color:#3a95de;} .login-menu span:nth-child(1){background-position:left center;} .login-menu span:nth-child(2){background-position:-30px center;} .login-menu span:nth-child(3){background-position:-90px center;} .login-menu span:nth-child(3):hover{background-color:#ea4848;} .login-ner{margin-top:182px;float:left;width:100%;height:148px;} .login-left{float:left;width:133px;height:148px;} .login-head{float:left;width:80px;height:80px;border-radius:50%;border:1px solid #ccc;overflow:hidden;margin:12px 11px 0 40px;} .login-head img{width:80px;height:80px;} .login-on{width:194px;height:148px;float:left;} .login-txt{float:left;margin-top:12px;height:60px;width:100%;} .login-txt input{border:1px solid #d1d1d1;float:left;height:30px;padding:0 7px;font-size:12px;width:100%;} .login-txt input:nth-child(1){border-radius:4px 4px 0 0;} .login-txt input:nth-child(2){border-radius:0 0 4px 4px;margin-top:-1px;} .login-xuan{width:100%;float:left;height:14px;line-height:14px;margin-top:8px;} .login-xuan input{width:14px;height:14px;float:left;} .login-xuan i{float:left;padding-left:4px;} .login-right{width:103px;height:60px;float:left;margin-top:12px;} .login-right a{float:left;padding-left:10px;width:90%;color:#2786e4;line-height:30px;text-indent:10px;} .login-but{width:100%;height:30px;margin:13px 0;float:left;background:#09a3dc;color:#fff;text-align:center;line-height:30px;border-radius:4px;font-size:14px; cursor:context-menu;} .login-menu span { float: left; width: 30px; height: 30px; background-image: url(js/images/bj/wins.png); } .login-tips{line-height:40px;width:300px;padding:10px;color: white;top:0;left:0;position:absolute;} </style> <script id="login_html" type="text/html"> <div class="qq-login"> <div class="login-tips" id="login-tips"></div> <div class="login-menu"> <span></span><span></span><span class="login-close"></span> </div> <div class="login-ner"> <div class="login-left"> <div class="login-head"><img src="js/css/modules/layim/skin/4.jpg"></div> </div> <div class="login-on"> <div class="login-txt"><input type="text" id="username" placeholder="QQ號碼/手機/郵箱"><input id="password" type="password" placeholder="密碼"></div> <div class="login-xuan"><span class="fl"><input type="checkbox"><i>記住密碼</i></span><span class="fr"><input type="checkbox"><i>自動登錄</i></span></div> <div class="login-but" id="login-but">安全登錄</div> </div> <div class="login-right"> <a target="_blank">注冊賬號</a><a target="_blank">找回密碼</a> </div> </div> </div> </script> <script> layui.use('layim', function(){ var layim = layui.layim,id; $ = layui.jquery, layer.open({ title:false ,type: 1 ,offset: 'auto' //具體配置參考:http://www.layui.com/doc/modules/layer.html#offset ,content: login_html.innerHTML ,btn: false ,shadeClose: false ,closeBtn: 0 ,moveType: 0 ,move: '.login-head' ,btnAlign: 'r' //按鈕居中 ,shade: 0 //不顯示遮罩 }); //綁定登陸事件 $(document).on('click', '#login-but', function(data) { login(); }); var tips= $('#login-tips'); function login(){ tips.html("正在登陸……"); var un= $("#username").val(); var ps= $("#password").val(); var d={"m":"login","username":un,"password":ps} $.ajax({ type:"POST", url:"../chat", dataType:"json", data:d, success:function(data){ if(data.code==20000){ id=data.data.id tips.html("登陸成功"); chartSetting(); connection(); }else{ tips.html("登陸失敗請重試!"+data.msg); } }, error:function(jqXHR){ tips.html("發生錯誤"+jqXHR.status); } }); } function chartSetting(){ //基礎配置 layim.config({ //初始化接口 init: { url: 'chat?m=list&id='+id ,data: {} } //查看群員接口 ,members: { url: 'chat?m=getMembers&id='+id ,data: {} } ,uploadImage: { url: 'uploadv2?filepath=' //(返回的數據格式見下文) ,type: '' //默認post } ,uploadFile: { url: 'uploadv2?filepath=' //(返回的數據格式見下文) ,type: '' //默認post } ,isAudio: true //開啟聊天工具欄音頻 ,isVideo: true //開啟聊天工具欄視頻 //擴展工具欄 ,tool: [{ alias: 'code' ,title: '代碼' ,icon: '' }] ,brief: false //是否簡約模式(若開啟則不顯示主面板) ,title: '消息' //自定義主面板最小化時的標題 ,right: '10px' //主面板相對瀏覽器右側距離 ,minRight: '90px' //聊天面板最小化時相對瀏覽器右側距離 ,initSkin: '3.jpg' //1-5 設置初始背景 ,skin: ['js/css/modules/layim/skin/6.jpg', 'js/css/modules/layim/skin/1.jpg'] //新增皮膚 ,isfriend: true //是否開啟好友 ,isgroup: true //是否開啟群組 ,min: false //是否始終最小化主面板,默認false ,notice: true //是否開啟桌面消息提醒,默認false ,voice: true //聲音提醒,默認開啟,聲音文件為:default.mp3 ,msgbox: 'msgbox.html' //消息盒子頁面地址,若不開啟,剔除該項即可 ,find: 'find.html' //發現頁面地址,若不開啟,剔除該項即可 ,chatLog: 'chatlog.html' //聊天記錄頁面地址,若不開啟,剔除該項即可 }); } //監聽在線狀態的切換事件 layim.on('online', function(status){ layer.msg(status); }); //演示自動回復 var autoReplay = [ '您好,我現在有事不在,一會再和您聯系。', '你沒發錯吧?face[微笑] ', '洗澡中,請勿打擾,偷窺請購票,個體四十,團體八折,訂票電話:一般人我不告訴他!face[哈哈] ', '你好,我是主人的美女秘書,有什么事就跟我說吧,等他回來我會轉告他的。face[心] face[心] face[心] ', 'face[威武] face[威武] face[威武] face[威武] ', '<(@ ̄︶ ̄@)>', '你要和我說話?你真的要和我說話?你確定自己想說嗎?你一定非說不可嗎?那你說吧,這是自動回復。', 'face[黑線] 你慢慢說,別急……', '(*^__^*) face[嘻嘻] ,是賢心嗎?' ]; //監聽在線狀態的切換事件 layim.on('online', function(status){ layer.msg(status); }); //監聽簽名修改 layim.on('sign', function(value){ layer.msg(value); }); //監聽自定義工具欄點擊,以添加代碼為例 layim.on('tool(code)', function(insert){ layer.prompt({ title: '插入代碼 - 工具欄擴展示例' ,formType: 2 ,shade: 0 }, function(text, index){ layer.close(index); insert('[pre class=layui-code]' + text + '[/pre]'); //將內容插入到編輯器 }); }); //監聽layim建立就緒 layim.on('ready', function(res){ //console.log(res.mine); layim.msgbox(5); //模擬消息盒子有新消息,實際使用時,一般是動態獲得 }); //監聽發送消息 layim.on('sendMessage', function(data){ var To = data.to; var Me = data.mine; if(To.type === 'friend'){ layim.setChatStatus('<span style="color:#FF5722;">對方正在輸入。。。</span>'); } if(To.id==Me.id){ alert("無法和自己發起聊天"); return; }else{ var data={ username: Me.username //消息來源用戶名 ,avatar: Me.avatar //消息來源用戶頭像 ,id: To.id //消息的來源ID(如果是私聊,則是用戶id,如果是群聊,則是群組id) ,type:To.type //聊天窗口來源類型,從發送消息傳遞的to里面獲取 ,content: Me.content //消息內容 ,cid: 0 //消息id,可不傳。除非你要對消息進行一些操作(如撤回) ,mine: false //是否我發送的消息,如果為true,則會顯示在右方 ,fromid:Me.id //消息的發送者id(比如群組中的某個消息發送者),可用于自動解決瀏覽器多窗口時的一些問題 ,timestamp:new Date().getTime() //服務端時間戳毫秒數。注意:如果你返回的是標準的 unix 時間戳,記得要 *1000 }; //模擬系統消息 websocket.send(JSON.stringify(data)); layim.setChatStatus('<span style="color:#FF5722;">在線</span>'); } }); //監聽查看群員 layim.on('members', function(data){ //console.log(data); }); //監聽聊天窗口的切換 layim.on('chatChange', function(res){ var type = res.data.type; console.log(res.data.id) if(type === 'friend'){ //模擬標注好友狀態 layim.setChatStatus('<span style="color:#FF5722;">在線</span>'); } else if(type === 'group'){ //模擬系統消息 layim.getMessage({ system: true ,id: res.data.id ,type: "group" ,content: '模擬群員'+(Math.random()*100|0) + '加入群聊' }); } }); function connection(){ tips.html("開始連接服務……"); if('WebSocket' in window){ websocket = new WebSocket("ws://"+sy()+"/imchat/"+id); }else{ tips.html("不支持websocket"); } //連接發生錯誤的回調方法 websocket.onerror = function(ev,data){ tips.html("連接發生錯誤的回調方法"); }; //連接成功建立的回調方法 websocket.onopen = function(e){ tips.html(""); }; //接收到消息的回調方法 websocket.onmessage = function(event){ // layim.getMessage(event.data); var json=JSON.parse(event.data); console.log("接收信息:"); console.log(event.data); if(json.key=="offline"){ //用戶離線 layim.setFriendStatus(json.id, 'offline'); layim.setChatStatus('<span style="color:gray;">離線</span>'); layer.msg(json.content+"無法接收到消息", { icon: 1 }); }else if(json.key=="online"){ //接收在線消息 //制造好友消息 layim.setFriendStatus(json.id, 'online'); layim.setChatStatus('<span style="color:#FF5722;">在線</span>'); layim.getMessage(json); } }; //連接關閉的回調方法 websocket.onclose = function(event){ //alert('連接關閉的回調方法'); tips.html("連接已關閉,嘗試重連……"); disConnect(); }; //監聽窗口關閉事件,當窗口關閉時,主動去關閉websocket連接,防止連接還沒斷開就關閉窗口,server端會拋異常。 window.onbeforeunload = function(){ websocket.close(); }; } //檢查鏈接,短線重連 var disConnect = function(){ setTimeout(function(){ connection(); },5000); } //關閉連接 function closeWebSocket(){ tips.html("關閉closeWebSocket"); websocket.close(); } function sy(){ var curWwwPath = window.document.location.href; var pathName = window.document.location.pathname; var pos = curWwwPath.indexOf(pathName); var localhostPaht = curWwwPath.substring(0,pos); var projectName = pathName.substring(0,pathName.substr(1).indexOf('/')+1); var ip=window.location.host; var prot=window.location.port; return (ip + projectName); } }); </script> </body> </html>
結合以上服務端代碼和前端代碼可以實現基本的聊天功能,本篇沒涉及到登錄接口和用戶列表接口,下篇再做補充。需要的同學關注一下下篇。有問題歡迎留言指正
HTML 的 tabindex 屬性開發過程中一般不會使用到,最近開發中有個需求兼顧富交互,便總結了一下。本篇文章同時收錄在我的【前端知識點】中,Github鏈接請點擊閱讀原文直達,歡迎 Star
兼容性:Safari不支持!
在我們日常使用網頁的過程中,可以通過鍵盤控制一些元素的聚焦,從而達到便捷訪問的目的
element 分為 focusable 和 非focusable ,如果使用了tabindex就可以改變相關的行為
在HTML中有6個元素默認支持聚焦:
以上的元素默認都可以使用 Tab 鍵,以及 JS focus() 方法聚焦
document.querySelector("a").focus();
使用 tab鍵 進行聚焦元素時,聚焦的順序等于元素在代碼中的出現先后順序,當我們進行富交互優化時,就需要用到 tabindex 這個屬性來幫助我們進行更好用戶體驗的優化了
①元素是否能聚焦:通過鍵盤這類輸入設備,或者通過 JS focus() 方法
②元素什么時候能聚焦:在用戶通過鍵盤與頁面交互時
通俗來說:就是當用戶使用鍵盤時,tabindex用來定位html元素,即使用tab鍵時焦點的順序。
tabindex理論上可以使用在幾乎所有元素上
tabindex 有三個值:0,-N(通常是-1),N(正值)
tabindex 決定聚焦順序
// HTML
<button type="button" tabindex="1">tabindex === 1</button>
<button type="button" tabindex="999">tabindex === 999</button>
<button type="button" tabindex="0">tabindex === 0</button>
// HTML
<button type="button" tabindex="0">tabindex === 0</button>
<button type="button" tabindex="1">tabindex === 1</button>
<button type="button" tabindex="999">tabindex === 999</button>
<button type="button" tabindex="0">tabindex === 0</button>
tabindex 決定是否聚焦
// HTML
<button type="button">未設置tabindex</button>
<button type="button" tabindex="-1">tabindex === -1</button>
<button type="button" tabindex="0">tabindex === 0</button>
<button type="button" tabindex="1">tabindex === 1</button>
tabindex 與JS編程聚焦
// HTML
<button type="button" @click="clickBtn()">點擊讓DIV聚焦</button>
<div id="FocusDiv" ref="FocusDiv" tabindex="-1">這是一個div</div>
// JS
clickBtn: function() {
document.getElementById('FocusDiv').focus();
}
針對自定義標簽進行富交互優化
針對特定節點禁止聚焦操作
復雜列表控制聚焦順序
*請認真填寫需求信息,我們會在24小時內與您取得聯系。