You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Expo创建的React Native项目中实现签名捕获(无需Eject)

React Native Expo 无Eject实现签名捕获方案建议

当然可以用WebView封装signature_pad来实现签名捕获,而且完全不用eject,完美适配你的Expo项目!下面给你详细的实现思路、代码示例和相关建议:

一、WebView封装signature_pad的实操方案

这个方案完全基于JS层实现,不需要触碰原生代码,非常适合Expo项目:

  • 准备一个内嵌signature_pad逻辑的HTML页面:你可以把signature_pad的JS、CSS直接写在HTML里,也可以引入固定版本的CDN资源保证稳定性
  • 在React Native端通过WebView加载这个HTML:可以直接把HTML字符串传给WebView的source={{ html: 你的HTML内容 }},也可以把HTML文件放在项目assets目录下,通过Expo的文件系统API获取本地URI加载
  • 实现RN与WebView的双向通信:
    • 当用户完成签名点击保存时,在WebView的JS代码里用window.ReactNativeWebView.postMessage把签名的base64数据传给RN端
    • RN端通过WebView的onMessage回调接收数据,后续可以用来保存到本地、展示或者上传到服务器
  • 适配细节处理:给签名画布添加自适应屏幕的逻辑,添加清除、保存按钮提升用户体验

简单代码示例

WebView内嵌的HTML内容

<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body { margin: 0; padding: 10px; }
    #signature-pad { border: 1px solid #ddd; width: 100%; height: 300px; border-radius: 4px; }
    .action-buttons { margin-top: 15px; display: flex; gap: 10px; }
    button { flex: 1; padding: 10px; background: #007AFF; color: white; border: none; border-radius: 4px; font-size: 16px; }
    button:nth-child(1) { background: #8E8E93; }
  </style>
</head>
<body>
  <canvas id="signature-pad"></canvas>
  <div class="action-buttons">
    <button onclick="clearSignature()">清除签名</button>
    <button onclick="saveSignature()">保存签名</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/signature_pad@4.0.0/dist/signature_pad.umd.min.js"></script>
  <script>
    const canvas = document.getElementById('signature-pad');
    const signaturePad = new SignaturePad(canvas);

    // 适配屏幕像素比,保证签名清晰度
    function resizeCanvas() {
      const ratio = Math.max(window.devicePixelRatio || 1, 1);
      canvas.width = canvas.offsetWidth * ratio;
      canvas.height = canvas.offsetHeight * ratio;
      canvas.getContext('2d').scale(ratio, ratio);
      signaturePad.clear();
    }
    window.addEventListener('resize', resizeCanvas);
    resizeCanvas();

    function clearSignature() {
      signaturePad.clear();
    }

    function saveSignature() {
      if (signaturePad.isEmpty()) {
        window.ReactNativeWebView.postMessage(JSON.stringify({
          type: 'error',
          msg: '请先完成签名'
        }));
        return;
      }
      const signatureData = signaturePad.toDataURL('image/png');
      window.ReactNativeWebView.postMessage(JSON.stringify({
        type: 'success',
        data: signatureData
      }));
    }
  </script>
</body>
</html>

React Native端代码

import React, { useRef } from 'react';
import { View, WebView, Alert, StyleSheet } from 'react-native';

export default function SignatureCaptureScreen() {
  const webViewRef = useRef(null);

  const handleWebMessage = (event) => {
    const message = JSON.parse(event.nativeEvent.data);
    if (message.type === 'success') {
      // 这里拿到签名的base64数据,可根据需求处理
      console.log('签名数据:', message.data);
      Alert.alert('成功', '签名已保存');
    } else if (message.type === 'error') {
      Alert.alert('提示', message.msg);
    }
  };

  // 把上面的HTML内容作为字符串传入
  const signatureHtml = `<!DOCTYPE html>
<html>
<head>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <style>
    body { margin: 0; padding: 10px; }
    #signature-pad { border: 1px solid #ddd; width: 100%; height: 300px; border-radius: 4px; }
    .action-buttons { margin-top: 15px; display: flex; gap: 10px; }
    button { flex: 1; padding: 10px; background: #007AFF; color: white; border: none; border-radius: 4px; font-size: 16px; }
    button:nth-child(1) { background: #8E8E93; }
  </style>
</head>
<body>
  <canvas id="signature-pad"></canvas>
  <div class="action-buttons">
    <button onclick="clearSignature()">清除签名</button>
    <button onclick="saveSignature()">保存签名</button>
  </div>
  <script src="https://cdn.jsdelivr.net/npm/signature_pad@4.0.0/dist/signature_pad.umd.min.js"></script>
  <script>
    const canvas = document.getElementById('signature-pad');
    const signaturePad = new SignaturePad(canvas);

    function resizeCanvas() {
      const ratio = Math.max(window.devicePixelRatio || 1, 1);
      canvas.width = canvas.offsetWidth * ratio;
      canvas.height = canvas.offsetHeight * ratio;
      canvas.getContext('2d').scale(ratio, ratio);
      signaturePad.clear();
    }
    window.addEventListener('resize', resizeCanvas);
    resizeCanvas();

    function clearSignature() {
      signaturePad.clear();
    }

    function saveSignature() {
      if (signaturePad.isEmpty()) {
        window.ReactNativeWebView.postMessage(JSON.stringify({
          type: 'error',
          msg: '请先完成签名'
        }));
        return;
      }
      const signatureData = signaturePad.toDataURL('image/png');
      window.ReactNativeWebView.postMessage(JSON.stringify({
        type: 'success',
        data: signatureData
      }));
    }
  </script>
</body>
</html>`;

  return (
    <View style={styles.container}>
      <WebView
        ref={webViewRef}
        source={{ html: signatureHtml }}
        onMessage={handleWebMessage}
        javaScriptEnabled={true}
        domStorageEnabled={true}
        style={styles.webView}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    padding: 20,
    backgroundColor: '#fff',
  },
  webView: {
    flex: 1,
    borderRadius: 8,
    borderWidth: 1,
    borderColor: '#ddd',
  },
});

二、关于react-native-signature-capture的说明

这个库确实需要你eject Expo项目,并且执行react-native link来链接原生依赖,这会让你失去Expo提供的OTA更新、预构建原生组件等便捷功能。如果你的项目没有eject的计划,完全不建议选择这个方案;如果之后确定要迁移到纯React Native项目(脱离Expo),它的原生实现会比WebView方案性能稍好,但需要维护iOS和Android两端的原生代码。

三、总结建议

如果要保持Expo项目不eject,WebView封装signature_pad是最稳妥、灵活的方案,实现成本低,不需要处理原生依赖,性能也完全能满足签名捕获的需求。你可以根据自己的UI需求调整HTML样式,也可以扩展更多功能(比如支持手写笔、调整签名颜色粗细等)。

内容的提问来源于stack exchange,提问作者blueether

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 11:33:22