程序员老王 发布的文章

方式一

const dragAndDrop = async (elem, startX, startY, distance) => {
    // 初始化鼠标事件
    const dispatchMouseEvent = (type, x, y) => {
        const event = new MouseEvent(type, {
            view: window,
            bubbles: true,
            cancelable: true,
            clientX: Math.floor(x),
            clientY: Math.floor(y),
            screenX: Math.floor(x + window.screenX),
            screenY: Math.floor(y + window.screenY),
            button: 0,
            // 当是 mousedown 或 mousemove 时,保持左键按下状态
            buttons: type === 'mouseup' ? 0 : 1,
        });
        elem.dispatchEvent(event);
    };

    // 贝塞尔曲线运动参数
    const duration = 800 + Math.random() * 400; // 随机持续时间
    const startTime = performance.now();
    const humanizeFactor = 0.3; // 人类操作随机因子

    // 触发初始按下事件
    dispatchMouseEvent('mousedown', startX, startY);

    // 使用requestAnimationFrame实现平滑动画
    return new Promise(resolve => {
        const animate = (currentTime) => {
            const elapsed = currentTime - startTime;
            const progress = Math.min(elapsed / duration, 1);

            // 应用缓动函数(三次贝塞尔曲线)
            const easedProgress = Math.sin(progress * Math.PI / 2);

            // 添加人类操作随机扰动
            const randomOffset = Math.random() * humanizeFactor * distance;
            const currentX = startX + (distance * easedProgress) + randomOffset;
            const currentY = startY + Math.sin(progress * Math.PI) * 5; // 自然Y轴波动

            dispatchMouseEvent('mousemove', currentX, currentY);

            if (progress < 1) {
                requestAnimationFrame(animate);
            } else {
                // 结束操作
                dispatchMouseEvent('mouseup', currentX, currentY);
                resolve();
            }
        };

        requestAnimationFrame(animate);
    });
};

// 优化后的启动函数
const simulateHorizontalDrag = async (slide, distance) => {
    const elem = document.querySelector(slide);
    if (!elem) {
        logger.error('selector:%s not found', slide);
        return;
    }

    const rect = elem.getBoundingClientRect();
    const startX = rect.left + rect.width * 0.3 + Math.random() * rect.width * 0.4;
    const startY = rect.top + rect.height * 0.3 + Math.random() * rect.height * 0.4;

    await dragAndDrop(elem, startX, startY, distance);
};

方式二

function dragandDrop(id, clientX, clientY, distance) {
    const elem = document.querySelector(id);
    let k = 0;
    iME(elem, "mousedown", 0, 0, clientX, clientY);
    const interval = setInterval(function () {
        k++;
        iME(elem, "mousemove", clientX + k, clientY, clientX + k, clientY);

        if (k >= distance) {
            clearInterval(interval);
            iME(elem, "mouseup", clientX + k, clientY, clientX + k, clientY);
        }
    }, 8);

    function iME(obj, event, screenXArg, screenYArg, clientXArg, clientYArg) {
        const mousemove = document.createEvent("MouseEvent");
        mousemove.initMouseEvent(event, true, true, window, 0,
            screenXArg, screenYArg, clientXArg, clientYArg, 0, 0, 0, 0, 0, null);
        obj.dispatchEvent(mousemove);
    }
}

function simulateHorizontalDrag(slide, distance) {
    const obj = document.querySelector(slide);
    obj.target = '_self';
    const _owh = obj.getBoundingClientRect();
    let _ox = _owh.width / 2, _oh = _owh.height / 2;
    _ox = Math.floor(Math.random() * _ox + 60);
    _oh = Math.floor(Math.random() * _oh + 60);
    _ox = _ox + _owh.x;
    _oh = _oh + _owh.y;
    dragandDrop(slide, _ox, _oh, distance);
}

content js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log('got message: ', message)
    if (message.type === 'get_slider_position') {
// 获取滑块元素(需替换为实际选择器)
        const slider = document.querySelector('.tc-slider-normal');
        let rect = slider.getBoundingClientRect();
        sendResponse({rect})
    } else if (message.type === 'get_captcha_position') {
        console.log('got message: ', 'get_captcha_position')
// 获取滑块元素(需替换为实际选择器)
        const slider = document.querySelector('#slideBg');
        let rect = slider.getBoundingClientRect();
        sendResponse({rect})
    } else if (message.type === 'devicePixelRatio') {
        console.log('devicePixelRatio', window.devicePixelRatio)
        sendResponse({devicePixelRatio: window.devicePixelRatio});
    }
});

background js

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
    console.log('got message: ', message)
    if (message.type === 'TAKE_ELEMENT_SCREENSHOT') {
        get_captcha_screen(message, sender, sendResponse)
        return true
    } else if (message.type === 'download_image') {
        chrome.downloads.download(message.data);
    }

});

const get_captcha_screen = (message, sender, sendResponse) => {
// 获取当前标签页的 windowId
    const windowId = sender.tab.windowId;

    chrome.tabs.captureVisibleTab(windowId, {format: 'png'}, async (dataUrl) => {
        if (chrome.runtime.lastError) {
            console.error(chrome.runtime.lastError.message);
            return;
        }

        // chrome.downloads.download({
        //     url: dataUrl,
        //     filename: 'page.png'
        // });

        try {
            // 将 dataUrl 转为 Blob 并创建 ImageBitmap 便于裁剪
            const blob = await fetch(dataUrl).then(r => r.blob());
            const imageBitmap = await createImageBitmap(blob);

            const rect = message.rect
            console.log('rect', rect)
            let iframe = await get_element_position(sender.tab.id, 'iframe').then(result => result.rect)
            console.log('iframe', iframe)

            // 创建 OffscreenCanvas,其尺寸与目标元素一致
            // const canvas = new OffscreenCanvas(iframe.width, iframe.height);
            const radio = await chrome.tabs.sendMessage(sender.tab.id, {type: "devicePixelRatio"}).then(r => r.devicePixelRatio)
            const canvas = new OffscreenCanvas(rect.width, rect.height);
            const ctx = canvas.getContext('2d');
            ctx.drawImage(
                imageBitmap,
                (iframe.left + rect.left) * radio, //源图像的裁剪起点 X 坐标。
                (iframe.top + rect.top) * radio,//iframe.top,  //源图像的裁剪起点 Y 坐标。
                rect.width * radio,//iframe.width, //源图像的裁剪宽度。
                rect.height * radio,//iframe.height, //源图像的裁剪高度。
                0, //目标 Canvas 上的 X 坐标。
                0, //目标 Canvas 上的 Y 坐标。
                rect.width, //绘制到 Canvas 上的宽度(可以缩放图像)。
                rect.height,//绘制到 Canvas 上的高度(可以缩放图像)
            );

            // 将裁剪结果转换为 Blob 或 dataURL
            const croppedBlob = await canvas.convertToBlob();
            const reader = new FileReader();
            reader.onload = () => {
                const croppedDataUrl = reader.result;
                console.log('元素截图完成,dataURL:', croppedDataUrl);
                chrome.downloads.download({
                    url: croppedDataUrl,
                    filename: 'screenshot.png'
                });
                sendResponse({url: croppedDataUrl})
                // 此处可以将截图 dataURL 发送到 popup 显示,或者打开新窗口展示
            };
            reader.readAsDataURL(croppedBlob);

        } catch (error) {
            console.error('截图过程中出错:', error);
        }
    });
}

async function get_element_position(tabId, element_type) {
    const type = `get_${element_type}_position`
    return await chrome.tabs.sendMessage(tabId, {type})
}

// 引入 html2canvas
import html2canvas from 'html2canvas';


async function captureElement(elementSelector, image_name = 'screenshot_from_html2canvas') {
    const element = document.querySelector(elementSelector);
    // 获取元素的实际渲染尺寸
    const rect = element.getBoundingClientRect();
    const width = rect.width;
    const height = rect.height;

    // 创建临时容器(避免截取时元素被缩放影响布局)
    const container = document.createElement('div');
    Object.assign(container.style, {
        position: 'fixed',
        left: '-9999px',      // 隐藏到屏幕外
        width: `${width}px`,  // 固定容器尺寸
        height: `${height}px`
    });
    document.body.appendChild(container);

    // 克隆元素到临时容器(保持样式)
    const clone = element.cloneNode(true);
    container.appendChild(clone);

    // width和height是为了和屏幕上显示的大小一致,不加也可以
    const canvas = await html2canvas(element, {
        useCORS: true,       // 允许跨域资源
        logging: false,      // 关闭日志
        scale: 1,            // 必须设为 1,否则尺寸会缩放
        width: width,        // 显式设置宽高
        height: height,
        backgroundColor: null // 透明背景
    });

    // 转换为图片 URL
    const url = canvas.toDataURL('image/png', 1.0);
    const data = {
        url: url,
        filename: `${image_name}.png`
    }
    // downloadImg(data);
    postMessage({type: "download_image", data})
    return url
}

读取剪切板需要有用户交互,比如点击,第一次需要申请权限

<a-button :disabled="Object.keys(harFiles.valueOf()).length===0" @click="readClipboardText">add record from
          clipboard
        </a-button>
const readClipboardText = async () => {
  try {
    // 请求剪切板读取权限
    const permission = await navigator.permissions.query({name: 'clipboard-read'});

    if (permission.state === 'granted' || permission.state === 'prompt') {
      // 读取文本内容
      let text = await navigator.clipboard.readText()
      console.log(text)
      
    } else {
      alert('请允许剪切板访问权限!');
    }
  } catch (error) {
    console.error('读取失败:', error);
    alert('无法读取剪切板内容,请确保浏览器支持或已授予权限');
  }
};

[2025-02-10 13:40:41] [ERROR] [unknown:0] [0] got error:TimeoutError: elementHandle.hover: Timeout 2000ms exceeded.
Call log:

  • attempting hover action
    2 × waiting for element to be visible and stable

    • element is visible and stable
    • scrolling into view if needed
    • done scrolling
    • performing hover action
    • from
      subtree intercepts pointer events
    • retrying hover action
    • waiting 20ms
    • × waiting for element to be visible and stable

      • element is visible and stable
      • scrolling into view if needed
      • done scrolling
      • performing hover action
      • from
        subtree intercepts pointer events
    • retrying hover action

      • waiting 100ms
    • × waiting for element to be visible and stable

      • element is visible and stable
      • scrolling into view if needed
      • done scrolling
      • performing hover action
      • from
        subtree intercepts pointer events
    • retrying hover action

      • waiting 500ms

<div id="slideBg"> 拦截了指针事件(intercepts pointer events)