整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          探秘網頁性能提升利器之CSS硬件加速

          天我們將為大家介紹一個令網頁性能大幅提升的神奇技術——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 瀏覽器的渲染流程

          一個完整的渲染步驟大致可總結為如下:

          • 渲染進程將HTML內容轉換為能夠讀懂的DOM樹結構。
          • 渲染引擎將CSS樣式表轉化為瀏覽器可以理解的 styleSheets ,計算出DOM節點的樣式。
          • 創建布局樹,并計算元素的布局信息。
          • 對布局樹進行分層,并生成分層樹。
          • 為每個圖層生成繪制列表,并將其提交到合成線程。
          • 合成線程將圖層分成圖塊,并在光柵化線程池中將圖塊轉換成位圖。
          • 合成線程發送繪制圖塊命令DrawQuad給瀏覽器進程。
          • 瀏覽器進程根據DrawQuad消息生成頁面,并顯示到顯示器上。

          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

          基于Tomcat9的websocket服務實現

          首先創建一個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;
          	 }
          

          第二步我們對websocket進行封裝,此處消息實體根據layim前端封裝,需要了解詳情的請移步layui官網訪問layim模塊

          @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 的作用

          HTML 的 tabindex 屬性開發過程中一般不會使用到,最近開發中有個需求兼顧富交互,便總結了一下。本篇文章同時收錄在我的【前端知識點】中,Github鏈接請點擊閱讀原文直達,歡迎 Star

          兼容性:Safari不支持!

          閱讀本文您將收獲

          • tabindex的作用
          • tabindex的使用
          • 如何利用 tabindex 創造更好的用戶體驗

          前言

          在我們日常使用網頁的過程中,可以通過鍵盤控制一些元素的聚焦,從而達到便捷訪問的目的

          element 分為 focusable 和 非focusable ,如果使用了tabindex就可以改變相關的行為

          在HTML中有6個元素默認支持聚焦:

          • 帶 href 屬性的 <a> 標簽
          • 帶 href 屬性的 <link> 標簽
          • <button></button> 標簽
          • <input /> 標簽 (排除帶有 type="hidden" 屬性的)
          • <select></select> 標簽
          • <textarea></textarea> 標簽

          以上的元素默認都可以使用 Tab 鍵,以及 JS focus() 方法聚焦

          document.querySelector("a").focus();

          使用 tab鍵 進行聚焦元素時,聚焦的順序等于元素在代碼中的出現先后順序,當我們進行富交互優化時,就需要用到 tabindex 這個屬性來幫助我們進行更好用戶體驗的優化了

          tabindex的作用

          ①元素是否能聚焦:通過鍵盤這類輸入設備,或者通過 JS focus() 方法

          ②元素什么時候能聚焦:在用戶通過鍵盤與頁面交互時

          通俗來說:就是當用戶使用鍵盤時,tabindex用來定位html元素,即使用tab鍵時焦點的順序。

          tabindex的范圍

          tabindex理論上可以使用在幾乎所有元素上

          • tabindex 理論上可以用在幾乎所有元素上,不管這個元素默認是否支持聚焦

          tabindex 有三個值:0,-N(通常是-1),N(正值)

          • tabindex=0,該元素可以用tab鍵獲取焦點
            • 且訪問的順序是按照元素在文檔中的順序來focus,即使采用了浮動改變了頁面中顯示的順序,依然是按照html文檔中的順序來定位
          • tabindex<=-1,該元素用tab鍵獲取不到焦點,但是可以通過js獲取
            • 這樣就便于我們通過js設置上下左右鍵的響應事件來focus
            • 取值 -1~-999 之間沒有區別,但為了可讀性和一致性考慮,推薦使用 -1
          • tabindex>=1,該元素可以用tab鍵獲取焦點,而且優先級大于tabindex=0
            • 不過在tabindex>=1時,數字越小,越先定位到;
            • 如果多個元素擁有相同的 tabindex ,他們的相對順序按照他們在當前DOM中的先后順序決定

          tabindex的使用

          tabindex 決定聚焦順序

          • 可聚焦元素中,正整數數值越大,順序越往后,正整數數值的節點順序比0值的節點靠前
          • 代碼:
          // 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>
          • 效果:
          • 可聚焦元素中,相同 tabindex 數值的節點,根據 DOM節點 先后順序決定聚焦順序
          • 代碼:
          // 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 決定是否聚焦

          • 節點的 tabindex 設置為 -1 時,當前節點使用 tab鍵 不能聚焦
          • 代碼:
          // 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編程聚焦

          • 通過 tabindex 結合JS可以讓默認不支持聚焦的節點進行聚焦,tabindex 為不超出范圍的任何整數值都可以
          • 代碼:
          // HTML
          <button type="button" @click="clickBtn()">點擊讓DIV聚焦</button>
          <div id="FocusDiv" ref="FocusDiv" tabindex="-1">這是一個div</div>
          
          // JS
          clickBtn: function() {
              document.getElementById('FocusDiv').focus();
          }
          • 效果:

          如何利用 tabindex 創造更好的用戶體驗

          針對自定義標簽進行富交互優化

          • 我們在創建一個自定義的標簽時,如果默認行為中不包含聚焦事件,我們可以使用 tabindex 為它增加聚焦功能,從而可以像很多可聚焦節點一樣進行順次焦點聚焦了

          針對特定節點禁止聚焦操作

          • 某些浮層及上層節點,如 toast組件、模態框、側邊彈出信息等,我們不希望節點被用戶聚焦捕獲,可以將節點的 tabindex 設置為 -1,就能避免這一問題

          復雜列表控制聚焦順序

          • 一些復雜的樹形結構或者列式結構,如果需要用戶操作順序按照我們預想的書序進行聚焦,可以利用tabindex 值的大小來進行處理。

          主站蜘蛛池模板: 亚洲日韩激情无码一区| 一区二区三区视频网站| 国模无码一区二区三区不卡| 精品成人一区二区三区四区| 中文字幕在线无码一区| 亚洲日韩精品国产一区二区三区 | 一区二区三区中文| 国产精品女同一区二区| 天码av无码一区二区三区四区 | 亚洲欧洲一区二区三区| 久久91精品国产一区二区| 国产熟女一区二区三区五月婷| 中文字幕无线码一区| 动漫精品第一区二区三区| 麻豆一区二区免费播放网站| 亚洲色欲一区二区三区在线观看| 国产精品亚洲一区二区在线观看| 日本片免费观看一区二区| 国产精品熟女一区二区| 中文字幕一区二区人妻性色| 中文字幕无码免费久久9一区9| 亚洲一区日韩高清中文字幕亚洲| 国产精品成人一区二区三区| 国产精品高清一区二区三区不卡 | 久久国产精品一区免费下载| 亚洲一区二区三区国产精品| 国产一区二区三区播放| 福利一区福利二区| 日本一区高清视频| 一区二区三区在线免费 | 性色AV一区二区三区无码| 在线电影一区二区三区| 亚洲日本中文字幕一区二区三区| 亚洲.国产.欧美一区二区三区| 激情综合一区二区三区| 国产一区二区三区不卡AV| 国产成人av一区二区三区在线观看| 一区二区日韩国产精品| 亚洲第一区香蕉_国产a| 国产av一区二区精品久久凹凸| 成人无码精品一区二区三区|