日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Flutter Web:Shadow Root问题

發布時間:2024/4/15 编程问答 42 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter Web:Shadow Root问题 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

document.getElementById找不到節點

在flutter1.x版本的dev分支上可以使用flutter web,但是我們在使用第三方js sdk的時候會出現問題,比如AgoraRtc、lottie等。

問題都是出現在document.getElementById,因為這些sdk中或者使用的時候需要通過這個方法獲取節點來操作,比如lottie,我們封裝的代碼如下:

... class LottieWidget extends StatefulWidget{String path;bool isLoop;bool isAutoPlay;double width;double height;...LottieWidget(this.path, this.width, this.height, this.isAutoPlay, this.isLoop, this._animationListener);@overrideState<StatefulWidget> createState() {return _lottieWidget;}void lottiePlay() {_lottieWidget.lottiePlay();}void lottieStop() {_animationListener = null;_lottieWidget.lottieStop();} }class _LottieWidget extends State<LottieWidget> {@overrideWidget build(BuildContext context) {js.context["lottieLoaded"] = lottieLoaded;DivElement divElement = DivElement();divElement.id = "lottie_anim";StyleElement styleElement = StyleElement();styleElement.type = "text/css";styleElement.innerHtml = """html,body {}""";divElement.append(styleElement);var script = """var lottieAnim = document.getElementById("lottie_anim");var lottieObj = lottie.loadAnimation({container:lottieAnim,renderer: 'svg',loop:${widget.isLoop},autoplay:${widget.isAutoPlay},path:"assets/${widget.path}"});// 動畫播放完成觸發lottieObj.addEventListener('complete', lottieLoaded);var lottiePlay = function(){lottieObj.play();}var lottiePause = function(){lottieObj.pause(); }var lottieStop = function() {lottieObj.stop();}""";ScriptElement scriptElement = new ScriptElement();scriptElement.innerHtml = script;divElement.append(scriptElement);String _divId = "lottieanim" + DateTime.now().toIso8601String();ui.platformViewRegistry.registerViewFactory(_divId,(int viewId) => divElement,);Widget _iframeWidget = HtmlElementView(key: UniqueKey(),viewType: _divId,);return SizedBox(child: _iframeWidget, width: widget.width, height: widget.height,);}void lottiePlay() {js.context.callMethod("lottiePlay");}void lottieStop() {js.context.callMethod("lottieStop");}void lottiePause() {js.context.callMethod("lottiePause");}// 動畫播放完成觸發void lottieLoaded() {print("loaded");widget._animationListener?.call();}@overridevoid dispose() {super.dispose();lottieStop();widget._animationListener = null;} }

可以看到我們將一個id為lottie_anim的div添加到頁面中,然后在js代碼中通過document.getElementById獲取這個節點,并設置到lottie中,這樣lottie的sdk中就會在這個div上繪制動畫。

但是執行的時候發現動畫根本沒顯示,而且沒有報錯。通過在js中打印日志逐行測試發現document.getElementById(“lottie_anim”)獲取到的是null。但是為什么是null的呢?

我們運行后打開chrome開發者工具,在Elements欄中查找lottie_anim,發現可以找到,但是它的位置如下:

可以看到這個div是在一個shadow-root下。

那么這個是做什么用的?

Shadow Dom

shadow dom簡單來說就是封裝,就是將一個組件封裝起來,同時設置了隔離,外界無法訪問內部的節點。比如video,我們使用的時候非常簡單:

<video src="" id='test'></video>

但是當我們打開開發者工具,在設置中將show user agent shadow DOM選中后,在回頭看Elements中的節點,就會發現video下面存在一個shadow-root,在下面有很多節點,包括播放按鈕、播放時長、進度條等等。

關于Shadow DOM,我參考的是http://quanzhan.applemei.com/webStack/TkRBeE1RPT0=

正是因為Shadow DOM隱藏的這種特性,導致了上面的問題。因為在flutter中,我們用HtmlElementView來展示html組件,這些組件都會被放在Shadow DOM中,所以導致了在js中通過document.getElementById獲取的都是null,也就導致了很多第三方sdk無法正常使用。

解決問題

我們發現了問題,但是如何去解決呢?

其實可以獲取Shadow DOM中的節點,只不過要復雜一點。首先我們看上面的節點信息,在Shadow DOM外一層是一個flt-platform-view的節點,這個我們是可以直接獲取到的,通過getElementsByTagName,因為頁面上可以會有多個flt-platform-view,所以這是一個array,,如下:

我們這里其實只有一個flt-platform-view,所以array里只有一個flt-platform-view節點。然后我們獲取它的shadowRoot就可以得到Shadow節點,再通過getElementById來獲取我們需要的節點即可,如下:

var sroot = document.getElementsByTagName("flt-platform-view")[0].shadowRoot; var lootiedom = sroot.getElementById('lottie_anim');

這樣就可以得到相應的節點,替換上面js第一行代碼就可以正常顯示了。

其實在網上也有很多人遇到了同樣的問題,比如https://blog.csdn.net/thunder_sz/article/details/111413043 ,官方也創建了一個對應的issues:https://github.com/flutter/flutter/issues/50452 。

里面有人提到了通過slot來解決這個問題,目前我還沒有研究明白怎么處理。另外還提到了在flutter2.0上已經解決了該問題,下面我們來聊聊。

Flutter2.0上的Shadow DOM

其實issues也說了,在flutter2.0上只有Canvas Kit解決了這個問題。那么這又是什么?

之前我們在解決image跨域的問題時提到過,flutter有兩種渲染模式:CanvasKit和Html(詳細介紹見【flutter web:網絡圖片(圓形圖片)、HTML renderer(解決大量圖片卡)】)。

在flutter2.0之后,在瀏覽器中默認使用的就是CanvasKit這種渲染模式,而這種模式就不存在Shadow DOM的問題。運行后節點如下:

可以看到整體結構變化了,沒有了Shadow DOM,所以可以直接獲取到該節點,這樣就不存在問題了。

但是這種模式下存在Image加載網絡圖片跨域的問題(同樣見上面提到的文章),官方給出的解決方案是用html來代替Image,通過圖片過多時要使用html render。這樣就又回到了之前的問題上了,還需要通過上面的處理來解決。

其實只要解決了Image跨域的問題,還是建議最好使用Canvas Kit來渲染,因為Html Render存在不少問題,比如在debug下不停的打印日志導致非??ǖ葐栴}。

同時加載多個HtmlElementView導致失效

因為在項目中,我們可能會同時顯示兩個HtmlElementView,比如在直播過程(AgoraRtc)中顯示動畫(lottie),這樣如果通過上面的處理就會出現問題。因為上面我們得到的array中都取第一個,這樣其實第二個獲取的節點是錯誤的。但是我們又無法確定哪個是第幾個,怎么處理?

目前我想到的方法就是遍歷,因為我們設置的每個div的id是不同的,所以通過遍歷來找到一個即可,代碼如下:

var lottieAnim = document.getElementById("lottie_anim"); //渲染模式如果是html,則不能直接這么獲取;如果是canvas則可以 var roots = document.getElementsByTagName("flt-platform-view");for(var i = 0; i < roots.length; i++){var tmp = roots[i].shadowRoot.getElementById("lottie_anim");if(tmp){lottieAnim = tmp;}}

這里第一步先正常獲取,如果獲取不到再通過flt-platform-view獲取,并遍歷找到節點即可。

AgoraRtc和aliplayer的處理

lottie處理起來比較簡單,因為獲取節點的操作是我們自己的代碼,但是其他sdk就不一樣了,比如AgoraRtc和aliplayer是在sdk內部獲取節點的,這樣我們就需要修改他們的sdk。

AgoraRtc的代碼大約在4000多行,如下:

void 0 !== t.elementID ? (document.getElementById(t.elementID).appendChild(t.div), t.container = document.getElementById(t.elementID)) : (document.body.appendChild(t.div), t.container = document.body),t.parentNode = t.div.parentNode;var a = {video: {playerId: t.playerId,stateId: 0,playDeferTimeout: null,error: !1,status: "init",reason: null,updatedAt: Date.now()},audio: {playerId: t.playerId,stateId: 0,playDeferTimeout: null,error: !1,status: "init",reason: null,updatedAt: Date.now()}};

這里document.getElementById(t.elementID)就無法正常獲取,導致進入房間且已經訂閱成功,但是一直不顯示直播流。

通過上面的方法處理一下即可,如下:

var element = document.getElementsByTagName("flt-platform-view")[0].shadowRoot;void 0 !== t.elementID ? (element.getElementById(t.elementID).appendChild(t.div),t.container = element.getElementById(t.elementID)) : (document.body.appendChild(t.div),t.container = document.body),t.parentNode = t.div.parentNode;console.log(t.elementID);var a = {video: {..},audio: {...}};

這里沒有遍歷,因為我們進入直播場景第一個加載的一定是直播控件,所以它一定是第一個。但是其實保險起見,還是遍歷一下最好。

aliplayer就更復雜了,里面大量的使用了document,比如:

document.activeElement && document.activeElement !== e && document.activeElement.blur()

r = document.createEvent("MouseEvents")

這些都是無法正常執行的,因為我們的player被shadow封裝,所以document訪問不了。這就需要將所有的document替換成document.getElementsByTagName("flt-platform-view")[0].shadowRoot,為了方便可以新建一個全局變量方便替換。
這里也沒有使用遍歷,同樣因為在播放場景播放器一定是第一個加載。

總結

通過最后對兩個sdk的修改可以感覺到,每次這樣處理并不是很好的方法,如果要更新這些sdk就需要重新維護一次。所以最好的辦法就是在flutter2.0上使用CanvasKit渲染,當然前提是先解決Image跨域的問題,這個還需要后續研究。如果你的項目不涉及Image加載網絡圖片跨域,那么直接使用CanvasKit吧,這些js sdk不需要任何處理可以直接使用。

關注公眾號:BennuCTech,獲取更多干貨!

總結

以上是生活随笔為你收集整理的Flutter Web:Shadow Root问题的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。