[原创]egret的WebView实现(基于egret2.5)
egret的WebView實現(基于egret2.5)
標簽(空格分隔): egret webview
客戶端開發中有一種很常見的場景就是展現web頁面,也就是 WebView 。例如 IOS 、 Android ,都為開發者提供了 WebView 組件,而在 egret 中暫時并沒有提供 WebView 組件,所以我們只能自己動手實現一個能在 egret 引擎中可用的 WebView 組件。
由于egret運行在瀏覽器中,所以想要實現一個能展現web頁面的組件,第一想法應該是使用iframe。
那么這些問題需要解決:
- 與 egret 集成,并提供與 egret 組件一致的調用方式
- 融入 egret 的展現,即 WebView 的坐標、大小需要與 egret 一致
查看 egret 源碼,我們不難發現 egret 的 HtmlInput 組件也是使用上面我們所考慮的方式實現在游戲中進行文本輸入的。所以,我們可以采用 egret HtmlInput 類似的方式實現 WebView 。因此,我們首先從 egret HtmlInuput 源碼入手:
1. Egret中HtmlInput的實現
1.1 HtmlInput實現方式:
在 egret.web.js 中可以很找到 egret.web.HTMLInput 的初始化相關的源碼:
HtmlInput.prototype._initStageDelegateDiv = function (container, canvas) {this.canvas = canvas;var self = this;var stageDelegateDiv;if (!stageDelegateDiv) {stageDelegateDiv = document.createElement("div");this.StageDelegateDiv = stageDelegateDiv;stageDelegateDiv.id = "StageDelegateDiv";container.appendChild(stageDelegateDiv);self.initValue(stageDelegateDiv);self._inputDIV = document.createElement("div");self.initValue(self._inputDIV);self._inputDIV.style.width = "0px";self._inputDIV.style.height = "0px";self._inputDIV.style.left = 0 + "px";self._inputDIV.style.top = "-100px";self._inputDIV.style[egret.web.getPrefixStyleName("transformOrigin")] = "0% 0% 0px";stageDelegateDiv.appendChild(self._inputDIV);this.canvas.addEventListener("click", function (e) {if (self._needShow) {self._needShow = false;self._stageText._onClickHandler(e);self.show();}else {if (self._inputElement) {self.clearInputElement();self._inputElement.blur();self._inputElement = null;}}});self.initInputElement(true);self.initInputElement(false);}}; HtmlInput.prototype.initInputElement = function (multiline) {var self = this;//增加1個空的textareavar inputElement;if (multiline) {inputElement = document.createElement("textarea");inputElement.style["resize"] = "none";self._multiElement = inputElement;inputElement.id = "egretTextarea";}else {inputElement = document.createElement("input");self._simpleElement = inputElement;inputElement.id = "egretInput";}inputElement.type = "text";self._inputDIV.appendChild(inputElement);inputElement.setAttribute("tabindex", "-1");inputElement.style.width = "1px";inputElement.style.height = "12px";self.initValue(inputElement);inputElement.style.outline = "thin";inputElement.style.background = "none";inputElement.style.overflow = "hidden";inputElement.style.wordBreak = "break-all";//隱藏輸入框inputElement.style.opacity = 0;inputElement.oninput = function () {if (self._stageText) {self._stageText._onInput();}};};很明顯,是在id為 StageDelegateDiv 的 dom 中嵌入 <input type=”text” > 和 <textarea> ,并在這兩個 html 元素上做文章。
1.2 HtmlInput 渲染展現:
那么 text 輸入框和 textarea 文本輸入域是如何正確展現到 egret 的 canvas 上的呢?
在 egret.web.js 的 egret.web.HTML5StageText 中我們看到如下代碼:
從這幾段關鍵代碼中我們可以看到, HtmlInput 的文本使用 egret.TextFiled 組件進行渲染的,并且 HtmlInput 對應的 Dom 位置、寬高直接使用 TextFiled 的位置、寬高,這樣 HtmlInputDom 就能與 egret 的坐標寬高一致了。
1.3 HtmlInput文本輸入:
那么是怎么實現輸入的呢?
在 egret.web.js 的 egret.web.HTML5StageText 的源碼中能找到如下代碼:
從最后一行代碼可以看到,直接調用 HtmlInput 對應的 dom 的 onfocus() ,從而彈出輸入框,即可進行文本輸入操作。
2. WebView的實現
看完 HtmlInput 的實現,可見 WebView 的實現有所不同:
HtmlInput 依賴 egret.TextField 來進行渲染展現,egret.TextField 是 egret 中內建的組件,所以 HtmlInput 坐標寬高直接使用 textfield 的坐標、寬高就可以了。但是 WebView 依賴的是 iframe ,是 html 的標準組件,并不是 egret 的內建組件,而且在 egret 的 canvas 之外,所以必然會涉及到 webview 組件的坐標寬高的換算。結合上文提到的 WebView 應該達到的效果,我們可以考慮如下實 現WebView :
- WebView 繼承degret.displayObjectContailner,override相關方法(getX,getY,setX,setY,getWidht,getHeight,setWidth,setHeight等)以提供與egret引擎中內建組件一致的調用方式:
- 與 HtmlInput 一樣,在 StageDelegatDiv 中 加入 iframe, WebView 對象持有此 iframe dom 的引用,但是必須根據其 x,y,width,height 進行相應的坐標、大小的轉化并進行顯示:
要實現以上想法,以下需要處理:
(1) 了解egret的scalemode( 縮放模式)
(2) 根據各種scalemode的實現方式換算webview的x,y,width,height
(3) 控制WebView中iframe的樣式及顯示時機
2.1 egret 的 scalemode (縮放模式)
egret的六種 scalemode :
StageScaleMode.NO_SCALE = "noScale"; StageScaleMode.SHOW_ALL = "showAll"; StageScaleMode.NO_BORDER = "noBorder"; StageScaleMode.EXACT_FIT = "exactFit"; StageScaleMode.FIXED_WIDTH = "fixedWidth"; StageScaleMode.FIXED_HEIGHT = "fixedHeight";StageScaleMode.NO_SCALE:
不縮放應用程序內容。即使在更改播放器視口大小時,它仍然保持不變。如果播放器視口比內容小,則可能進行一些裁切。 在此模式下,舞臺尺寸(Stage.stageWidth,Stage.stageHeight)始終跟播放器視口大小保持一致。StageScaleMode.SHOW_ALL:
保持原始寬高比縮放應用程序內容,縮放后應用程序內容的較寬方向填滿播放器視口,另一個方向的兩側可能會不夠寬而留有黑邊。在此模式下,舞臺尺寸(Stage.stageWidth,Stage.stageHeight)始終等于初始化時外部傳入的應用程序內容尺寸。StageScaleMode.NO_BORDER:
保持原始寬高比縮放應用程序內容,縮放后應用程序內容的較窄方向填滿播放器視口,另一個方向的兩側可能會超出播放器視口而被裁切在此模式下,舞臺尺寸(Stage.stageWidth,Stage.stageHeight)始終等于初始化時外部傳入的應用程序內容尺寸。StageScaleMode.EXACT_FIT:
不保持原始寬高比縮放應用程序內容,縮放后應用程序內容正好填滿播放器視口。在此模式下,舞臺尺寸(Stage.stageWidth,Stage.stageHeight)始終等于初始化時外部傳入的應用程序內容尺寸。StageScaleMode.FIXED_WIDTH :
保持原始寬高比縮放應用程序內容,縮放后應用程序內容在水平和垂直方向都填滿播放器視口,但只保持應用程序內容的原始高度不變,寬度可能會改變。在此模式下,舞臺高度(Stage.stageHeight)始終等于初始化時外部傳入的應用程序內容高度。舞臺寬度(Stage.stageWidth)由當前的縮放比例與播放器視口寬度決定。StageScaleMode.FIXED_HEIGHT :
保持原始寬高比縮放應用程序內容,縮放后應用程序內容在水平和垂直方向都填滿播放器視口,但只保持應用程序內容的原始高度不變,寬度可能會改變。在此模式下,舞臺高度(Stage.stageHeight)始終等于初始化時外部傳入的應用程序內容高度。舞臺寬度(Stage.stageWidth)由當前的縮放比例與播放器視口寬度決定。
引用官方的一張圖:
egret官方文章的介紹: egret scalemode ,有很詳細的講解。
可見,不同的縮放模式對于舞臺大小、顯示寬高的大小有決定性的影響。我們僅考慮SHOW_ALL,NO_BORDER,FIXED_WIDTH,FIXED_HEIGHT這幾種在實際生產中用得比較多的縮放模式。
各種縮放模式下是如何計算舞臺寬高、顯示寬高的呢?
在egret.js的源碼中,我們可以找到如下代碼片段:
2.2 根據各種 scalemode 的實現方式換算 webview 的 x,y,width,height
我們可以清楚看到各種縮放模式下,舞臺寬高、顯示寬高的計算,因此我們可以根據相應的縮放模式來換算WebView的x,y,widht,height對應在web窗口中的具體數值,這里以WebView.setwidth(w:number)為例:
public set width(value:number) {this._width = value;if(this._scaleMode==egret.StageScaleMode.FIXED_WIDTH || this._scaleMode==egret.StageScaleMode.FIXED_HEIGHT ){this._iframe.width=this._width/this._stageW*this._windowW+"px";this._iframeWrapper.style.width=this._width/this._stageW*this._windowW+"px";}if(this._scaleMode==egret.StageScaleMode.SHOW_ALL || this._scaleMode==egret.StageScaleMode.NO_BORDER ) {if(this._windowW==this._displayW){this._iframe.style.width = this._width / this._stageW * this._windowW + "px";this._iframeWrapper.style.width = this._width / this._stageW * this._windowW + "px";}else{this._iframe.style.width = this._width / this._stageW * this._displayW + "px";this._iframeWrapper.style.width = this._width / this._stageW * this._displayW + "px";}}}setX,setY,setHeight等方法坐標換算與上述方式相同。
通過坐標換算,我們就能以egret組件一樣的方式調用WebView并且展現在正確的位置。
2.3 控制 WebView 中 iframe 的樣式及顯示時機
坐標寬高都確定了,就可以設置 iframe 的樣式了,這個比較簡單,通過js即可設置樣式。另外,iframe在網頁完全加載完再顯示會比較友好。
3. IOS 系統中 iframe 寬高撐大、無法滑動的解決方案
測試如上方法開發的 WebView 時,發現在 ios 中, iframe 寬高會被里面的內容撐大、并且無法滑動:
通過google,找到了解決方案:
http://stackoverflow.com/questions/23083462/how-to-get-an-iframe-to-be-responsive-in-ios-safari
或者
http://davidwalsh.name/scroll-iframes-ios
即:
給iframe一個父級div,設置如下css屬性:
4. WebView.ts完整代碼及使用方式:
WebView.ts:
/*** WebView* 適配FIXED_WIDTH、FIXED_HEIGHT、NO_BORDER、SHOW_ALL四種縮放模式* 暫未考慮屏幕大小改變、屏幕旋轉以及單頁面多Webplay實例的情形* Created by yxiao on 2015/9/30.*/ class WebView extends egret.DisplayObjectContainer {private _x:number=0;private _y:number=0;private _width:number=0;private _height:number=0;private _src:string="";private _scaleMode:string=egret.MainContext.instance.stage.scaleMode;private _stageW:number;private _stageH:number;private _windowW:number;private _windowH:number;private _displayH:number;private _displayW:number;private _designH:number;private _designW:number;private _iframeWrapper:HTMLDivElement=null;private _iframe:HTMLIFrameElement=null;/*** @param src*/public constructor(src:string){super();var stageDelegateDom:HTMLElement=document.getElementById("StageDelegateDiv"),playerContainer:HTMLElement=stageDelegateDom.parentElement;var iframeWrapperDom=document.getElementById("iframe-wrapper");if(!iframeWrapperDom){iframeWrapperDom=document.createElement("div");iframeWrapperDom.style.display="none";iframeWrapperDom.attributes['style'].value+='position:absolute;-webkit-overflow-scrolling: touch;overflow-y: scroll;';//解決iframe在ios下的顯示問題iframeWrapperDom.id="iframe-wrapper";stageDelegateDom.appendChild(iframeWrapperDom);}this._iframeWrapper=<HTMLDivElement>iframeWrapperDom;this._iframeWrapper.style.display="none";this._iframeWrapper.style.opacity="0";var iframe = document.createElement("iframe"),t=new Date().getTime();iframe.src=src;iframe.id="webview-iframe-"+t;iframe.name="webview-iframe-"+t;iframe.style.position="absolute";iframe.style.top="0";iframe.style.left="0";iframe.style.opacity="0";iframe.style.display='none';iframe.frameBorder='0';iframe.border="0";this._iframeWrapper.appendChild(iframe);this._iframe=<HTMLIFrameElement>document.getElementById("webview-iframe-"+t);var self=this;this._iframe.onload=function(){self._iframeWrapper.style.opacity="1";self._iframe.style.opacity="1";}this._stageW=egret.MainContext.instance.stage.stageWidth;this._stageH=egret.MainContext.instance.stage.stageHeight;this._windowW=window.innerWidth;this._windowH=window.innerHeight;this._designH=parseInt(playerContainer.attributes['data-content-height'].value);this._designW=parseInt(playerContainer.attributes['data-content-width'].value);var stageSize = egret.sys.screenAdapter.calculateStageSize(egret.MainContext.instance.stage.scaleMode, this._windowW, this._windowH, this._designW, this._designH);this._displayH=stageSize.displayHeight;this._displayW=stageSize.displayWidth;console.log("windowW:"+this._windowW);console.log("stageW:"+this._stageW);console.log("disPlayW:"+this._displayW);console.log("windowH:"+this._windowH);console.log("stageH:"+this._stageH);console.log("displayH:"+this._displayH);}public show():void {this._iframe.style.display='block';this._iframeWrapper.style.display='block';}public destroy():void {if(this._iframe){this._iframeWrapper.style.display="none";this._iframeWrapper.removeChild(this._iframe);}}public get width():number {return this._width;}public set width(value:number) {this._width = value;if(this._scaleMode==egret.StageScaleMode.FIXED_WIDTH || this._scaleMode==egret.StageScaleMode.FIXED_HEIGHT ){this._iframe.width=this._width/this._stageW*this._windowW+"px";this._iframeWrapper.style.width=this._width/this._stageW*this._windowW+"px";}if(this._scaleMode==egret.StageScaleMode.SHOW_ALL || this._scaleMode==egret.StageScaleMode.NO_BORDER ) {if(this._windowW==this._displayW){this._iframe.style.width = this._width / this._stageW * this._windowW + "px";this._iframeWrapper.style.width = this._width / this._stageW * this._windowW + "px";}else{this._iframe.style.width = this._width / this._stageW * this._displayW + "px";this._iframeWrapper.style.width = this._width / this._stageW * this._displayW + "px";}}}public get height():number {return this._height;}public set height(value:number) {this._height = value;if(this._scaleMode==egret.StageScaleMode.FIXED_WIDTH || this._scaleMode==egret.StageScaleMode.FIXED_HEIGHT ) {this._iframe.height=this._height/this._stageH*this._windowH+"px";this._iframeWrapper.style.height=this._height/this._stageH*this._windowH+"px";}if(this._scaleMode==egret.StageScaleMode.SHOW_ALL || this._scaleMode==egret.StageScaleMode.NO_BORDER ) {if(this._windowH==this._displayH){this._iframe.style.height = this._height / this._stageH * this._windowH + "px";this._iframeWrapper.style.height = this._height / this._stageH * this._windowH + "px";}else{this._iframe.style.height = this._height / this._stageH * this._displayH + "px";this._iframeWrapper.style.height = this._height / this._stageH * this._displayH + "px";}}}public set x(value:number) {this._x = value;if(this._scaleMode==egret.StageScaleMode.FIXED_WIDTH || this._scaleMode==egret.StageScaleMode.FIXED_HEIGHT) {this._iframeWrapper.style.left = this._x / this._stageW * this._windowW + "px";}if(this._scaleMode==egret.StageScaleMode.SHOW_ALL || this._scaleMode==egret.StageScaleMode.NO_BORDER ) {if(this._windowW==this._displayW){this._iframeWrapper.style.left = this._x / this._stageW * this._windowW + "px";}else{this._iframeWrapper.style.left = this._x / this._stageW * this._displayW + "px";}}}public set y(value:number) {this._y = value;if(this._scaleMode==egret.StageScaleMode.FIXED_WIDTH || this._scaleMode==egret.StageScaleMode.FIXED_HEIGHT ) {this._iframeWrapper.style.top = this._y / this._stageH * this._windowH + "px";}if(this._scaleMode==egret.StageScaleMode.SHOW_ALL || this._scaleMode==egret.StageScaleMode.NO_BORDER){if(this._windowH==this._displayH){this._iframeWrapper.style.top = this._y / this._stageH * this._windowH + "px";}else{this._iframeWrapper.style.top =this._y / this._stageH * this._displayH + "px";}}}public get x():number {return this._x;}public get y():number {return this._y;}public get src():string {return this._src;}public set src(value:string) {this._src = value;} }使用方式:
var webview=new WebView("http://www.sina.com"); webview.x=100; webview.y=100; webview.width = 500; webview.height = 800; webview.show();效果:
5. 后續
考慮屏幕大小改變、屏幕旋轉以及單頁面多Webplay實例的情形
總結
以上是生活随笔為你收集整理的[原创]egret的WebView实现(基于egret2.5)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Bert源代码(二)模型
- 下一篇: 海康录制视频文件无法播放以及FFmpeg