I am having a HTML file that is loading on webView, and it has different sections or anchors for eg. How can we identify that we have webView has scrolled at a particular section.
1 Answers
I'll share a general algorithm, without a specific (and tested :) ) implementation.
We know when webView
has scrolled by overriding a delegate's method:
func registerForScrollUpdates() {
webView!.scrollView.delegate = self
}
// MARK: UIScrollViewDelegate implementation
- (void)scrollViewDidScroll:(UIScrollView *)scrollView {
}
We can get visible DOM elements, e.g. using this approach: https://stackoverflow.com/a/44612174/2567725
// Get all elements on the page (change this to another DOM element if you want)
var all = document.getElementsByTagName("*");
for (var i = 0, max = all.length; i < max; i++) {
if (isHidden(all[i]))
// hidden
else
// visible
}
function isHidden(el) {
var style = window.getComputedStyle(el);
return ((style.display === 'none') || (style.visibility === 'hidden'))
}
Now, we can iterate visible elements and compare a class/id/whatever with the target elements' class/id/whatever.
Or, if you are interested in a specific element and it's not possible to "edit" DOM by adding additional tags, you can store a "path to root" of a given DOM element.
This JS code uniquely identifies the node in tree.
function nodePosRelativeToSiblings(dom, node) {
var prop = dom.body.previousElementSibling ? 'previousElementSibling' : 'previousSibling';
var i = 0;
while (node = node[prop]) { ++i }
return i;
}
function getNextSibling(dom, node) {
var prop = dom.body.nextElementSibling ? 'nextElementSibling' : 'nextSibling';
return node[prop];
}
function indentifyNodeByPath(dom, path) {
var parentNode = dom.body;
for (var i=0; i<path.length; i++) {
var path_i = path[i];
children.length=${parentNode.children.length}, path_i=${path_i}`);
// this is an ungly (an unsafe) quick-fix:
// window.selection() returns a Range with Node which is not Element, but Text.
// because Safari a Gesko-based browser.
// So, ".chilren" can contain much less elements than ".childNodes"
// our puspose is to keep backward-forward calculation consistent!
if ( path_i < parentNode.children.length )
{
parentNode = parentNode.children[path_i];
}
}
return parentNode;
}
// returns an array (pos to root, pos to siblings)
function getPathToRootNode(dom, node, path) {
if (node.parentNode == null || node.constructor.name == "HTMLBodyElement") {
return;
}
var posToRelative = nodePosRelativeToSiblings(dom, node);
path.push(posToRelative);
return getPathToRootNode(dom, node.parentNode, path);
}
So, in scrollViewDidScroll
you can get the list of all "target" elements which are currently visible, by calling:
webView.evaluateJavaScript("getVisibleTargetElements();") { (result, error) in
print("script evaluated")
}
where getVisibleTargetElements
is JS func like
func getVisibleTargetElements() {
window.alert(`TARGET_ELEMENTS_${el1}_${el2}_${el3}`);
}
and iOS will get this alert
func webView(_ webView: WKWebView, runJavaScriptAlertPanelWithMessage message: String, initiatedByFrame frame: WKFrameInfo, completionHandler: @escaping () -> Void) {
completionHandler();
if message.starts(with: "TARGET_ELEMENTS_") {
// parse the message
}
}
Please note that using window.alert
instead of the callback in webView.evaluateJavaScript("getVisibleTargetElements();")
will allow us to call this getVisibleTargetElements
even from JS and still receive the result in if message.starts(with: "TARGET_ELEMENTS_")
. So it's more ugly, but more flexible. :)

- 2,132
- 1
- 18
- 39