整合營銷服務商

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

          免費咨詢熱線:

          js提取html富文本字符串某個標簽的所有屬性

          js提取html富文本字符串某個標簽的所有屬性

          HTML中,標簽可以包含各種屬性,用于定義標簽的特性和行為。例如,img標簽可以有src屬性來指定要顯示的圖像的URL,a標簽可以有href屬性來指定鏈接的目標地址。通過獲取標簽的屬性值,我們可以獲取到這些額外的信息,然后根據需要進行處理。

          定義方法

          /**
           * 提取富文本字符串某個標簽的所有屬性
           * @param {String} str 要提取的html富文本
           * @param {Object} tagName 要提取的標簽名稱
           * @param {Object} attrName 要提取的屬性名稱
           * @returns {Array} list 屬性列表
           */
          function getStrTagAttribute(str,tagName,attrName){
          	let attributeList=[]
          	const re=new RegExp(`<${tagName} [^>]*${attrName}=['"]([^'"]+)[^>]*>`,'g')
          	str.replace(re, (match, capture)=> {
          	  attributeList.push(capture);
          	});
          	return attributeList;
          }

          使用方法

          域請求:

          現代的所有的瀏覽器都遵守同源策略,所謂的源指的就是一個域名、一個服務器,同源是指兩個腳本擁有相同的域名,不同源指的是不同的域名,同源策略指的是,一個源的腳本不能讀取或操作其他源的http響應和cookie,也就是出于安全方面的考慮,頁面中的JavaScript無法訪問其他服務器上的數據,這就是所謂的“同源策略”。
          同源是指協議、域名和端口都一致。

          不同源限制的內容:

          • Cookie、LocalStorge、IndexedDb等存儲性內容;
          • DOM節點;
          • Ajax請求;

          跨域請求時,不同域的服務器是返回了數據的,只不過瀏覽器攔截了響應數據;同時也說明了跨域并不能完全阻止CSRF,因為請求畢竟是發出去了;

          CORS(Cross-Origin Response Sharing)跨域資源共享:

          通過XHR實現Ajax通信的主要限制,是跨域安全策略;默認情況下,只能訪問同一個域中的資源,這種安全策略可以預防某些惡意行為,如:

          var xhr=new XMLHttpRequest();
          xhr.onload=function(){
          console.log(xhr.responseText);
          }
          xhr.open("GET", "https://www.zeronetwork.cn/study/index.html");
          xhr.send(null);

          其拋出了CORS policy異常;

          XHR2規范了在通過HTTP響應中如何選擇合適的CORS(Cross-Origin Response Sharing,跨域資源共享)去跨域訪問資源;其定義了在必須訪問跨源資源時,瀏覽器與服務器應該如何溝通;CORS的基本思想是,就是使用自定義的HTTP頭部讓瀏覽器與服務器進行溝通,從面決定請求或響應是否應該成功;

          比如一個簡單的使用GET或POST發送的請求,默認情況下它沒有自定義的頭,但一般會包括一個Origin請求頭,其中包含請求頁面的源信息(協議、域名和端口),以便服務器根據這個頭部信息來決定是否給予響應,如Origin頭部示例:

          Origin: https://www.zeronetwork.cn

          如果服務器認為這個請求可以接受,就在響應的Access-Control-Allow-Origin([??la?])頭中回發相同的源信息(如果是公共資源,可以回發”*”),例如:

          Access-Control-Allow-Origin: https://www.zeronetwork.cn

          如果沒有這個響應頭,或者有這個響應頭但與請求Origin頭信息不匹配,瀏覽器就會駁回請求;反之,瀏覽器會處理請求;

          實現跨域:

          IE和標準瀏覽器已經實現了各自的跨域解決方案;

          標準瀏覽器對CORS的實現:

          在標準瀏覽器中,客戶端在使用Ajax跨域請求時,拋出異常,不能訪問;如:

          var xhr=new XMLHttpRequest();
          xhr.onreadystatechange=function(){
          if (xhr.readyState==4 && xhr.status==200) {
          console.log(xhr.responseText);
          }
          }
          xhr.open("GET","https://www.b.com/normal/example.json");
          xhr.send(null);

          被請求的服務端需要設置Access-Control-Allow-Origin響應頭,以便于瀏覽器識別它是否為可信源。

          例如,在Apache服務器中,在服務器的配置中添加如下設置:

          Header set Access-Control-Allow-Origin 'origin-list'

          對于Nginx,設置此http頭的命令是:

          add_header 'Access-Control-Allow-Origin' 'origin-list'

          或者使用.htaccess文件配置,如:

          <IfModule mod_headers.c>
          Header set Access-Control-Allow-Origin "*"
          </IfModule>
          order allow,deny
          allow from all

          應用:

          xhr.open("GET","https://www.b.com/cors/example.json");

          單獨為某個后端程序設置響應頭,例如b.com/cors.php:

          <?php
          header("Access-Control-Allow-Origin: *");
          echo "跨域訪問b.com/cors.php";

          無論同源請求還是跨源請求都使用相同的接口,因此對地本地資源,最好使用相對URL,在訪問遠程資源時再使用絕對URL;這樣做能消除歧義,避免出現限制訪問頭部或本地cookie信息等問題。

          IE對CORS的實現:

          微軟在IE8中引入了XDR(XDomainRequest)對象,其與XHR類似,其可直接用于發起安全的跨域請求,實現安全可靠的跨域通信;

          var xdr=new XDomainRequest();
          console.log(xdr);

          IE11和標準瀏覽器并不支持;

          XDR對象的使用方法與XHR對象非常相似,兩者擁有幾乎相同的屬性和方法,也是調用open()方法,再調用send()方法;但與XHR對象的open()方法不同,XDR對象的open()方法只接收兩個參數:請求的類型和URL;如:

          var xdr=new XDomainRequest();
          console.log(xdr);
          // xdr.open("GET", "http://www.c.com/nocors.php");
          xdr.open("GET", "example.php");
          xdr.onload=function(){
          console.log(xdr.responseText);
          }
          xdr.send();

          此時,不管是跨域的還是同源的都不允許訪問,拋出“在 Access-Control-Allow-Origin 標頭中未找到源”;

          XDR對象的安全機制中部分實現了CORS,后端也需要設置 Access-Control-Allow-Origin響應頭,如c.com/cors.php:

          <?php
          header("Access-Control-Allow-Origin: *");
          echo "設置了ACAO響應頭";

          請求端:

          xdr.open("GET", "http://www.c.com/cors.php");

          XDR對象屬性和事件:

          請求返回后,會觸發onload事件,響應的數據也會保存在responseText屬性中,響應的MIME類型保存在contentType屬性中,如:

          var xdr=new XDomainRequest();
          xdr.onload=function(){
          console.log(xdr.contentType); // application/json
          console.log(xdr.responseText);
          }
          xdr.open("post","http://www.c.com/cors/example.json");
          xdr.send(null);

          例如再請求一個同源的contentType.php:

          header("Access-Control-Allow-Origin: *");
          header("Content-Type: application/json");
          echo '{"username":"王唯","age":18,"sex":true}';

          在接收到響應后,只能訪問響應的原始文本,不能確定響應的狀態代碼(也就是它沒有status屬性);而且,只要響應有效就會觸發onload事件,如果失敗就會觸發error事件,但除了錯誤本身之外,沒有其他信息可以確定請求是否成功,所以唯一能夠確定的就只有請求未成功;

          要檢測錯誤,如要指定error事件處理程序,如:

          xdr.onerror=function(){
          console.log("出現錯誤");
          }

          由于導致XDR請求失敗的因素很多,因此,最好通過error事件處理程序來捕獲該事件,否則,即使請求失敗也不會有任何提示。

          在請求返回前調用abort()方法可以終止請求,如:

          xdr.abort();

          與XHR對象一樣,XDR也支持timout屬性和ontimeout事件,如:

          xdr.timeout=1000;
          xdr.ontimeout=function(){
          console.log("請求超過1秒");
          };

          onprogress事件:

          應該始終定義 xdr.onprogress 事件,即使它是一個空函數,否則 XDomainRequest 對于重復請求,可能不會觸發 onload 事件;

          xdr.onprogress=function(event){
          console.log(event);
          };

          XDR與XHR的不同之外:

          1、必須使用 HTTP 或 HTTPS 協議訪問目標 URL:
          因為XDR對象依賴于一個HTTP響應頭來實現訪問控制,所以它要求目標URL符合HTTP或HTTPS 協議,以便于XDR對象檢驗響應頭;
          2、只支持GET和POST請求;
          3、不能設置自定義請求頭信息,也不能訪問響應頭部信息;
          4、只支持text/plain作為請求頭Content-Type的取值:在XDR中,不管是GET還是POST請求,Content-Type被限制成“text/plain”,所以服務端不會把請求主體數據解析成鍵值對,即不能從參數中獲取到POST的數據,只能讀取流數據,需要其自行解析,如:

          var xdr=new XDomainRequest();
          xdr.open("POST", "xdrpost.php");
          xdr.onload=function(){
          console.log(xdr.responseText);
          };
          var param="username=wangwei&age=18";
          xdr.send(param);

          xdrpost.php:

          <?php
          header("Access-Control-Allow-Origin: *");
          $content=file_get_contents("php://input");
          // echo $content; // username=王 唯&age=18
          $arr=explode("&", $content);
          foreach ($arr as $value) {
          $v=explode("=", $value);
          echo "key:$v[0], value:$v[1] \r\n";
          }

          5、身份驗證和cookie不能和請求一起發送,也不會隨響應返回;
          6、請求的URL必須和被請求的URL采用相同的協議:兩者的協議必須統一,要么是HTTP,要么是HTTPS;
          7、所有XDR請求都是異步執行的,不能用它來創建同步請求;

          Preflighted Requests:

          CORS通過一種叫做Prelighted Requests的透明服務器驗證機制支持開發人員使用自定義的頭部、GET或POST之外的請求方式,以及不同類型的主體內容;
          在使用下列高級選項來發送請求時,就會向服務器發送一個Preflight請求,這種請求使用OPTIONS方式,發送下列頭部:

          • Origin:與簡單的請求相同;
          • Access-Control-Request-Method:請求自身使用的方法;
          • Access-Control-Request-Headers:(可選)自定義的頭部信息,多個頭部以逗號分隔;

          以下是一個帶有自定義頭部customHeader,并使用POST方法發送的數據,如:

          • Origin: http://www.zeronetwork.cn
          • Access-Control-Request-Method: POST
          • Access-Control-Request-Headers: customHeader

          發送這個請求后,服務器端可以決定是否允許這種類型的請求,其通過在響應中發送如下頭部與瀏覽器進行溝通:

          • Access-Control-Allow-Origin:與簡單的請求相同;
          • Access-Control-Allow-Methods:允許的方法,多個方法以逗號分隔;
          • Access-Control-Allow-Headers:允許的頭部,多個頭部以逗號分隔;
          • Access-Control-Max-Age:應該將這個Preflight請求緩存多長時間(以秒表示)

          例如,允許任何源、POST請求方式、自定義頭customHeader以及請求的緩存時間:

          • Access-Control-Allow-Origin: https://www.zeronetwork.cn
          • Access-Control-Allow-Methods: POST
          • Access-Control-Allow-Headers: customHeader
          • Access-Control-Max-Age: 1728000

          如:

          var xhr=new XMLHttpRequest();
          xhr.onload=function(){
          console.log(xhr.responseText);
          }
          xhr.open("OPTIONS","https://www.b.com/flighted.php",true);
          xhr.setRequestHeader("customHeader", "customValue");
          xhr.send(null);

          后端flighted.php:

          <?php
          header("Access-Control-Allow-Origin: *");
          header("Access-Control-Allow-Headers: *");
          header("Access-Control-Allow-Methods: *");
          header("Access-Control-Max-Age: 1728000");
          echo "有Headers、Methods頭信息";

          Preflight請求結束后,結果將按照響應中指定的時間緩存起來;

          帶憑據的請求:

          默認情況下,跨域請求不提供憑據(cookie、HTTP認證及客戶端SSL證明等);
          通過將XHR對象的withCredentials屬性設置為true,可以指定某個跨域請求應該發送憑據(授權信息);如:

          xhr.withCredentials=true;
          xhr.send(null);

          當使用帶有憑據的請求時,不能把Access-Control-Allow-Origin設為*,并且Access-Control-Allow-Origin只能設置一個域,不能是多個,否則會拋出異常;

          后端c.com/credentials.php:

          header("Access-Control-Allow-Origin: http://www.a.com");
          echo "c.com/example.php,已經設置了ACAO";

          如果服務端接受帶憑據的請求,必須設置Access-Control-Allow-Credentials: true響應頭;

          如后端c.com/ credentials.php:

          header("Access-Control-Allow-Origin: http://www.a.com");
          header("Access-Control-Allow-Credentials: true");
          echo "設置了Origin,也設置了Credentials";
          echo json_encode($_COOKIE);

          如果在同源下配置withCredentials,無論配置true還是false,效果都會相同,且會一直提供憑據信息;另外,同時還可以發送自定義請求頭,如后端credentials.php:

          <?php
          header("Access-Control-Allow-Origin: http://www.a.com");
          header("Access-Control-Allow-Credentials: true");
          header("Access-Control-Allow-Headers: customHeader");
          echo "設置了Origin,也設置了Credentials";
          echo json_encode($_COOKIE);

          服務端還可以在Preflight響應中發送這個HTTP頭部,但不能把Access-Control-Allow-Headers設為*;

          跨瀏覽器的CORS:

          即使瀏覽器對CORS的支持程度并不一致,但所有瀏覽器都支持簡單的請求(非Preflight和不帶憑據的請求),因此有必要實現一個跨瀏覽器的方案:檢測XHR是否支持CORS的最簡單方式,就是檢查是否存在withCredentials屬性,再結合檢測XDomainRequest對象是否存在,就可以兼顧所有瀏覽器了,如:

          function createCORSRequest(method, url, withCredentials){
          var xhr=new XMLHttpRequest();
          if ("withCredentials" in xhr) {
          xhr.open(method, url);
          xhr.withCredentials=withCredentials;
          }else if(typeof XDomainRequest !="undefined"){
          xhr=new XDomainRequest();
          xhr.open(method, url);
          }else{
          xhr=null;
          }
          return xhr;
          }
          var request=createCORSRequest("GET", "https://www.b.com/credentials.php", true);
          if(request){
          request.onload=function(){
          console.log(request.responseText);
          };
          request.send(null);
          }

          示例:使用HEAD和CORS請求鏈接的詳細信息,如:

          var supportsCORS=(new XMLHttpRequest).withCredentials !=undefined;
          var links=document.getElementsByTagName("a");
          for(var i=0; i<links.length; i++){
          var link=links[i];
          if(!link.href) continue;
          if(link.title) continue;
          if(link.host !==location.host || link.protocol !=location.protocol){
          link.title="站外鏈接";
          if(!supportsCORS) continue;
          }
          if(link.addEventListener)
          link.addEventListener("mouseover", mouseoverHandler, false);
          else
          link.attachEvent("onmouseover", mouseoverHandler);
          }
          function mouseoverHandler(e){
          var link=e.target || e.srcElement;
          var url=link.href;
          var xhr=new XMLHttpRequest();
          xhr.open("HEAD", url);
          xhr.onreadystatechange=function(){
          if(xhr.readyState !==4) return;
          if(xhr.status==200){
          var type=xhr.getResponseHeader("Content-Type");
          var size=xhr.getResponseHeader("Content-Length");
          var date=xhr.getResponseHeader("Last-Modified");
          link.title="類型:" + type + "\n" +
          "大小:" + size + "\n" +
          "時間:" + date;
          }else{
          if(!link.title)
          link.title="獲取不到詳細信息:\n" +
          xhr.status + " " + xhr.statusText;
          }
          };
          xhr.send(null);
          if(link.removeEventListener)
          link.removeEventListener("mouseover", mouseoverHandler, false);
          else
          link.detachEvent("onmouseover", mouseoverHandler);
          }

          HTML:

          <a href="https://www.zeronetwork.cn/edu/">edu</a>
          <a href="https://www.zeronetwork.cn/study/">study</a>
          <a href="http://www.a.com/ demo.html">a.com</a>
          <a>no href</a>
          <a href="https://www.apple.com" title="baidu">apple.com</a>
          <a href="https://cn.bing.com">bing</a>

          其它跨域技術:

          雖然CORS技術已經無處不在,但在CORS出現之前,就已經存在一些跨域的技術了,雖然這些技術應用起來有些麻煩,但它們絕大部分不需要修改服務器端代碼,所以直到現在這些技術仍然被廣泛使用;

          后端代理方式:

          這種方式可以解決所有跨域問題,也就是將本域的后端程序作為代理,每次對其它域的請求都轉交給該代理程序,其通過模擬http請求去訪問其它域,再將返回的結果返回給前端,這樣做的好處是,無論訪問的是文檔、還是JS文件都可以實現跨域;
          例如,b.com/data.php響應JSON字符串:

          <?php
          $json_str='{"name":"wagwei","sex":true,"age":18}';
          echo $json_str;

          a.com/getdata.php服務端獲取b.com/data.php響應:

          <?php
          // 創建cURL資源
          $ch=curl_init();
          // 設置URL和相應的選項
          curl_setopt($ch, CURLOPT_URL, "http://www.b.com/data.php");
          curl_setopt($ch, CURLOPT_HEADER, 0);
          // 抓取URL并把它傳遞給瀏覽器
          curl_exec($ch);
          // 關閉cURL資源,并釋放系統資源
          curl_close($ch);

          a.com/data.html使用Ajax請求同源的getdata.php:

          var xhr=new XMLHttpRequest();
          xhr.open("GET", "getdata.php");
          xhr.onreadystatechange=function(){
          if(xhr.readyState==4 && xhr.status==200){
          console.log(xhr.responseText);
          }
          };
          xhr.send(null);

          基于iframe實現跨域:

          基于iframe實現的跨域要求兩個域屬于同一個根域,如:www.a.com和b.a.com其使用同一協議(例如都是 http)和同一端口(例如都是80),此時在兩個頁面中同時設置document.domain為同一個主域,就實現了同域,從而可以實現通信;如b.a.com中的iframe.html:

          <h1>iframe</h1>
          <img src="images/hu.png" />
          <script>
          document.domain="a.com";
          function show(msg){
          alert("收到的:" + msg);
          }
          if(parent.parentFun){
          parent.parentFun();
          }
          </script>

          www.a.com主頁面為:

          <script>
          function parentFun(){
          alert("parentFun");
          }
          </script>
          <iframe src="http://b.a.com/iframe.html" id="myframe"></iframe>
          <script>
          var myframe=document.getElementById("myframe");
          document.domain="a.com";
          myframe.onload=function(){
          var win=myframe.contentWindow;
          console.log(win);
          win.show("零點程序員");
          var doc=myframe.contentDocument;
          console.log(doc);
          }
          </script>

          使用window.name和iframe進行跨域:

          window的name屬性返回的是該window的名稱,它的值有個特點:在不同的頁面(甚至不同域名)加載后依舊存在,并且可以支持非常長的name值(2MB),即在一個窗口(window)的生命周期內,窗口載入的所有的頁面都是共享一個window.name,每個頁面對window.name都有讀寫的權限;

          正因為window的name屬性的這個特征,所以可以使用window.name來進行跨域;例如a.html:

          <h1>a.html</h1>
          <script>
          window.name="頁面a設置的name值";
          setTimeout(function(){
          window.location="b.html";
          },3000); // 3秒后在當前window中載入新的頁面
          </script>

          b.html:

          <h1>b.html</h1>
          <script>
          alert(window.name); // 頁面a設置的name值
          </script>

          跨域:例如,有一個a.com/a.html頁面,需要通過js來獲取位于另一個不同域上的頁面,如:b.com/b.html里的數據:

          <script>
          window.name="b.com/b.html中的數據";
          </script>

          如果b.html不跳轉,其他頁也可以獲取數據,可以采用iframe;
          如a.com/a.html:

          <h1>a.html</h1>
          <iframe id="iframe" src="http://www.b.com/b.html" style="display:none"></iframe>
          <script>
          var iframe=document.getElementById("iframe");
          // iframe在一開始載入b.com/b.html會執行此函數
          iframe.onload=function(){
          // 當iframe.src為b.html時觸發,此時iframe和當前頁面已經同源,可以訪問
          iframe.onload=function(){
          var data=iframe.contentWindow.name;
          alert(data);
          };
          // 這里的b.html為隨便一個頁面,只要與當前頁面同源就可以,
          // 目錄是讓iframe與當前頁面同源
          iframe.src="b.html";
          }
          </script>

          使用location.hash+iframe跨域:

          假設a.com/a.html要向b.com/b.html傳遞信息;如a.com/a.html:

          <h1>a.html</h1>
          <script>
          function checkHash(){
          try{
          var data=location.hash ? location.hash.substring(1) : '';
          console.log("收到的數據是:" + data);
          }catch(e){}
          }
          setInterval(checkHash, 5000);
          window.onload=function(){
          var iframe=document.createElement("iframe");
          // iframe.style.display="none";
          iframe.src="http://www.b.com/b.html#param"; // 傳遞的location.hash
          document.body.appendChild(iframe);
          };
          </script>

          b.com/b.html:

          <h1>b.html</h1>
          <script>
          function checkHash(){
          var data="";
          // 模擬一個簡單的參數處理操作
          switch(location.hash){
          case "#param":
          data="somedata";
          break;
          case "#other":
          // ...
          break;
          default:
          break;
          }
          data && callBack("#" + data);
          }
          function callBack(hash){
          // ie、chrome的安全機制無法修改parent.location.hash
          //所以要利用一個中間的www.csdnblogs.com域下的代理iframe
          var proxy=document.createElement("iframe");
          proxy.style.display="none";
          proxy.src="http://www.a.com/c.html" + hash;
          // 注意該文件在a.com中
          document.body.appendChild(proxy);
          }
          window.onload=checkHash;
          </script>

          a.com/c.html:

          <script>
          //因為parent.parent和自身屬于同一個域,所以可以改變其location.hash的值
          parent.parent.location.hash=self.location.hash.substring(1);
          </script>

          圖像Ping:

          使用<img>標簽,也可以動態創建圖像,使用它們的onload和onerror事件處理程序來確定是否接收到了響應;例如:

          var img=new Image();
          img.onload=img.onerror=function(){
          console.log("Done");
          };
          img.src="https://www.zeronetwork.cn/study/pingimg.php?name=wangwei";
          pingimg.php:
          if($_GET['name']){
          echo $_GET['name'];
          }

          圖像Ping有兩個主要的缺點,一是只能發送GET請求,二是無法訪問服務器的響應文本,因此,圖像Ping只能用于瀏覽器與服務器間的單向通信;提交的數據是通過查詢字符串形式發送的,但響應可以是任意內容,但通常是像素圖或204響應;
          通過圖像Ping,瀏覽器得不到任何具體的數據,但通過偵聽load和error事件,它能知道響應是什么時候接收到的,此時可以實現一些自身的邏輯;
          示例:圖像Ping最常用于跟蹤用戶點擊頁面或動態廣告曝光次數,如:

          <input type="button" id="btn" value="圖像Ping請求" />
          <div id="result"></div>
          <script>
          var increment=(function(){
          var counter=0;
          return function(){
          return ++counter;
          };
          })();
          var btn=document.getElementById("btn");
          btn.addEventListener("click", function(event){
          var sum=increment();
          var result=document.getElementById("result");
          var img=result.getElementsByTagName("img")[0];
          if(!img)
          img=new Image();
          img.onload=img.onerror=function(){
          result.appendChild(img);
          var oSpan=document.getElementById("sum");
          if(!oSpan){
          oSpan=document.createElement("span");
          oSpan.id="sum";
          }
          oSpan.innerHTML="發送請求的次數:" + sum;
          result.appendChild(oSpan);
          };
          if(sum % 2)
          img.src="https://www.zeronetwork.cn/study/images/ad1.jpg?sum=" + sum;
          else
          img.src="https://www.zeronetwork.cn/study/images/ad2.jpg?sum="+sum;
          </script>

          基于<script>標簽實現跨域:

          在某些HTML元素中,可以通過它的src屬性跨域請求內容,例如img、iframe等,也就是沒有跨域的限制;同樣,script也可以,也就是利用script來執行跨域的javascript代碼,從而實現前端跨域請求數據的目的;例如:

          <script>
          var script=document.createElement('script');
          script.src="http://www.b.com/scripts/demo.js";
          document.body.appendChild(script);
          script.onload=function(){
          show("從a.com傳過去的數據");
          }
          </script>

          b.com/scripts/demo.js:

          function show(msg){
          alert("收到的數據:" + msg);
          }
          alert("www.b.com/script/demo.js");

          JSONP:
          JSONP是JSON with padding(填充式JSON或參數式JSON)的簡寫,是應用JSON的新方法,其利用<script>標簽沒有跨域限制的特點,可以得到從其他源動態產生的JSON數據,但JSONP請求一定需要對方服務器的支持才可以;
          JSONP看起來與JSON差不多,是被包含在函數調用中的JSON,形如:callback({“name”: “wangwei”});
          JSONP由兩部分組成:回調函數callback和json數據;回調函數是當響應到來時應該在頁面中調用的函數,其名字一般是在請求中指定的,需要在本地實現;而數據就是傳入回調函數中的JSON數據;整個JSONP就是一個標準的JavaScript語句;
          JSONP后端服務:被請求服務端程序必須提供JSONP的服務,一般情況下,其會返回標準的JSONP;
          例如,被請求服務端b.com/jsonptest.php返回JSONP的簡單形式:

          <?php
          echo 'alert({"name":"王唯","age":18})';

          在請求端中使用<script>引入該文件,如:

          <script src="http://www.b.com/jsonptest.php"></script>

          請求端和服務端共同約定使用自定義函數,而不是內置函數;例如b.com/jsonptest.php:

          echo 'handlerJSONP({"name":"王唯","age":18})';

          請求端實現handlerJSONP()函數,并使用<script>引入b.com/jsonptest.php文件,如:

          <script>
          function handlerJSONP(response){
          alert("姓名:" + response.name + "\n" + "年齡:" + response.age);
          }
          </script>
          <script src="http://www.b.com/jsonptest.php"></script>

          一般來說,后端程序通過查詢字符串允許客戶端指定一個函數名,然后用這個函數名去填充響應,例如:

          <script>
          function handlerJSONP(response){
          alert(response);
          }
          </script>
          <script src="http://www.b.com/jsonptest.php?callback=handlerJSONP"></script>

          b.com/jsonptest.php:

          header('Content-type: application/json');
          //獲取回調函數名
          $callback=htmlspecialchars($_REQUEST['callback']);
          //json數據
          $json_data='["王唯","靜靜","娟子","大國"]';
          //輸出jsonp格式的數據
          echo $callback . "(" . $json_data . ")";

          JSONP是通過動態<script>元素來使用的,例如:

          // ...
          var script=document.createElement("script");
          script.src="http://www.b.com/jsonptest.php?callback=handlerJSONP";
          document.body.insertBefore(script, document.body.firstChild);

          請求端與后端就該函數名有個約定,使用隨機名,如:hander1234();而查詢字符串的名字一般約定使用”json”或”callback”,當然也可以是其他任意的名稱;

          JSONP之所以在開發人員中極為流行,主要原因是它非常簡單易用;與圖像Ping相比,它的優點在于能夠直接獲取響應文本,支持在瀏覽器與服務器之間雙向通信;

          在某些時候,處理完JSONP數據后,動態創建的<script>就可以刪除了;

          function clickHandler(e){
          var script=document.createElement("script");
          script.id="jsonp_script";
          script.src="http://www.a.com/jsonp.php?callback=handlerResponse";
          document.body.insertBefore(script, document.body.firstChild);
          }
          function handlerResponse(response){
          console.log(response);
          var script=document.getElementById("jsonp_script");
          script.parentNode.removeChild(script);
          }
          var btn=document.getElementById("btn");
          btn.addEventListener("click", clickHandler);

          定義將被腳本執行的回調函數

          getJSONP[cbnum]=function(response){
          try{
          callback(response);
          }finally{
          delete getJSONP[cbnum];
          script.parentNode.removeChild(script);
          }
          };
          script.src=url;
          document.body.appendChild(script);
          }
          getJSONP.counter=0; // 用于創建唯一回調函數名稱的計數器
          function handlerResponse(response){
          console.log(response);
          }
          getJSONP("http://www.a.com/jsonp.php", handlerResponse);

          示例:某接口的應用:

          <input type="button" id="btn" value="jsonp請求">
          <script>
          var btn=document.getElementById("btn");
          btn.addEventListener("click", function(){
          var script=document.createElement("script");
          script.id="jsonscript";
          script.src="https://suggest.taobao.com/sug?code=utf-8&q="+ encodeURIComponent("衣服") +"&callback=jsonpCallback";
          document.body.appendChild(script);
          });
          function jsonpCallback(response){
          console.log(response);
          var script=document.getElementById("jsonscript");
          script.parentNode.removeChild(script);
          }
          </script>

          JSONP的不足:

          • 首先,JSONP是從其他域中加載代碼執行,如果其他域不安全,很可能會在響應中夾帶一些惡意代碼;因此在使用不是你自己運維的Web服務時,一定得保證它安全可靠;
          • 其次,只能進行GET請求;
          • 最后,要確定JSONP請求是否失敗比較麻煩,雖然HTML5給<script>元素新增了一個onerror事件處理程序,但目前還沒有得到任何瀏覽器支持;

          JSONP和AJAX對比:
          JSONP和AJAX相同,都是客戶端向服務器端發送請求,從服務器端獲取數據的方式;但AJAX屬于同源策略,采用CORS方案跨域,而JSONP屬于非同源策略進行跨域請求;

          window.postMessage()方法:

          window.postMessage()方法可以安全地實現跨域通信;其是HTML5規范提供的一種受控機制來規避跨域安全限制的方法,采用異步的方式進行有限的通信,既可以用于同域傳遞消息,也可以用于跨域傳遞消息;

          其應用的場景是:

          • 頁面和其打開的新窗口的數據傳遞;
          • 多窗口之間消息傳遞;
          • 頁面與嵌套的iframe消息傳遞;

          一個窗口可以獲得對另一個窗口的引用,比如iframe的contentWindow屬性、執行window.open返回的窗口對象、或者是命名過或數值索引的window.frames,然后在窗口上調用targetWindow.postMessage()方法分發一個MessageEvent消息;接收消息的窗口觸發onmessage事件,并接收分發過來的消息;

          例如a.com/post.html,獲取iframe:

          <h1>a.com/post.html</h1>
          <iframe id="iframe" src="http://www.b.com/message.html"></iframe>
          <script>
          var iframe=document.getElementById("iframe");
          var win=iframe.contentWindow;
          console.log(win);
          </script>

          b.com/ message.html:

          <h1>b.com/message.html</h1>

          語法:otherWindow.postMessage(message, targetOrigin, [transfer]);

          • otherWindow為其他窗口的一個引用;
          • message參數:將要發送到其他window的數據;它將會被結構化克隆算法序列化,即可以不受限制的將數據對象安全的傳送給目標窗口而無需自己序列化;
          • targetOrigin:通過窗口的origin屬性來指定哪些窗口能接收到消息事件,其值可以是字符串"*"(表示無限制)或者一個URI;
          • transfer參數:可選,是一串和message同時傳遞的Transferable對象,這些對象的所有權將被轉移給消息的接收方,而發送一方將不再保有所有權;

          例如:a.com/post.html:

          // ...
          win.postMessage("來自a.com/post.html的消息","*");
          b.com/message.html:
          window.addEventListener("message", function(event){
          console.log(event); // MessageEvent
          });

          需要延遲執行postMessage()方法,延遲的方式有多種,使用setTimeout()、iframe的onload事件、發送者的onload事件或者使用按鈕的click事件處理程序,如a.com/post.html:

          setTimeout(function(){
          win.postMessage("來自a.com/post.html的消息","*");
          },500);
          // 或:
          var iframe=document.getElementsByTagName("iframe")[0];
          iframe.onload=function() {
          var iframe=document.getElementById("iframe");
          var win=iframe.contentWindow;
          win.postMessage("來自a.com/post.html的消息","*");
          }
          // 或:
          var btnSend=document.getElementById("btnSend");
          btnSend.addEventListener("click", function(){
          var iframe=document.getElementsByTagName("iframe")[0];
          var win=iframe.contentWindow;
          win.postMessage("來自a.com/post.html的消息","*");
          });

          使用postMessage()將數據發送到其他窗口時,最好指定明確的targetOrigin,而不是*,原因是惡意網站可以在用戶不知情的情況下更改窗口的位置,甚至可以攔截所發送的數據;

          win.postMessage("來自a.com/post.html的消息","http://www.b.com"); // 或
          win.postMessage("來自a.com/post.html的消息","http://www.b.com/"); // 或
          win.postMessage("來自a.com/post.html的消息","http://www.b.com/error.html");

          在發送消息的時候,如果目標窗口的協議、主機地址或端口這三者的任意一項不匹配targetOrigin的值,那么消息就不會被發送;只有三者完全匹配,消息才會被發送;這個機制用來控制消息可以發送到哪些窗口。

          MessageEvent接口:
          代表一段被目標對象接收的消息;
          屬性:

          • data屬性:其保存著由發送者發送的字符串數據;
          • lastEventId屬性:表示事件的唯一ID;
          • origin屬性:返回一個表示消息發送者來源;
          • ports屬性:MessagePort對象數組,表示消息正通過特定通道(數據通道)發送的相關端口;
          • source屬性:是一個MessageEventSource對象,代表消息發送者;
          window.addEventListener("message", function(event){
          console.log(event);
          console.log(event.data); // 來自a.com/post.html的消息
          console.log(event.lastEventId); // 空
          console.log(event.origin); // http://www.a.com
          console.log(event.ports); // []
          console.log(event.source); // window http://www.a.com/post.html
          });

          如果不希望接收message,就不要實現message事件;如果希望從其他網站接收message,最好使用origin或source屬性驗證消息發送者的身份;如:

          window.addEventListener("message", function(event){
          if(event.origin !="http://www.a.com")
          return;
          console.log(event.data);
          });

          雖然postMessage()是單向的通信,但可以使用source屬性在具有不同origin的兩個窗口之間建立雙向通信;
          例如a.com/message.html:

          <iframe id="iframe" src="http://www.b.com/message.html"></iframe>
          <input type="button" id="btn" value="send" />
          <script>
          var btn=document.getElementById("btn");
          btn.addEventListener("click", function(){
          var iframe=document.getElementById("iframe");
          var win=iframe.contentWindow;
          win.postMessage("來自a.com/post.html的消息","http://www.b.com/");
          });
          window.addEventListener("message", function(event){
          console.log("a.com收到:" + event.data);
          });
          </script>

          b.com/message.html:

          window.addEventListener("message", function(event){
          if(event.origin !="http://www.a.com")
          return;
          console.log("b.com收到:" + event.data);
          event.source.postMessage("b.com/message回發的消息", event.origin);
          });

          與使用open()方法打開的窗口通信:
          在向使用open()方法打開的窗口發送消息時,需要延遲執行,如:

          btnOpen.addEventListener("click", function(){
          var win=window.open("http://www.b.com/message.html","_blank","width:600px,height:400px");
          setTimeout(function(){
          win.postMessage("從a.com中打開","http://www.b.com");
          },1000);
          });
          window.addEventListener("message", function(event){
          console.log("a.com收到:" + event.data);
          });

          或者反向發送消息,例如a.com/post.html:

          var btnOpen=document.getElementById("btnOpen");
          btnOpen.addEventListener("click", function(){
          var win=window.open("http://www.b.com/message.html","_blank","width:600px,height:400px");
          });
          window.addEventListener("message", function(event){
          console.log("a.com收到:" + event.data);
          });

          b.com/message.html:

          <script>
          window.onload=function(){
          var targetWindow=window.opener;
          console.log(targetWindow);
          targetWindow.postMessage("message.html is ready","http://www.a.com");
          }
          window.addEventListener("message", function(event){
          console.log("b.com收到:");
          console.log(event.data);
          event.source.postMessage("b.com/message.html回發的消息", event.origin);
          });
          </script>

          postMessage()還可以發送結構化數據,該數據將會自動被(結構化克隆算法)序列化,如:

          var btn=document.getElementById("btn");
          btn.addEventListener("click", function(){
          var iframe=document.getElementById("iframe");
          var win=iframe.contentWindow;
          var person={
          name:"wanwei",
          sex: true,
          age: 18,
          friends: ["jingjing","daguo"],
          // smoking: function(){console.log(this.name)}, // 異常 could not be cloned.
          other1: undefined,
          other2: null
          }
          win.postMessage(person,"http://www.b.com/");

          b.com/message.html:

          window.addEventListener("message", function(event){
          console.log("b.com收到:");
          console.log(event.data);
          });

          因為是跨域,所以即使取得外源窗口的window對象,也無法操作對方的DOM,但是通過接收到的消息,自行構建DOM;

          例如a.com/post.html:

          var btn=document.getElementById("btn");
          btn.addEventListener("click", function(){
          var iframe=document.getElementById("iframe");
          var win=iframe.contentWindow;
          var person={
          name:"wanwei",
          sex: true,
          age: 18,
          friends: ["jingjing","daguo"],
          }
          win.postMessage(person,"http://www.b.com/");
          });
          window.addEventListener("message", function(event){
          if(event.origin=="http://www.b.com"){
          if(event.data.state){
          document.getElementById("btn").setAttribute("disabled",true);
          // ...
          }
          }
          });
          b.com/message.html:
          window.addEventListener("message", function(event){
          if(event.origin=="http://www.a.com"){
          var person=event.data;
          var h1=document.createElement("h1");
          h1.innerText=person.name + "信息";
          document.body.appendChild(h1);
          var p=document.createElement("p");
          p.innerHTML="性別:" + (person.sex ? "男" : "女");
          p.innerHTML +="<br/>年齡:" + person.age;
          p.innerHTML +="<br/>朋友:" + person.friends.join(",");
          document.body.appendChild(p);
          event.source.postMessage({state:1}, event.origin);
          }
          });

          示例,后臺管理的應用,main.html:

          <style>
          *{margin:0; padding:0;}
          ul,li{list-style: none;}
          .top{width:100%; height:100px; background-color:yellowgreen;}
          .left{width:20%; height: 100%; float: left; background-color:yellow;}
          .left ul li{padding:10px;}
          .left ul li a{color:#000; text-decoration: none;}
          iframe{width:80%; float: right; border:none;}
          .bg{
          position: fixed; left:0; top: 0; display: none;
          width:100%; height:100%; background-color: rgba(0, 0, 0, .5);
          }
          .showBg{display: block !important}
          .confirm{
          position:fixed; width:400px; height:200px; z-index: 2;
          left: 50%; top: 50%; transform: translate(-50%, -50%);
          padding: 20px; text-align: center; background-color: #FFF;
          }
          </style>
          <div class="top"></div>
          <div class="left">
          <ul>
          <li><a href="console.html" target="iframe">控制臺</a></li>
          <li><a href="content.html" onclick="sendMessage()" target="iframe">系統設置</a></li>
          </ul>
          </div>
          <iframe name="iframe" src="console.html"></iframe>
          <div class="bg">
          <div class="confirm">
          <p>是否確認保存?</p>
          <p><input type="button" id="btnCancel" value="取消" />
          <input type="button" id="btnSave" value="保存" /></p>
          </div>
          </div>
          <script>
          var iframe=null;
          window.onload=function(){
          var leftDiv=document.getElementsByClassName("left")[0];
          iframe=document.getElementsByTagName("iframe")[0];
          iframe.style.height=leftDiv.style.height=(document.documentElement.scrollHeight - 100) + "px";
          }
          function sendMessage(){
          iframe.onload=function(){
          iframe.contentWindow.postMessage({
          method: 'dataId',
          data: {dataId: 1}
          }, "*");
          }
          }
          window.addEventListener("message", function(event){
          if(event.data.method=="showBg"){
          document.getElementsByClassName("bg")[0].classList.add("showBg");
          }
          });
          var btnCancel=document.getElementById("btnCancel");
          btnCancel.addEventListener("click", function(){
          if(iframe){
          iframe.contentWindow.postMessage({
          method: "cancel"
          }, "*");
          document.getElementsByClassName("bg")[0].classList.remove("showBg");
          }
          });
          var btnSave=document.getElementById("btnSave");
          btnSave.addEventListener("click", function(){
          if(iframe){
          iframe.contentWindow.postMessage({
          method: "save"
          }, "*");
          document.getElementsByClassName("bg")[0].classList.remove("showBg");
          }
          });
          </script>

          console.html:

          <h1>控制臺</h1>

          content.html:

          <style>
          .container{width:60%; margin: 0 auto;}
          </style>
          <div class="container">
          <p>設置1:<input type="text" /></p>
          <p>設置2:<input type="text" /></p>
          <p>設置3:<input type="text" /></p>
          <p>設置4:<input type="text" /></p>
          <p><input type="button" id="btnConfirm" value="保存" /></p>
          </div>
          <script>
          window.addEventListener("message", function(event){
          if(event.data.method=="dataId"){
          console.log(event.data.data.dataId);
          }else if(event.data.method=="cancel"){
          console.log("取消操作");
          }else if(event.data.method=="save"){
          console.log("保存成功");
          }
          });
          var btnConfirm=document.getElementById("btnConfirm");
          btnConfirm.addEventListener("click", function(){
          window.parent.postMessage({
          method: "showBg"
          }, "*");
          });
          </script>

          示例,修改信息:

          content.html:
          <div class="container">
          <h1>用戶信息</h1>
          <p>ID:001</p>
          <p>姓名:<span id="usernameSpan">王唯</span>
          <input type="button" id="editInfo" value="修改個人信息" /> </p>
          <p>單位:<span id="jobSpan">零點網絡</span>
          <input type="button" id="editJob" value="修改工作信息" /></p>
          <p>地址:<input type="text" id="address" value="北京市東城區" /></p>
          <p>電話:<input type="text" id="tel" value="13888888888" /></p>
          <p><input type="button" id="btnConfirm" value="保存" /></p>
          </div>
          <script>
          window.addEventListener("message", function(event){
          if(event.data.method=="dataId"){
          console.log(event.data.data.dataId);
          }else if(event.data.method=="cancel"){
          console.log("取消操作");
          }else if(event.data.method=="save"){
          document.getElementById("usernameSpan").innerText=event.data.data.username;
          console.log("保存成功");
          }
          });
          var btnConfirm=document.getElementById("btnConfirm");
          btnConfirm.addEventListener("click", function(){
          window.parent.postMessage({
          method: "showBg"
          }, "*");
          });
          var editInfo=document.getElementById("editInfo");
          editInfo.addEventListener("click", function(){
          var win=window.open("editInfo.html", "_blank","width:200px,height:500px");
          setTimeout(function(){
          win.postMessage({
          method: "info",
          dataId: 2
          });
          },500);
          });
          </script>

          editInfo.html:

          <form>
          <input type="hidden" id="ID" name="ID" />
          <p>姓名:<input type="text" id="username" name="username" /></p>
          <p>性別:<input type="radio" id="male" name="sex" value="1" />男
          <input type="radio" id="female" name="sex" value="0" />女</p>
          <p>年齡:<input type="text" id="age" name="age" /></p>
          <p><input type="button" id="btnCancel" value="取消" />
          <input type="button" id="btnSave" value="保存" /></p>
          </form>
          <script>
          window.addEventListener("message", function(event){
          if(event.data.method=="info"){
          // Ajax請求,從數據庫中取出這條記錄
          var id=event.data.dataId;
          var xhr=new XMLHttpRequest();
          xhr.open("GET", "getInfo.php?action=showInfo&ID=" + id);
          xhr.onreadystatechange=function(){
          if(xhr.readyState==4 && xhr.status==200){
          // console.log(xhr.response);
          var person=xhr.response;
          document.forms[0].elements['ID'].value=person.ID;
          document.forms[0].elements['username'].value=person.username;
          document.forms[0].elements['sex'].value=person.sex;
          document.forms[0].elements['age'].value=person.age;
          xhr=null;
          }
          };
          xhr.responseType="json";
          xhr.send(null);
          }
          });
          var btnCancel=document.getElementById("btnCancel");
          btnCancel.addEventListener("click", function(){
          window.opener.postMessage({method: "cancel"}, "*");
          window.close();
          });
          var btnSave=document.getElementById("btnSave");
          btnSave.addEventListener("click", function(){
          // 保存到數據庫
          var data=new FormData(document.forms[0]);
          data.append("action", "update");
          var xhr=new XMLHttpRequest();
          xhr.open("POST", "getInfo.php");
          xhr.onreadystatechange=function(){
          if(xhr.readyState==4 && xhr.status==200){
          var data=JSON.parse(xhr.responseText);
          if(data.status){
          var person={
          ID: document.forms[0].elements['ID'].value,
          username: document.forms[0].elements['username'].value,
          sex: document.forms[0].elements['sex'].value,
          age: document.forms[0].elements['age'].value
          };
          window.opener.postMessage({
          method: "save",
          data: person
          });
          }
          xhr=null;
          }
          };
          xhr.send(data);
          window.close();
          })
          </script>

          editInfo.php:

          <?php
          require_once "conn.php";
          if(isset($_REQUEST['action'])){
          if($_REQUEST['action']=='showInfo'){
          $ID=intval($_GET['ID']);
          $sql="select ID,username, sex, age from users where ID=$ID";
          $result=$conn->query($sql);
          $row=mysqli_fetch_array($result);
          echo json_encode($row);
          }elseif($_REQUEST['action']=='update'){
          $ID=intval($_POST['ID']);
          $username=$_POST['username'];
          $sex=$_POST['sex'];
          $age=$_POST['age'];
          $sql="update users set username='$username', sex='$sex', age='$age' where ID=$ID";
          $result=$conn->query($sql);
          if($result){
          echo '{"status": 1}';
          }else{
          echo '{"status": 0}';
          }
          }

          超鏈接打開的窗口也可使用postMessage()方法進行通信,但需要設置a標簽的target屬性為自定義值,如:

          <p><a href="http://www.b.com/content.html" target="mywin">零點程序員</a></p>
          b.com/content.html:
          console.log(window.opener); // Window or global
          console.log(window.name); // "mywin"

          發送消息,b.com/content.html:

          if(window.opener){
          console.log(window.opener);
          window.opener.postMessage("我已經打開了","*");
          }

          主頁面:

          何保持頁面樣式基本不變的前提下將HTML頁面導出為PDF,下面提供一些示例代碼,純屬個人原創,如對你有幫助請記得加關注、加收藏、點贊、轉發、分享~謝謝~~

          • 基本思路:保持頁面樣式基本不變,使用 `html2canvas` 將頁面轉換為圖片,然后再通過 `jspdf` 將圖片分頁導出為PDF文件(中間會遇到圖片或文字等內容在分頁處被切割開的問題,如何解決了?詳見末尾干貨)
          • 上基礎代碼:下面為項目中實際代碼截取
          <div>
              <!-- 要打印的內容區 -->
              <div ref="contentRef">
                  <div class="print-item print-out-flow">這是脫離文檔流的內容區域</div>
                  <div class="print-item">這是一行內容,也是最小葉子元素內容</div>
              </div>
              <!-- 打印內容容器 -->
              <div ref="printContainerRef" class="print-container"></div>
          </div>
          /**
            * 1.使用一個隱藏div裝載有滾動條的div.innerHTML
            * 2.隱藏div使用position: absolute, z-index: -999, left: -9999px, width: 900px 控制讓用戶無感知
            * 3.根據需要覆寫隱藏div內html樣式(例如textarea多行顯示有問題, 可以新增一個隱藏的div
            *    包裹textarea的綁定值, 然后在打印樣式中覆寫樣式, 隱藏textarea并顯示對應div)
            */
          handleExport() {
             // 下面是VUE組件內獲取DOM元素代碼,將內容放置到打印區(定義的隱藏DIV)中
              const contentRef=this.$refs.contentRef as HTMLElement;
              const printContainerRef=this.$refs.printContainerRef as HTMLElement;
              // 打印區的需額外處理絕對定位值, 調整使得第一個元素的.top值為0, 以便于頁面計算
              printContainerRef.innerHTML=contentRef.innerHTML;	
              
              // 所有葉子div元素加上 print-item 樣式名, 脫離文檔流的額外添加 print-out-flow
              handlePrintItem(printContainerRef);  // 解決多頁內容可能被切割問題
              
              html2canvas(printContainerRef, {allowTaint: false, useCORS: true}).then((canvas: any)=> {
                const contentHeight=canvas.height;
                const contentWidth=canvas.width;
                // pdf每頁顯示的內容高度
                const pageHeight=contentWidth / 595.28 * 841.89;
                // 未生成pdf的頁面高度
                let offsetHeight=contentHeight;
                // 頁面偏移值
                let position=0;
                // a4紙的尺寸[595.28, 841.89], canvas圖片按a4紙大小縮放后的寬高
                const imgWidth=595.28;
                const imgHeight=595.28 / contentWidth * contentHeight;
          
                const dataURL=canvas.toDataURL('image/jpeg', 1.0);
                const doc=new jsPDF('p', 'pt', 'a4');
          
                if (offsetHeight < pageHeight) {
                  doc.addImage(dataURL, 'JPEG', 0, 0, imgWidth, imgHeight);
                } else {
                  while (offsetHeight > 0) {
                    doc.addImage(dataURL, 'JPEG', 0, position, imgWidth, imgHeight);
                    offsetHeight -=pageHeight;
                    position -=841.89;
          
                    if (offsetHeight > 0) {
                      doc.addPage();
                    }
                  }
                }
          
                doc.save(this.generateReportFileName());
                printContainerRef.innerHTML='';
              });
          }

          上干貨代碼:上面分頁導出PDF可能網上能看到類型代碼,但絕對找不到下面的代碼,純手搓解決分頁元素被切開問題(思路:獲取自身定位,如自己剛好在被分頁處,則加上一定的margin-top值將內容向下移)

          /** 
           * 處理打印元素項, 修復分頁后被切割的元素
           * @param printContainerRef 打印內容div容器
           * @param itemClassName 打印最小元素標識類名
           * @param outFlowClassName 脫離文檔流的元素標識類名
           */
          export function handlePrintItem(
            printContainerRef: HTMLElement,
            itemClassName: string='print-item',
            outFlowClassName: string='print-out-flow'
          ): void {
            const rootClientRect=printContainerRef.getBoundingClientRect();
            // 初始化頁面相關數據
            const totalHeight=rootClientRect.height;  // 內容總高度
            const a4PageHeight=(printContainerRef.clientWidth / 595.28) * 841.89; // a4紙高度
            let pageNum=Math.ceil(totalHeight / a4PageHeight);  // 總頁數
            let addPageHeight=0;  // 修正被分割元素而增加的頁面高度總和
            let currentPage=1;  // 當前正在處理切割的頁面
            const splitItemObj: { [key: number]: HTMLElement[] }={};  // 內容中各頁被切割元素存儲對象
          
            const printItemNodes: NodeListOf<HTMLElement>=printContainerRef.querySelectorAll(`.${itemClassName}`);
            for (let item of printItemNodes) {
              // 如果當前頁已經是最后一頁, 則中斷判斷
              if (currentPage >=pageNum) {
                break;
              }
          
              // 獲取元素絕對定位數據
              const clientRect=item.getBoundingClientRect();
              let top=clientRect.top;
              const selfHeight=clientRect.height;
              // 如果當前元素距離頂部高度大于當前頁面頁腳高度, 則開始判斷下一頁頁腳被切割元素
              if (top > currentPage * a4PageHeight) {
                // 換頁前修正上一頁被切割元素
                addPageHeight +=fixSplitItems(currentPage, a4PageHeight, splitItemObj[currentPage], outFlowClassName);
                pageNum=Math.ceil((totalHeight + addPageHeight) / a4PageHeight);
                top=item.getBoundingClientRect().top;
                currentPage++;
              }
              // 如果元素剛好處于兩頁之間, 則記錄該元素
              if (top > (currentPage - 1) * a4PageHeight && top < currentPage * a4PageHeight && top + selfHeight > currentPage * a4PageHeight) {
                if (!splitItemObj[currentPage]) {
                  splitItemObj[currentPage]=[];
                }
                splitItemObj[currentPage].unshift(item);
                // 如果當前元素是最后一個元素, 則直接處理切割元素, 否則交由處理下一頁元素時再處理切割
                if (item===printItemNodes[printItemNodes.length - 1]) {
                  fixSplitItems(currentPage, a4PageHeight, splitItemObj[currentPage], outFlowClassName);
                }
              }
            }
          }
          
          /**
            * 修復當前頁所有被切割元素
            * @param currentPage 當前頁
            * @param pageHeight 每頁高度
            * @param splitElementItems 當前被切割元素數組
            * @param outFlowClassName 脫離文檔流的樣式類名
            */
          function fixSplitItems(
            currentPage: number,
            pageHeight: number,
            splitElementItems: HTMLElement[],
            outFlowClassName: string
          ): number {
            if (!splitElementItems || !splitElementItems.length) {
              return 0;
            }
          
            const yMargin=5;  // y方向距離頁眉的距離
            const splitItemsMinTop=getSplitItemsMinTop(splitElementItems);
            if (!splitItemsMinTop) {
              return 0;
            }
          
            let fixHeight=currentPage * pageHeight - splitItemsMinTop + yMargin;
            const outFlowElement=splitElementItems.find((item)=> item.classList.contains(outFlowClassName));
            if (outFlowElement && outFlowElement.parentElement) {
              const parentPreviousElement=outFlowElement.parentElement.previousElementSibling as HTMLElement;
              fixHeight +=getMarinTopNum(parentPreviousElement, outFlowElement.parentElement);
              outFlowElement.parentElement.style.marginTop=`${fixHeight}px`;
              return fixHeight;
            }
          
            splitElementItems.forEach((splitElement)=> {
              splitElement.style.marginTop=`${fixHeight}px`;
            });
            return fixHeight;
          }
          
          /**
            * 獲取被切割元素數組中最小高度值(如一行有多個元素被切割,則選出距離頂部最小的高度值)
            * @param splitElementItems 當前被切割元素數組
            */
          function getSplitItemsMinTop(
            splitElementItems: HTMLElement[]
          ): number | undefined {
            // 獲取元素中最小top值作為基準進行修正
            let minTop: number | undefined;
            let minElement: HTMLElement | undefined;
            splitElementItems.forEach((splitElement)=> {
              let top=splitElement.getBoundingClientRect().top;
              if (minTop) {
                minTop=top < minTop ? top : minTop;
                minElement=top < minTop ? splitElement : minElement;
              } else {
                minTop=top;
                minElement=splitElement;
              }
            });
          
            // 修正當前節點及其前面同層級節點的margin值
            if (minTop && minElement) {
              const previousElement=splitElementItems[splitElementItems.length - 1].previousElementSibling as HTMLElement;
              minTop -=getMarinTopNum(previousElement, minElement);
            }
            return minTop;
          }
          
          /**
            * 通過前一個兄弟元素和元素自身的位置確認一個距離頂部高度修正值
            * @param previousElement 前一個兄弟元素
            * @param curElement 當前元素
            */
          function getMarinTopNum(previousElement: HTMLElement, curElement: HTMLElement): number {
            let preMarginNum=0;
            let curMarginNum=0;
            if (previousElement) {
              // 獲取外聯樣式需要getComputedStyle(), 直接.style時對象的值都為空
              const previousMarginBottom=window.getComputedStyle(previousElement).marginBottom;
              preMarginNum=previousMarginBottom ? Number(previousMarginBottom.replace('px', '')) : 0;
            }
            const marginTop=window.getComputedStyle(curElement).marginTop;
            curMarginNum=marginTop ? Number(marginTop.replace('px', '')) : 0;
            return preMarginNum > curMarginNum ? preMarginNum : curMarginNum;
          }

          以上純原創!歡迎加關注、加收藏、點贊、轉發、分享(代碼閑聊站)~


          主站蜘蛛池模板: 国产视频一区二区| 亚洲欧洲一区二区三区| 国产第一区二区三区在线观看| 中文字幕乱码一区久久麻豆樱花 | 国产精品一区二区久久精品| 麻豆亚洲av熟女国产一区二| 国产精品亚洲综合一区在线观看| 国产伦一区二区三区高清| 国产婷婷色一区二区三区| 亚洲AV无码一区二区三区牛牛| 国产成人高清亚洲一区91| 国产午夜精品一区二区| 久久久久人妻一区二区三区| 国产成人高清亚洲一区91| 国产午夜精品一区二区三区漫画| 亚洲国产精品一区二区久| 卡通动漫中文字幕第一区| 亚洲AV无一区二区三区久久 | 精品一区二区三区免费视频| 久久国产午夜一区二区福利| 无码少妇一区二区三区芒果| 成人区人妻精品一区二区不卡网站| 精品国产乱子伦一区二区三区| 无码人妻精品一区二区蜜桃百度 | 国产精品一区二区香蕉| 国产亚洲一区二区三区在线观看| 亚洲一区二区三区写真| 变态拳头交视频一区二区| 日韩中文字幕精品免费一区| 国产精品被窝福利一区 | 国产福利电影一区二区三区久久老子无码午夜伦不 | 中文字幕在线一区二区在线| 无码毛片一区二区三区视频免费播放 | 久久久精品人妻一区二区三区| 国产精品一区不卡| 中文字幕日韩一区二区三区不卡| 性色AV 一区二区三区| 国产99久久精品一区二区| 久久成人国产精品一区二区 | 日韩精品无码久久一区二区三| 亚洲国产视频一区|