【问题标题】:JavaFX WebView: how to add HyperLink listener to usemap / area?JavaFX WebView:如何将 HyperLink 侦听器添加到 usemap/area?
【发布时间】:2020-04-02 22:09:16
【问题描述】:

在使用 WebView 时,有没有办法为 <img> 标签添加伪 HyperlinkListener,利用 usemap 属性将点击指向地图?

例如,在来自w3schools的以下HTML中,

<h1>The map and area elements</h1>

<p>Click on the sun or on one of the planets to watch it closer:</p>

<img src="planets.gif" width="145" height="126" alt="Planets" usemap="#planetmap">

<map name="planetmap">
  <area shape="rect" coords="0,0,82,126" alt="Sun" href="sun.htm">
  <area shape="circle" coords="90,58,3" alt="Mercury" href="mercur.htm">
  <area shape="circle" coords="124,58,8" alt="Venus" href="venus.htm">
</map>

有没有办法在用户点击“Sun”元素时获取事件监听器?

相关:带有&lt;a&gt;标签、HyperlinkListener in JavaFX WebEngine的超链接监听器的解决方案。

【问题讨论】:

    标签: java javafx webview


    【解决方案1】:

    我可以通过以下步骤做到这一点:

    • 等待网页加载。
    • 使用 XPath 定位 &lt;img&gt; 元素。
    • 使用&lt;img&gt;usemap 值来定位对应的&lt;map&gt; 元素。
    • 获取&lt;map&gt; 的所有&lt;area&gt; 子代的列表。
    • &lt;map&gt; 元素添加一个点击监听器,它:
      • 使用 JavaScript getBoundingClientRect() 调用来检索 &lt;img&gt; 元素相对于视口的边界。
      • 获取鼠标点击相对于视口的坐标。
      • 迭代&lt;area&gt; 子元素,并为每个子元素将coords 属性解析为值列表,然后创建与shape 属性对应的Shape。
      • 调整鼠标点击坐标,使其相对于&lt;img&gt; 的原点。
      • 检查鼠标点击坐标是否在形状内。

    这是一个完整的演示:

    import java.net.URI;
    
    import javax.xml.xpath.XPath;
    import javax.xml.xpath.XPathConstants;
    import javax.xml.xpath.XPathFactory;
    import javax.xml.xpath.XPathException;
    
    import org.w3c.dom.Document;
    import org.w3c.dom.Element;
    import org.w3c.dom.NodeList;
    import org.w3c.dom.events.EventTarget;
    import org.w3c.dom.events.MouseEvent;
    
    import javafx.application.Application;
    import javafx.concurrent.Worker;
    import javafx.geometry.Insets;
    import javafx.stage.Stage;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.layout.BorderPane;
    
    import javafx.scene.shape.Shape;
    import javafx.scene.shape.Circle;
    import javafx.scene.shape.Rectangle;
    import javafx.scene.shape.Polygon;
    
    import javafx.scene.web.WebView;
    import javafx.scene.web.WebEngine;
    
    import netscape.javascript.JSObject;
    
    public class WebViewImageMapTest
    extends Application {
    
        /**
         * A single coordinate in the list which comprises the value of
         * the {@code coords} attribute of an {@code <area>} element.
         */
        private static class Coordinate {
            final double value;
            final boolean percentage;
    
            Coordinate(double value,
                       boolean percentage) {
                this.value = value;
                this.percentage = percentage;
            }
    
            double resolveAgainst(double size) {
                return percentage ? value * size / 100 : value;
            }
    
            static Coordinate parse(String s) {
                if (s.endsWith("%")) {
                    return new Coordinate(
                        Double.parseDouble(s.substring(0, s.length() - 1)), true);
                } else {
                    return new Coordinate(Double.parseDouble(s), false);
                }
            }
    
            @Override
            public String toString() {
                return getClass().getName() +
                    "[" + value + (percentage ? "%" : "") + "]";
            }
        }
    
        @Override
        public void start(Stage stage) {
            WebView view = new WebView();
    
            Label destination = new Label(" ");
            destination.setPadding(new Insets(12));
    
            WebEngine engine = view.getEngine();
            engine.getLoadWorker().stateProperty().addListener((o, old, state) -> {
                if (state != Worker.State.SUCCEEDED) {
                    return;
                }
    
                Document doc = engine.getDocument();
                try {
                    XPath xpath = XPathFactory.newInstance().newXPath();
    
                    Element img = (Element)
                        xpath.evaluate("//*[local-name()='img']",
                            doc, XPathConstants.NODE);
    
                    String mapURI = img.getAttribute("usemap");
                    String mapID = URI.create(mapURI).getFragment();
                    Element map = doc.getElementById(mapID);
                    if (map == null) {
                        // No <map> with matching id.
                        // Look for <map> with matching name instead.
                        map = (Element) xpath.evaluate(
                            "//*[local-name()='map'][@name='" + mapID + "']",
                            doc, XPathConstants.NODE);
                    }
    
                    NodeList areas = (NodeList)
                        xpath.evaluate("//*[local-name()='area']",
                            map, XPathConstants.NODESET);
    
                    ((EventTarget) map).addEventListener("click", e -> {
                        Element area = getClickedArea(
                            (MouseEvent) e, areas, getClientBounds(img, engine));
    
                        if (area != null) {
                            destination.setText(area.getAttribute("href"));
                        } else {
                            destination.setText(" ");
                        }
                    }, false);
                } catch (XPathException e) {
                    e.printStackTrace();
                }
            });
    
            engine.load(
                WebViewImageMapTest.class.getResource("imgmap.html").toString());
    
            stage.setScene(new Scene(
                new BorderPane(view, null, null, destination, null)));
            stage.setTitle("Image Map Test");
            stage.show();
        }
    
        /**
         * Returns the bounds of an element relative to the viewport.
         */
        private Rectangle getClientBounds(Element element,
                                          WebEngine engine) {
    
            JSObject window = (JSObject) engine.executeScript("window");
            window.setMember("desiredBoundsElement", element);
            JSObject bounds = (JSObject) engine.executeScript(
                "desiredBoundsElement.getBoundingClientRect();");
    
            Number n;
            n = (Number) bounds.getMember("x");
            double x = n.doubleValue();
            n = (Number) bounds.getMember("y");
            double y = n.doubleValue();
            n = (Number) bounds.getMember("width");
            double width = n.doubleValue();
            n = (Number) bounds.getMember("height");
            double height = n.doubleValue();
    
            return new Rectangle(x, y, width, height);
        }
    
        private Element getClickedArea(MouseEvent event,
                                       NodeList areas,
                                       Rectangle imgClientBounds) {
    
            int clickX = event.getClientX();
            int clickY = event.getClientY();
    
            double imgX = imgClientBounds.getX();
            double imgY = imgClientBounds.getY();
            double imgWidth = imgClientBounds.getWidth();
            double imgHeight = imgClientBounds.getHeight();
    
            int count = areas.getLength();
            for (int i = 0; i < count; i++) {
                Element area = (Element) areas.item(i);
    
                String shapeType = area.getAttribute("shape");
                if (shapeType == null) {
                    shapeType = "";
                }
    
                String[] rawCoords = area.getAttribute("coords").split(",");
                int numCoords = rawCoords.length;
                Coordinate[] coords = new Coordinate[numCoords];
                for (int c = 0; c < numCoords; c++) {
                    coords[c] = Coordinate.parse(rawCoords[c].trim());
                }
    
                Shape shape = null;
                switch (shapeType) {
                    case "rect":
                        double left = coords[0].resolveAgainst(imgWidth);
                        double top = coords[1].resolveAgainst(imgHeight);
                        double right = coords[2].resolveAgainst(imgWidth);
                        double bottom = coords[3].resolveAgainst(imgHeight);
                        shape = new Rectangle(
                            left, top, right - left, bottom - top);
                        break;
                    case "circle":
                        double centerX = coords[0].resolveAgainst(imgWidth);
                        double centerY = coords[1].resolveAgainst(imgHeight);
                        double radius = coords[2].resolveAgainst(
                            Math.min(imgWidth, imgHeight));
                        shape = new Circle(centerX, centerY, radius);
                        break;
                    case "poly":
                        double[] polygonCoords = new double[coords.length];
                        for (int c = polygonCoords.length - 1; c >= 0; c--) {
                            polygonCoords[c] = coords[c].resolveAgainst(
                                c % 2 == 0 ? imgWidth : imgHeight);
                        }
                        shape = new Polygon(polygonCoords);
                        break;
                    default:
                        shape = new Rectangle(imgWidth, imgHeight);
                        break;
                }
    
                if (shape.contains(clickX - imgX, clickY - imgY)) {
                    return area;
                }
            }
    
            return null;
        }
    
        public static class Main {
            public static void main(String[] args) {
                Application.launch(WebViewImageMapTest.class, args);
            }
        }
    }
    

    这是我使用的 planets.gif:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-07-13
      • 2023-03-25
      • 2016-08-21
      • 2018-10-27
      • 2022-08-03
      • 2013-02-17
      • 2011-09-28
      • 1970-01-01
      相关资源
      最近更新 更多