H5 основан на холсте для реализации электронной подписи и генерировать документ PDF

внешний интерфейс
H5 основан на холсте для реализации электронной подписи и генерировать документ PDF

предисловие

Вообще говоря, электронная подпись заключается в загрузке электронной подписи в электронный документ с помощью технических средств, и ее функция аналогична собственноручной подписи или официальной печати на бумажном контракте. Хотя законность электронных подписей подвергалась сомнению в течение многих лет, они широко используются в сценариях утверждения рабочих процессов предприятия, приглашений, сохранения документов и т. д. В последних проектах возникла потребность в такой собственноручной подписи и создании PDF-файлов.

Реализовать идеи

    1. Используйте холст для реализации функции рукописной подписи, а затем преобразуйте холст в изображение и вставьте его в положение подписи;
    1. Используйте плагин html2canvas, чтобы преобразовать всю область DOM, необходимую для создания документов, в большое изображение;
    1. Используйте подключаемый модуль JsPDF, чтобы сгенерировать приведенное выше изображение в документ PDF;
    1. В случае большого количества файлового содержимого необходимо разумно выбирать позицию подкачки;

Генерировать подпись

1. Определите холст в tsx

 <canvas className={styles.canvas} ref={canvasDom} width="350" height="150" />

注意: ширина и высота Canvas должны быть определены с помощью встроенных стилей, поскольку тег Canvas имеет собственную ширину и высоту по умолчанию 300 пикселей × 150 пикселей. Ширина и высота, определяемые его встроенным стилем, являются фактической шириной и высотой области рисования (холста), и графика рисуется на ней. Если вы определяете его ширину и высоту в файле внешней ссылки стиля, тогда ширина и высота являются высотой и шириной холста, отображаемого в браузере. Если ширина и высота не определены напрямую в Canvas или значения неверны, они будут установлены на значения по умолчанию {width: 300px, height: 150px}. Поэтому, если вы установили холст {ширина: 200 пикселей; высота: 200 пикселей;} во внешних и внутренних файлах ссылок стиля, но не определили ширину и высоту холста напрямую на холсте, то ваше выходное значение canvas.height равно по-прежнему 150, холст. Значение ширины по-прежнему 300. То есть холст 150х300 рендерится в области 200х200, поэтому картинка будет растянута и деформирована.

2. Определите функцию подписи

 const writing = (
    beginX: number,
    beginY: number,
    stopX: number,
    stopY: number,
    ctx: any,
  ) => {
    ctx.beginPath();  // 开启一条新路径
    ctx.globalAlpha = 1;  // 设置图片的透明度
    ctx.lineWidth = 3;  // 设置线宽
    ctx.strokeStyle = 'red';  // 设置路径颜色
    ctx.moveTo(beginX, beginY);  // 从(beginX, beginY)这个坐标点开始画图
    ctx.lineTo(stopX, stopY);  // 定义从(beginX, beginY)到(stopX, stopY)的线条(该方法不会创建线条)
    ctx.closePath();  // 创建该条路径
    ctx.stroke();  // 实际地绘制出通过 moveTo() 和 lineTo() 方法定义的路径。默认颜色是黑色。
  };

3. Зарегистрируйтесь, чтобы следить за событиями

    let beginX: number, beginY: number;
    const canvas: HTMLCanvasElement = canvasDom.current;
    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '#fff';
    ctx.fillRect(0, 0, canvas.width, canvas.height);
    canvas.addEventListener('touchstart', function(event: any) {
      event.preventDefault(); // 阻止在canvas画布上签名的时候页面跟着滚动
      beginX = event.touches[0].clientX - this.offsetLeft; 
      beginY = event.touches[0].pageY - this.offsetTop;
    });
    canvas.addEventListener('touchmove', (event: any) => {
      event.preventDefault(); // 阻止在canvas画布上签名的时候页面跟着滚动
      event = event.touches[0];
      let stopX = event.clientX - canvas.offsetLeft;
      let stopY = event.pageY - canvas.offsetTop;
      writing(beginX, beginY, stopX, stopY, ctx);
      beginX = stopX; // 这一步很关键,需要不断更新起点,否则画出来的是射线簇
      beginY = stopY;
    });

注意:

    1. При регистрации событий «touchstart» и «touchmove» нужно запретить события по умолчанию, иначе страница будет скользить вверх и вниз вместе с жестом.
    1. Каждый объект сенсорного события на мобильном терминале включает в себя атрибут touches, который используется для описания списка всех пальцев на экране.Для получения текущего объекта события мы обычно используем event = event.touches[0], тогда как на ПК терминалу этого делать не нужно.
    1. Значение offsetLeft и offsetTop не имеют ничего общего с родительским элементом, но имеют связь с элементом позиционирования на предыдущем уровне (все позиционирование, кроме position: static, например фиксированные, относительные, абсолютные элементы). Если предыдущий элемент позиционирования не имеет другого позиционирования, кроме position:staice, смещение определяется относительно тела.
    1. Нужно разобраться с некоторыми свойствами объекта мобильного события, ⏬

clientX/clientY: координаты x,y положения касания из текущей видимой области тела;
pageX/pageY: для всей страницы координаты x, y положения касания от левого верхнего угла тела, включая значения scrollTop и scrollLeft;
screenX/screenY: расстояние по осям x, y от положения касания слева и сверху дисплея.
Следовательно, при получении координат конечной точки, если на текущей странице нет полосы прокрутки, разница между использованием для расчета clientY и pageY невелика.Если страница относительно длинная и появляется полоса прокрутки, то страницаY должна быть используется для расчета. То же самое верно и для clientX, но на мобильной стороне не так много сценариев горизонтальной прокрутки, поэтому для расчета можно использовать clientX.

    1. В процессе подписи (touchmove) нам нужно постоянно обновлять начальную позицию, иначе рисунок будет выглядеть вот так🔽


На самом деле, этот принцип очень похож на исчисление.Прямой отрезок по существу состоит из бесконечного числа маленьких прямых отрезков.С макро точки зрения отрезок можно рассматривать как небольшой отрезок прямой с малой длиной соединенного конца в конец. Поэтому я всегда чувствую, что программирование — это проверка математических способностей человека в конце, а математику можно увидеть в пересечении и объединении, логическом мышлении и алгоритмах. Окончательная сгенерированная подпись выглядит следующим образом:

Создание PDF-документов

html2canvas — это подключаемый модуль, который преобразует HTML-код в Canvas, поэтому вам нужно обернуть область содержимого для печати с помощью div, чтобы получить узел dom.

html2Canvas(dom, {
    allowTaint: true,
    width: dom.offsetWidth, //设置获取到的canvas宽度
    height: dom.offsetHeight, //设置获取到的canvas高度
    x: 0, //页面在水平方向滚动的距离
    y: 0, //页面在垂直方向滚动的距离
   })

注意: Вам необходимо установить ширину, высоту и x, y здесь, в противном случае нет проблем, когда содержимое страницы представляет собой только одну страницу, но если содержимое страницы имеет много страниц, будет явление, что только небольшая часть сгенерированное изображение имеет содержимое. Проблема заключается в этом параметре конфигурации: если ширина и высота не заданы, по умолчанию берется только содержимое текущего окна просмотра, а остальное содержимое за пределами текущего окна просмотра отбрасывается.
Задайте параметры печати:

const print = () => {
    let dom: HTMLElement = pdfDom.current;
    html2Canvas(dom, {
      allowTaint: true,
      width: dom.offsetWidth, //设置获取到的canvas宽度
      height: dom.offsetHeight, //设置获取到的canvas高度
      x: 0, //页面在水平方向滚动的距离
      y: 0, //页面在垂直方向滚动的距离
    }).then((canvas: HTMLCanvasElement) => {
      let canvasWidth = canvas.width;
      let canvasHeight = canvas.height;
      let pageHeight = (canvasWidth / 592.28) * 841.89; // 一页A4 pdf能显示的canvas高度
      let imgWidth = 595.28; // 设置图片宽度和A4纸宽度相等
      let imgHeight = (592.28 / canvasWidth) * canvasHeight;//等比例换算成A4纸的高度
      let totalHeight = imgHeight; // 需要打印的图片总高度,初始状态和图片高度相等
      let pageData = canvas.toDataURL('image/png', 1.0);
      let PDF = new JsPDF('p', 'pt', 'a4', true);
      if (totalHeight < pageHeight) { //
        PDF.addImage(pageData, 'JPEG', 0, 0, imgWidth, imgHeight); // 从顶部开始打印
      } else {
        let top = 0;   // 打印初始区域
        while (totalHeight > 0) {
          PDF.addImage(pageData, 'JPEG', 0, top, imgWidth, imgHeight);  // 从图片顶部往下top位置开始打印
          totalHeight -= pageHeight;
          top -= 841.89;
          if (totalHeight > 0) {
            PDF.addPage();
          }
        }
      }
      PDF.save('test.pdf');
    });
  };

Выберите место для страницы

PDF-документ формируется по вышеперечисленным шагам, но когда в PDF много страниц, будет такая проблема⏬Видно, что текст лениво обрезается от этого текста при листании. Это явно не тот эффект, который мы хотим видеть, как решить эту проблему? 🤔

  • PDF-документ с меньшим количеством страниц

Вы можете заранее вставить отступ в место разбиения на страницы при разработке и тестировании, то есть заранее зарезервировав позицию подкачки

  • PDF-документ с большим количеством страниц

В этом случае автор пытается пройти через дочерние узлы узла dom для печати и накапливает высоту узла dom, которая может быть напечатана на каждой странице.Если она превышает максимальную высоту, которую может нести страница, добавьте отступы. до последнего узла и распечатайте. Когда закончите, восстановите стиль. Поскольку этот метод должен вычислять высоту каждого узла DOM, он очень требователен к производительности, а также требует более тонкой детализации элементов DOM страницы, иначе на странице будет большой пробел, и он полностью невозможно смоделировать эффект генерации слов в формате pdf, поэтому никаких дискуссий. Если есть читатели, у которых есть лучший план освобождения, пожалуйста, дайте мне знать, спасибо~❤️