Skip to content

案例:元素选择器 ​

在已接入 Makoo的 React 项目中,创建一个浮动面板:点击“选择元素”,悬停时高亮目标,点击后显示元素的标签名和 ID。

启动面板 ​

在应用入口注册组件任务:

src/main.ts

ts
import { createMakoo, inject } from '@makoojs/core';
import { createReactAdapter } from '@makoojs/react';
import Picker from './injections/picker/Picker.tsx';

createMakoo({ adapters: [createReactAdapter()] }).start([
  inject({ id: 'element-picker', injectAt: 'body', artifact: Picker })
]);

选择元素 ​

picking 控制选择模式。开启时注册页面事件并创建高亮框;选中目标、取消选择或关闭面板时,effect 的清理函数移除事件和高亮框。data-makoo-picker 用来排除面板自身。

src/injections/picker/Picker.tsx

tsx
import { useEffect, useState } from 'react';
import type { ReactMountProps } from '@makoojs/react';
import './style.css';

export default function Picker({ makoo }: ReactMountProps) {
  const [picking, setPicking] = useState(false);
  const [targetLabel, setTargetLabel] = useState('尚未选择元素');

  useEffect(() => {
    if (!picking) return;

    const outline = document.createElement('div');
    outline.className = 'makoo-picker-outline';
    document.body.appendChild(outline);

    function preview(event: PointerEvent) {
      const target = event.target;
      if (!(target instanceof Element) || target.closest('[data-makoo-picker]')) {
        outline.style.display = 'none';
        return;
      }

      const rect = target.getBoundingClientRect();
      Object.assign(outline.style, {
        display: 'block',
        left: `${rect.left}px`,
        top: `${rect.top}px`,
        width: `${rect.width}px`,
        height: `${rect.height}px`
      });
    }

    function select(event: MouseEvent) {
      const target = event.target;
      if (!(target instanceof Element) || target.closest('[data-makoo-picker]')) return;

      event.preventDefault();
      event.stopPropagation();
      const tag = target.tagName.toLowerCase();
      setTargetLabel(target.id ? `${tag}#${target.id}` : tag);
      setPicking(false);
    }

    document.addEventListener('pointermove', preview, true);
    document.addEventListener('click', select, true);
    return () => {
      document.removeEventListener('pointermove', preview, true);
      document.removeEventListener('click', select, true);
      outline.remove();
    };
  }, [picking]);

  return (
    <aside data-makoo-picker>
      <p>{targetLabel}</p>
      <button onClick={() => setPicking((value) => !value)}>
        {picking ? '取消' : '选择元素'}
      </button>
      <button onClick={() => makoo.destroy()}>关闭</button>
    </aside>
  );
}

设置样式 ​

面板固定在右下角。高亮框使用视口坐标定位,pointer-events: none 让鼠标事件继续落在目标元素上。

src/injections/picker/style.css

css
[data-makoo-picker] {
  position: fixed;
  right: 16px;
  bottom: 16px;
  z-index: 2147483647;
  padding: 16px;
  background: white;
  color: #222;
  border: 1px solid #ccc;
  border-radius: 8px;
  font: 14px/1.5 sans-serif;
}

.makoo-picker-outline {
  display: none;
  position: fixed;
  pointer-events: none;
  box-sizing: border-box;
  z-index: 2147483646;
  border: 2px solid #2563eb;
  background: rgb(37 99 235 / 10%);
}

在网页中使用 ​

运行 pnpm dev,安装开发脚本并打开匹配网页。点击“选择元素”,移动鼠标观察高亮,再点击目标;面板会显示类似 div#toolbar 的结果。点击“关闭”会通过 makoo.destroy() 卸载面板。

这个任务挂载到 body,选择过程中的事件和高亮框由 React effect 管理。组件卸载时,React 会运行同一份清理函数。任务销毁方法见 MakooContext。