惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

酷 壳 – CoolShell
酷 壳 – CoolShell
H
Hacker News: Front Page
P
Palo Alto Networks Blog
T
ThreatConnect
Apple Machine Learning Research
Apple Machine Learning Research
博客园_首页
T
True Tiger Recordings
P
Privacy & Cybersecurity Law Blog
B
Blog
IT之家
IT之家
Last Week in AI
Last Week in AI
F
Full Disclosure
Hacker News: Ask HN
Hacker News: Ask HN
C
Comments on: Blog
Microsoft Azure Blog
Microsoft Azure Blog
C
Cybersecurity and Infrastructure Security Agency CISA
Microsoft Security Blog
Microsoft Security Blog
博客园 - 【当耐特】
N
News and Events Feed by Topic
NISL@THU
NISL@THU
腾讯CDC
雷峰网
雷峰网
Security Latest
Security Latest
李成银的技术随笔
M
Microsoft Research Blog - Microsoft Research
L
LangChain Blog
L
Lohrmann on Cybersecurity
cs.CL updates on arXiv.org
cs.CL updates on arXiv.org
C
Check Point Blog
Y
Y Combinator Blog
Recent Announcements
Recent Announcements
博客园 - Franky
N
News | PayPal Newsroom
V
V2EX
A
About on SuperTechFans
The Register - Security
The Register - Security
月光博客
月光博客
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
Google Online Security Blog
Google Online Security Blog
MyScale Blog
MyScale Blog
Cisco Talos Blog
Cisco Talos Blog
Vercel News
Vercel News
WordPress大学
WordPress大学
C
Cyber Attacks, Cyber Crime and Cyber Security
The Hacker News
The Hacker News
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
爱范儿
爱范儿
A
Arctic Wolf
L
LINUX DO - 最新话题
freeCodeCamp Programming Tutorials: Python, JavaScript, Git & More

博客园 - LoveCoder

SQL Server查询一个很大字段varchar字段的字符串被截断问题 sql server 每个表占用大小查询【转】 电商ERP系统源码出售 kubernetes镜像拉取失败解决方法 ErrImagePull 淘宝虚拟商品自动发货接口 如何排查线上w3wp.exe CPU高的问题,使用到了WinDbg、Visual studio来分析IIS进程池的.dmp文件 sql server 将数据库表里面的数据,转为insert语句,方便小批量转移数据 淘宝库存更新修改接口 淘宝开放平台上货接口,淘宝商品发布接口 C# 模拟http请求出现 由于系统缓冲区空间不足或队列已满,不能执行套接字上的操作[windows服务器] 淘宝订单信息获取接口API,淘宝打单发货接口 使用win-acme在windows+iis服务器下配置自动续期SSL证书【转】 开发Android应用程序,在Android10的系统上提示网络出错? android 反编译APK取源代码。 .net7(.net core) 依赖注入:从 AddSingleton 注册的类里面访问 AddScoped 的问题 在.net core使用Serilog,只要简单的三步 淘宝订单信息获取接口API,淘宝打单发货接口 淘宝订单信息获取接口,淘宝订单信息获取API win10远程登录的账号密码
一个HTML小工具,可以显示你在网页上触摸点击的轨迹、位置、按压时长以及你触发的所有JS事件
LoveCoder · 2026-03-26 · via 博客园 - LoveCoder

IOS模拟点击的时候发现没作用,怀疑是坐标不对,想找个工具来显示下我当前点击的位置,各种搜在线工具,都没有找到,结果把需求告诉ChatGPT让它帮我写,瞬间就写对了,一行代码都不需要改的,AI真的太强大了,记录一下,方便以后万一用到。

现有的功能有:JS捕获所有触摸、click、keyup和keyon等事件并打印坐标、按压时长等信息(里面还有一个输入框和登录框是我为了模拟真实环境写的)

我是在Appium+wda模拟点击safari加载的网页上的按钮没起作用,原因我一开始以为是JS像素点与触摸坐标转换问题,通过这个工具,我发现我获取到按钮的坐标传入后,点击的位置在按钮上面,瞬间想到有可能是顶部灵动岛高度问题,后来仔细研究发现:Appium+wda在webview下获取的控件位置是指浏览器可视区开始(不包括灵动岛),而模拟触摸方式的坐标是从手机屏幕的左上角开始,所以控件的高+灵动岛的高度44,就可以了准确点击到控件了

以下是代码,保存成.html文件,访问就可以看到效果

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>触摸行为监控</title>
    <style>
        body {
            margin: 0;
            font-family: Arial, sans-serif;
            background: #111;
            color: #0f0;
        }

        #log {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            height: 160px;
            overflow-y: auto;
            background: rgba(0,0,0,0.9);
            font-size: 12px;
            padding: 8px;
            z-index: 999;
        }

        #canvas {
            position: absolute;
            top: 160px;
            left: 0;
        }

        #bottomBar {
            position: fixed;
            bottom: 0;
            left: 0;
            right: 0;
            height: 60px;
            background: #222;
            display: flex;
            align-items: center;
            padding: 8px;
            gap: 8px;
        }

        #input {
            flex: 1;
            height: 40px;
            font-size: 16px;
            padding: 0 8px;
        }

        #btn {
            width: 100px;
            height: 40px;
            font-size: 16px;
        }

        #toast {
            position: fixed;
            bottom: 80px;
            left: 50%;
            transform: translateX(-50%);
            background: rgba(0,0,0,0.8);
            color: #0f0;
            padding: 10px 16px;
            border-radius: 6px;
            display: none;
            z-index: 1000;
        }
    </style>
</head>
<body>

<div id="log"></div>
<canvas id="canvas"></canvas>

<div id="bottomBar">
    <input id="input" placeholder="请输入内容" />
    <button id="btn">登录</button>
</div>

<div id="toast">登录按钮已点击</div>

<script>
    const logDiv = document.getElementById('log');
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    const toast = document.getElementById('toast');

    const bottomBarHeight = 60;

    function resize() {
        canvas.width = window.innerWidth;
        canvas.height = window.innerHeight - 160 - bottomBarHeight;
    }

    resize();
    window.addEventListener('resize', resize);

    let startTime = 0;
    let path = [];

    const keyDownMap = {};

    function log(msg) {
        const el = document.createElement('div');
        el.innerText = msg;
        logDiv.prepend(el);
    }

    function showToast(msg) {
        toast.innerText = msg;
        toast.style.display = 'block';
        setTimeout(() => {
            toast.style.display = 'none';
        }, 1000);
    }

    function drawPath() {
        if (path.length < 2) return;

        ctx.beginPath();
        ctx.moveTo(path[0].x, path[0].y);

        for (let i = 1; i < path.length; i++) {
            ctx.lineTo(path[i].x, path[i].y);
        }

        ctx.strokeStyle = 'lime';
        ctx.lineWidth = 2;
        ctx.stroke();
    }

    function drawPoint(x, y) {
        ctx.beginPath();
        ctx.arc(x, y, 6, 0, Math.PI * 2);
        ctx.fillStyle = 'red';
        ctx.fill();
    }

    function handleStart(e) {
        const touch = e.touches ? e.touches[0] : e;

        startTime = Date.now();
        path = [];

        ctx.clearRect(0, 0, canvas.width, canvas.height);

        log(`START | type=${e.type} | x=${touch.clientX} | y=${touch.clientY}`);

        drawPoint(touch.clientX, touch.clientY - 160);
    }

    function handleMove(e) {
        const touch = e.touches ? e.touches[0] : e;

        const x = touch.clientX;
        const y = touch.clientY - 160;

        path.push({ x, y });

        ctx.clearRect(0, 0, canvas.width, canvas.height);
        drawPath();

        log(`MOVE | type=${e.type} | x=${touch.clientX} | y=${touch.clientY}`);
    }

    function handleEnd(e) {
        const duration = Date.now() - startTime;
        log(`END | type=${e.type} | duration=${duration}ms | points=${path.length}`);
    }

    function handleClick(e) {
        log(`CLICK | x=${e.clientX} | y=${e.clientY}`);
        drawPoint(e.clientX, e.clientY - 160);
    }

    function handleFocus(e) {
        log(`FOCUS | target=${e.target.tagName}`);
    }

    function handleKeyDown(e) {
        keyDownMap[e.code] = Date.now();
        log(`KEYDOWN | key=${e.key} | code=${e.code}`);
    }

    function handleKeyUp(e) {
        const now = Date.now();
        const start = keyDownMap[e.code] || now;
        const duration = now - start;
        log(`KEYUP | key=${e.key} | code=${e.code} | duration=${duration}ms`);
    }

    function handleInput(e) {
        log(`INPUT | value=${e.target.value}`);
    }

    // 登录按钮点击提示(核心新增)
    document.getElementById('btn').addEventListener('click', function(e) {
        const btn = document.getElementById('btn');
        const input = document.getElementById('input');

        const btnRect = btn.getBoundingClientRect();
        const inputRect = input.getBoundingClientRect();

        const msg = `LOGIN BUTTON CLICKED | BTN[x=${btnRect.left.toFixed(1)},y=${btnRect.top.toFixed(1)},w=${btnRect.width.toFixed(1)},h=${btnRect.height.toFixed(1)}] | INPUT[x=${inputRect.left.toFixed(1)},y=${inputRect.top.toFixed(1)},w=${inputRect.width.toFixed(1)},h=${inputRect.height.toFixed(1)}]`;

        log(msg);

        showToast(`按钮坐标:(${btnRect.left.toFixed(0)},${btnRect.top.toFixed(0)}) 输入框:(${inputRect.left.toFixed(0)},${inputRect.top.toFixed(0)})`);

        // 在画布上标出按钮中心点
        const btnCenterX = btnRect.left + btnRect.width / 2;
        const btnCenterY = btnRect.top + btnRect.height / 2 - 160;

        drawPoint(btnCenterX, btnCenterY);
    });

    // Touch
    document.addEventListener('touchstart', handleStart);
    document.addEventListener('touchmove', handleMove);
    document.addEventListener('touchend', handleEnd);

    // Mouse
    document.addEventListener('mousedown', handleStart);
    document.addEventListener('mousemove', handleMove);
    document.addEventListener('mouseup', handleEnd);

    // Click
    document.addEventListener('click', handleClick);

    // Focus
    document.addEventListener('focus', handleFocus, true);

    // Keyboard
    document.addEventListener('keydown', handleKeyDown);
    document.addEventListener('keyup', handleKeyUp);

    // Input
    document.getElementById('input').addEventListener('input', handleInput);

    document.addEventListener('touchmove', function(e) {
        if (e.target.id !== 'input') {
            e.preventDefault();
        }
    }, { passive: false });

</script>

</body>
</html>