Flutter WebView中evaluateJavascript使用报错及解决咨询
我在Flutter WebView里尝试用evaluateJavascript方法隐藏页面顶部栏,但一直报错。官方文档讲得不够清楚,我在onPageFinished回调里测试了evaluateJavascript和loadUrl两种方式,结果都提示无法获取指定类名DOM元素的style属性。
报错日志
Syncing files to device iPhone X... flutter: Page finished loading: https://stackoverflow.com/ [VERBOSE-2:ui_dart_state.cc(148)] Unhandled Exception: PlatformException(evaluateJavaScript_failed, Failed evaluating JavaScript, JavaScript string was: 'javascript:(function() { var head = document.getElementsByClassName('top-bar js-top-bar top-bar__network _fixed')[0].style.display='none'; })()' Error Domain=WKErrorDomain Code=4 "A JavaScript exception occurred" UserInfo={WKJavaScriptExceptionLineNumber=1, WKJavaScriptExceptionMessage=TypeError: undefined is not an object (evaluating 'document.getElementsByClassName('top-bar js-top-bar top-bar__network _fixed')[0].style'), WKJavaScriptExceptionColumnNumber=117, WKJavaScriptExceptionSourceURL=https://stackoverflow.com/, NSLocalizedDescription=A JavaScript exception occurred}) #0 StandardMethodCodec.decodeEnvelope (package:flutter/src/services/message_codecs.dart:564:7) #1 MethodChannel.invokeMethod (package:flutter/src/services/platform_channel.dart:302:33) <asynchronous suspension> #2 WebViewController.evaluateJavascript (package:web<…> Application finished.
相关代码
class _WebViewExampleState extends State<WebViewExample> { WebViewController _myController; bool _loadedPage = false; @override void initState() { super.initState(); setState(() { _loadedPage = false; }); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( backgroundColor: Colors.green, title: const Text( 'Stackoverflow', style: TextStyle(fontSize: 14), ), ), body: Builder(builder: (BuildContext context) { return new Stack( children: <Widget>[ new WebView( initialUrl: 'https://stackoverflow.com', javascriptMode: JavascriptMode.unrestricted, onWebViewCreated: (controller){ _myController = controller; }, javascriptChannels: <JavascriptChannel>[ _toasterJavascriptChannel(context), ].toSet(), onPageFinished: (url){ print('Page finished loading: $url'); _myController.evaluateJavascript("javascript:(function() { " + "var head = document.getElementsByClassName('top-bar js-top-bar top-bar__network _fixed')[0].style.display='none'; " + "})()"); /* _myController.loadUrl("javascript:(function() { " + "var head = document.getElementsByClassName('top-bar js-top-bar top-bar__network _fixed')[0].style.display='none'; " + "})()"); */ setState(() { _loadedPage = true; }); }, ), _loadedPage == false ? new Center( child: new CircularProgressIndicator( backgroundColor: Colors.green), ) : new Container(), ], ); }), ); } JavascriptChannel _toasterJavascriptChannel(BuildContext context) { return JavascriptChannel( name: 'Toaster', onMessageReceived: (JavascriptMessage message) { Scaffold.of(context).showSnackBar( SnackBar(content: Text(message.message)), ); }); } }
核心问题原因
报错里的TypeError: undefined is not an object说明你直接访问了undefined的style属性——也就是说,document.getElementsByClassName('top-bar js-top-bar top-bar__network _fixed')[0]返回了undefined,页面上找不到这个类名的元素。
为什么onPageFinished触发了还找不到?因为onPageFinished只是标记页面的主资源加载完成,但很多现代网站会用JavaScript动态渲染DOM元素(比如Stack Overflow的顶部栏可能是异步加载的),这时候主页面加载完成了,但目标元素还没出现在DOM里。另外,你写的类名里_fixed可能是滚动后才动态添加的,这也会导致找不到元素。
解决步骤
1. 先检查元素是否存在,避免直接访问索引
修改你的JavaScript代码,先判断元素是否存在再操作,这样就不会抛出异常:
_myController.evaluateJavascript(""" (function() { var topBar = document.getElementsByClassName('top-bar js-top-bar top-bar__network')[0]; if (topBar) { topBar.style.display = 'none'; } })() """);
这里去掉了_fixed类,用前面几个固定的类名更可靠。
2. 延迟执行,确保元素加载完成
如果元素是异步渲染的,可以用setTimeout延迟几百毫秒再执行,给页面足够的时间渲染元素:
_myController.evaluateJavascript(""" setTimeout(function() { var topBar = document.querySelector('.top-bar.js-top-bar'); if (topBar) { topBar.style.display = 'none'; } }, 500); """);
这里用了querySelector,语法更简洁,选择器也更灵活。
3. 用MutationObserver监听元素出现(更可靠)
如果延迟执行还是不稳定,可以用MutationObserver监听DOM变化,直到目标元素出现再执行隐藏操作:
_myController.evaluateJavascript(""" (function() { var observer = new MutationObserver(function(mutations) { var topBar = document.querySelector('.top-bar.js-top-bar'); if (topBar) { topBar.style.display = 'none'; observer.disconnect(); // 找到元素后停止监听 } }); // 监听body的子元素变化 observer.observe(document.body, { childList: true, subtree: true }); })() """);
这种方式可以精准捕捉到元素出现的时机,不会因为延迟时间设置不当而失效。
4. 确认WebView的JavaScript模式开启
你已经设置了javascriptMode: JavascriptMode.unrestricted,这部分是对的,确保这个配置不要漏掉,否则JavaScript代码根本无法执行。
内容的提问来源于stack exchange,提问作者Nick

