Sep 08, 2025
3 min read
Tauri,
Rust,

使用 Tauri 实现跨平台截图功能的挑战与解决方案

探讨在 Tauri 框架中实现截图功能的技术难点,包括跨平台兼容性问题和数据传输效率问题,并提供相应的解决方案。

使用 Tauri 作为应用技术栈来实现截图功能并不占优势。

Tauri 最大的特色是跨平台,一份代码,多端应用。然而,截图这个功能本身就不可太可能跨得了平台,因为每个平台都有自己的独特 API 实现。比如,macOS 上,需要调用 cocoa 框架的接口来实现。并且,macOS上是需要应用申请屏幕录制权限的。如果用户不授权,那么应用只能获取到应用自己的截图以及空桌面截图。在 linux 上,我们可能还得考虑是在 X server 上实现还是 wayland 上实现。

另外一个问题,是 Tauri 的传输效率。我们知道,Tauri 使用 Command 指令来交互前端与Rust 。在 Tauri v1 时代,Command 指令只支持序列化的文本传输信息,因为 Tauri 默认使用 serde::Serialize 处理数据。这对传输二进制数据来说,效率慢的非常可怕。

好在,Tauri v2 的数据已经支持 Raw:

pub enum InvokeBody {
  /// Json payload.
  Json(JsonValue),
  /// Bytes payload.
  Raw(Vec<u8>),
}

但是对于前后端来说,还是有一些传输数据的开销在,只是不那么另人无法接受了。

为什么这对于截图来说很重要?你试试打开微信的截图,微信的截图功能在截图前会有一个鼠标跟随预览功能(类似放大镜效果),方便用户精准定位截图的开始点。

Tauri 要怎么实现截图?

截图软件本质上就是一个透明的置顶的全屏的应用。

tauri.conf.json 里,添加如下配置:

  "app": {
    "windows": [
      {
        "title": "jietu",
        "width": 800,
        "height": 600,
        "resizable": false,
        "alwaysOnTop": true,
        "decorations": false,
        "fullscreen": true,
        "shadow": false,
        "transparent": true
      }
    ],
    "security": {
       "csp": "default-src 'self' 'unsafe-inline'; connect-src 'self' ipc://localhost"
    }
  },

开发阶段,最好设置  "fullscreen": false,,不然不好调试 。

前端也要设置背景透明色:

html,body,#root,.container {
  @apply bg-transparent;
}

剩下的,我们需要在 Rust 层实现各个平台的截图功能。 并在前端层实现截图上的交互。

如果我们自己在Rust 层实现三大平台的截图,代码量是比较大的,同时你还得非常了解 windows api、cocoa 框架等。幸运的是,有人把这部分脏活给干了:

cargo add xcap

放大镜(截图预览开始点)

[屏幕截图 2025-09-07 220129.png] 下面这个方法就实现了放大镜功能,我们需要获取鼠标的坐标点,并以这个坐标点为原点,获取一个200x200 的截图:

#[tauri::command]
fn xcap_start(x:u32,y:u32) -> Result<Response, String> {
    let monitors = Monitor::all().map_err(|e| e.to_string())?;

    let monitor = monitors
        .into_iter()
        .find(|m| m.is_primary().unwrap_or(false))
        .expect("No primary monitor found");


    let region_width = 200u32;
    let region_height = 200u32;

    let start = Instant::now();

    let image = monitor.capture_region(x, y, region_width, region_height).map_err(|e| e.to_string())?;
    println!(
        "Time to record region of size {}x{}: {:?}",
        image.width(),
        image.height(),
        start.elapsed()
    );
    // 将图像数据转换为字节数组返回给前端
    let width = image.width();
    let height = image.height();
    let mut buffer = Cursor::new(Vec::new());
    let encoder = image::codecs::png::PngEncoder::new(&mut buffer);
    let rgba_image = image::DynamicImage::ImageRgba8(image).into_rgba8();
    encoder.write_image(rgba_image.as_bytes(), width, height, image::ExtendedColorType::Rgba8)
           .map_err(|e| e.to_string())?;
    
    Ok(Response::new(buffer.into_inner()))
        
}

前端则使用一个全屏画布来实现截图页面,我习惯使用 konva 。

“tsx <Text text={鼠标位置: (${mousePosition.x}, ${mousePosition.y})} fontSize={15} x={20} y={20} fill=“white” />

      {image && (
        <Image
          x={300}
          y={50}
          width={200}
          height={200}
          image={image}
        />
      )}
    </Layer>
  </Stage>

在最外层的容器上添加鼠标事件:
capsview(e.clientX, e.clientY)}> ```
  const [imagePath, setImagePath] = useState<string | null>(null);
  // 使用节流优化截图调用
  const capsview = useRef(throttle(async (x: number, y: number) => {
    try {
      // 更新鼠标位置状态
      setMousePosition({ x, y });
      
      // 调用Rust命令获取截图数据
      const result: Uint8Array = await invoke("xcap_start",{x,y});
      const blob = new Blob([new Uint8Array(result).buffer], { type: "image/png" });
      setImagePath(URL.createObjectURL(blob));
      console.log("截图成功获取");
    } catch (error) {
      console.error("截图失败:", error);
    }
  }, 30)).current; // 限制为每30ms最多执行一次

这里的 30ms 是有说法的。因为 Rust 层在 windows 平台上截图一个 200x200大小的图片耗时(使用的是 pnpm tauri dev, 这是未优化过的,理论上优化过的应该更少耗时)如下:

Time to record region of size 200x200: 16.8005ms
Time to record region of size 200x200: 16.9432ms
Time to record region of size 200x200: 16.5475ms
Time to record region of size 200x200: 15.8173ms
Time to record region of size 200x200: 17.1305ms
Time to record region of size 200x200: 16.2595ms
Time to record region of size 200x200: 16.0607ms
Time to record region of size 200x200: 16.7726ms
Time to record region of size 200x200: 16.3253ms
Time to record region of size 200x200: 15.9661ms
Time to record region of size 200x200: 17.0717ms
Time to record region of size 200x200: 15.809ms
Time to record region of size 200x200: 12.2509ms
Time to record region of size 200x200: 19.8513ms
Time to record region of size 200x200: 17.0228ms
Time to record region of size 200x200: 14.8592ms
Time to record region of size 200x200: 15.5001ms
Time to record region of size 200x200: 15.9599ms
Time to record region of size 200x200: 16.6687ms
Time to record region of size 200x200: 15.5552ms
Time to record region of size 200x200: 15.9622ms

再叠加前后端传输以及一些处理等,30ms 是比较合适的间隔。

这时候截图前的放大镜效果最核心的功能就已经实现出来了,细节什么的,只是UI 问题而已。

同样地,选取区域截图功能,你甚至改改上面的xcap_start 就能使用,至于截图后要加画笔功能、文本功能、画框功能什么的,都属于前端的范畴,直接使用 konva 的api 就能轻松搞定.