Как добавить интерактивные компоненты, такие как кнопки и поля ввода, поверх PDF в Angular?
Я пытаюсь создать пользовательский интерфейс, где нажатие на кнопку “Подписать” открывает модальное окно с отображением PDF. Мне нужно добавить:
- Кнопку в поле подписи PDF, которая при нажатии отображает панель для подписи пользователей
- Поле ввода в поле даты для ввода даты
Как можно реализовать эти интерактивные функции PDF в Angular? Есть ли какие-либо рекомендуемые open-source библиотеки или альтернативные подходы для достижения этой функциональности?
Добавление интерактивных компонентов поверх PDF в Angular
Чтобы добавить интерактивные компоненты, такие как кнопки и поля ввода, поверх PDF в Angular, можно использовать специализированные библиотеки просмотра PDF, которые поддерживают наложение форм и интерактивные элементы. Наиболее эффективный подход включает использование библиотек, таких как ngx-extended-pdf-viewer или Syncfusion PDF Viewer, которые обеспечивают встроенную поддержку интерактивных форм, или реализацию пользовательского решения наложения с помощью PDF.js в сочетании с компонентами Angular, позиционированными абсолютно поверх содержимого PDF.
Содержание
- Обзор подходов
- Рекомендуемые библиотеки для интерактивных PDF
- Реализация функциональности подписи
- Реализация полей ввода даты
- Реализация пользовательского наложения
- Примеры кода и реализация
- Лучшие практики и соображения по производительности
Обзор подходов
При реализации интерактивных компонентов поверх PDF в Angular следует рассмотреть несколько подходов:
Решения на основе библиотек
Большинство разработчиков выбирают специализированные библиотеки просмотра PDF, которые предоставляют встроенную поддержку интерактивных форм. Эти библиотеки обрабатывают сложную логику рендеринга и взаимодействия, позволяя при этом настраивать пользовательский интерфейс.
Пользовательские решения наложения
Для более специфических требований можно реализовать пользовательскую систему наложения, в которой компоненты Angular (кнопки, поля ввода) позиционируются абсолютно поверх содержимого PDF с использованием координат и обработки событий.
Гибридные подходы
Многие проекты объединяют просмотр PDF на основе библиотек с пользовательскими интерактивными компонентами для конкретных функций, таких как цифровые планшеты для подписи или выборщики даты, которые не полностью поддерживаются стандартными элементами форм PDF.
Выбор зависит от требований вашего проекта, бюджета и сложности необходимых интерактивных функций.
Рекомендуемые библиотеки для интерактивных PDF
ngx-extended-pdf-viewer
Эта библиотека предоставляет комплексную поддержку интерактивных форм с двусторонним связыванием данных, что делает ее идеальной для вашего случая использования. Она поддерживает:
- Стандартные и XFA формы
- Текстовые поля, флажки, переключатели и выпадающие списки
- Расширенные возможности аннотирования
- Настраиваемую панель инструментов и пользовательский интерфейс
Согласно документации npm-пакета, она предлагает “Комплексную поддержку форм с двусторонним связыванием данных” и “Функции аннотирования и редактирования PDF”.
Syncfusion PDF Viewer
Syncfusion предоставляет полный интерактивный просмотрщик PDF с обширной поддержкой форм:
- Полностью интерактивные формы с текстовыми полями, флажками, переключателями и выпадающими списками
- Редактирование, предварительный просмотр и сохранение форм в реальном времени
- Поддержка рукописных подписей
- Настраиваемая панель инструментов и пользовательский интерфейс
Как указано в их описании продукта, она позволяет вам “Полностью интерактивные формы с текстовыми полями, флажками, переключателями и выпадающими списками, или проектирование новых форм внутри просмотрщика.”
PDF.js Express
Это коммерческое решение обертывает открытый движок рендеринга PDF.js и предоставляет:
- Возможности аннотирования
- Электронные подписи
- Заполнение форм
- Расширенные параметры настройки
В документации PDF.js Express упоминается, что она “предоставляет разработчикам быстрый способ добавления аннотаций, электронных подписей и заполнения форм в их просмотрщик PDF Angular”.
ng2-pdf-viewer
Более простой альтернатива для базового просмотра PDF с некоторыми интерактивными функциями:
- Извлечение и выбор текста PDF
- Базовая поддержка полей формы
- Обработка событий для пользовательских взаимодействий
Репозиторий GitHub предоставляет примеры реализации просмотра PDF с компонентами Angular.
Реализация функциональности подписи
Использование встроенной поддержки подписи
Многие просмотрщики PDF предоставляют встроенную функциональность подписи. Например, просмотрщик PDF от Syncfusion поддерживает рукописные подписи через свою панель подписи:
import { Component } from '@angular/core';
import { PdfViewerModule } from '@syncfusion/ej2-angular-pdfviewer';
import { ToolbarItem } from '@syncfusion/ej2-pdfviewer';
@Component({
selector: 'app-signature-example',
template: `
<button (click)="activateSignatureTool()">Подписать документ</button>
<ejs-pdfviewer
#pdfviewer
[serviceUrl]='serviceUrl'
[documentPath]='documentPath'
[toolbarItems]='toolbarItems'>
</ejs-pdfviewer>
`
})
export class SignatureExampleComponent {
serviceUrl = 'https://services.syncfusion.com/production/js/production/pdfviewer';
documentPath = 'https://cdn.syncfusion.com/content/pdf/pdf-succinctly.pdf';
toolbarItems: ToolbarItem[] = ['SignatureTool'];
activateSignatureTool() {
const viewer = document.getElementById('pdfviewer').ej2_instances[0];
viewer.annotationModule.setAnnotationMode('Signature');
}
}
Реализация пользовательского планшета для подписи
Для большего контроля можно реализовать пользовательский планшет для подписи с использованием canvas и наложить его поверх PDF:
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-custom-signature',
template: `
<div class="pdf-container" #pdfContainer>
<pdf-viewer [src]="pdfSrc" [render-text]="true"></pdf-viewer>
<div class="overlay-controls">
<button (click)="showSignaturePad()">Подписать здесь</button>
<div *ngIf="showPad" class="signature-pad">
<canvas #signatureCanvas width="300" height="150"></canvas>
<button (click)="clearSignature()">Очистить</button>
<button (click)="saveSignature()">Сохранить</button>
</div>
</div>
</div>
`,
styles: [`
.pdf-container { position: relative; }
.overlay-controls { position: absolute; top: 100px; left: 50px; }
.signature-pad { border: 1px solid #ccc; margin-top: 10px; }
`]
})
export class CustomSignatureComponent {
@ViewChild('pdfContainer') pdfContainer: ElementRef;
@ViewChild('signatureCanvas') signatureCanvas: ElementRef;
pdfSrc = 'path/to/your/document.pdf';
showPad = false;
isDrawing = false;
showSignaturePad() {
this.showPad = true;
this.setupCanvas();
}
setupCanvas() {
const canvas = this.signatureCanvas.nativeElement;
const ctx = canvas.getContext('2d');
canvas.addEventListener('mousedown', (e) => {
this.isDrawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX, e.offsetY);
});
canvas.addEventListener('mousemove', (e) => {
if (!this.isDrawing) return;
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
});
canvas.addEventListener('mouseup', () => {
this.isDrawing = false;
});
}
clearSignature() {
const canvas = this.signatureCanvas.nativeElement;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
saveSignature() {
const canvas = this.signatureCanvas.nativeElement;
const signatureData = canvas.toDataURL();
// Здесь обычно отправляется данные в сервис обработки PDF
console.log('Подпись сохранена:', signatureData);
this.showPad = false;
}
}
Реализация полей ввода даты
Использование полей формы PDF
Наиболее прямой подход - использование полей формы PDF, совместимых с просмотрщиками PDF Angular:
import { Component } from '@angular/core';
@Component({
selector: 'app-date-form-example',
template: `
<pdf-viewer
[src]="pdfSrc"
[render-text]="true"
(pageRendered)="onPageRendered($event)">
</pdf-viewer>
<div *ngIf="showDateInput" class="date-overlay">
<input
type="date"
[(ngModel)]="selectedDate"
(blur)="onDateBlur()">
</div>
`
})
export class DateFormExampleComponent {
pdfSrc = 'path/to/your/form.pdf';
showDateInput = false;
selectedDate: string;
dateFieldPosition: {x: number, y: number};
onPageRendered(event) {
// Логика обнаружения, когда поля даты отрисованы
// и соответствующего позиционирования наложенного поля ввода
}
onDateBlur() {
// Сохранение даты в поле формы PDF
this.showDateInput = false;
}
}
Пользовательское наложение поля ввода даты
Для большего контроля над опытом использования поля ввода даты:
import { Component, ViewChild, ElementRef } from '@angular/core';
@Component({
selector: 'app-custom-date-input',
template: `
<div class="pdf-viewer-container">
<pdf-viewer [src]="pdfSrc" [render-text]="true"></pdf-viewer>
<div *ngIf="showDatePicker" class="date-picker-overlay"
[style.left]="datePosition.x + 'px'"
[style.top]="datePosition.y + 'px'">
<input type="date"
[(ngModel)]="selectedDate"
(change)="onDateSelect($event)"
(blur)="hideDatePicker()">
</div>
</div>
`,
styles: [`
.pdf-viewer-container { position: relative; }
.date-picker-overlay {
position: absolute;
z-index: 1000;
background: white;
border: 1px solid #ccc;
padding: 5px;
border-radius: 4px;
}
`]
})
export class CustomDateInputComponent {
@ViewChild('pdfViewer') pdfViewer: ElementRef;
pdfSrc = 'path/to/your/document.pdf';
showDatePicker = false;
selectedDate: string;
datePosition = { x: 0, y: 0 };
showDatePickerAtPosition(x: number, y: number) {
this.datePosition = { x, y };
this.showDatePicker = true;
this.selectedDate = new Date().toISOString().split('T')[0]; // Сегодняшняя дата
}
onDateSelect(event: any) {
// Обработка выбора даты и обновление PDF
console.log('Выбрана дата:', this.selectedDate);
// Здесь обычно обновляется поле формы PDF
}
hideDatePicker() {
setTimeout(() => this.showDatePicker = false, 200); // Время для завершения blur
}
}
Реализация пользовательского наложения
Для максимальной гибкости можно реализовать пользовательскую систему наложения:
import { Component, ViewChild, ElementRef } from '@angular/core';
import { fromEvent } from 'rxjs';
import { debounceTime } from 'rxjs/operators';
@Component({
selector: 'app-custom-overlay',
template: `
<div class="pdf-container" #pdfContainer>
<pdf-viewer [src]="pdfSrc" [render-text]="true"></pdf-viewer>
<div class="interactive-overlay">
<!-- Наложение кнопки подписи -->
<div class="signature-field"
[style.left]="signatureField.x + 'px'"
[style.top]="signatureField.y + 'px'"
[style.width]="signatureField.width + 'px'"
[style.height]="signatureField.height + 'px'">
<button (click)="openSignaturePad()">Подписать</button>
</div>
<!-- Наложение поля ввода даты -->
<div class="date-field"
[style.left]="dateField.x + 'px'"
[style.top]="dateField.y + 'px'"
[style.width]="dateField.width + 'px'"
[style.height]="dateField.height + 'px'">
<input *ngIf="showDateInput"
type="date"
[(ngModel)]="selectedDate"
(blur)="saveDateInput()">
</div>
</div>
</div>
`,
styles: [`
.pdf-container { position: relative; width: 100%; height: 800px; }
.interactive-overlay { position: absolute; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; }
.signature-field, .date-field {
position: absolute;
border: 1px dashed #007bff;
background: rgba(0, 123, 255, 0.1);
pointer-events: all;
}
.signature-field button {
width: 100%; height: 100%;
border: none; background: transparent;
cursor: pointer; font-size: 12px;
}
.signature-field button:hover { background: rgba(0, 123, 255, 0.2); }
`]
})
export class CustomOverlayComponent {
@ViewChild('pdfContainer') pdfContainer: ElementRef;
pdfSrc = 'path/to/your/document.pdf';
showDateInput = false;
selectedDate: string;
// Определение позиций полей (эти значения динамически устанавливаются на основе анализа PDF)
signatureField = { x: 100, y: 200, width: 80, height: 30 };
dateField = { x: 200, y: 250, width: 100, height: 30 };
ngOnInit() {
// Настройка прослушивателей событий для взаимодействий с PDF
this.setupFieldInteractions();
}
setupFieldInteractions() {
// Логика обнаружения, когда пользователи кликают по областям PDF
// и определение, кликают ли они по полям подписи/даты
}
openSignaturePad() {
// Открытие модального окна или наложения планшета для подписи
console.log('Открытие планшета для подписи');
// Реализация покажет canvas для подписи
}
saveDateInput() {
console.log('Дата сохранена:', this.selectedDate);
// Логика сохранения даты в документ PDF
this.showDateInput = false;
}
}
Примеры кода и реализация
Полный пример с использованием ngx-extended-pdf-viewer
Вот комплексная реализация с использованием ngx-extended-pdf-viewer:
import { Component } from '@angular/core';
@Component({
selector: 'app-pdf-forms-example',
template: `
<div class="pdf-forms-container">
<ngx-extended-pdf-viewer
[src]="pdfSrc"
[useBrowserLocale]="true"
[height]="'80vh'"
[textLayer]="true"
[handTool]="true"
[stampTool]="true"
[signatureTool]="true"
[formElements]="true"
(pageRendered)="onPageRendered($event)"
(fieldClick)="onFieldClick($event)">
<!-- Пользовательская панель инструментов с кнопкой подписи -->
<ngx-pdf-toolbar>
<button class="custom-sign-btn" (click)="openSignaturePad()">
Подписать документ
</button>
</ngx-pdf-toolbar>
</ngx-extended-pdf-viewer>
<!-- Модальное окно планшета для подписи -->
<div *ngIf="showSignatureModal" class="signature-modal">
<div class="modal-content">
<h3>Подписать документ</h3>
<canvas #signatureCanvas width="400" height="200"></canvas>
<div class="modal-buttons">
<button (click)="clearSignature()">Очистить</button>
<button (click)="saveSignature()">Сохранить подпись</button>
<button (click)="closeSignatureModal()">Отмена</button>
</div>
</div>
</div>
<!-- Наложение выборщика даты -->
<div *ngIf="showDatePicker" class="date-picker-overlay"
[style.left]="datePosition.x + 'px'"
[style.top]="datePosition.y + 'px'">
<input type="date"
[(ngModel)]="selectedDate"
(change)="onDateSelect($event)"
(blur)="hideDatePicker()">
</div>
</div>
`,
styles: [`
.pdf-forms-container { position: relative; }
.custom-sign-btn {
background: #007bff;
color: white;
border: none;
padding: 5px 10px;
border-radius: 3px;
cursor: pointer;
}
.signature-modal {
position: fixed;
top: 0; left: 0;
width: 100%; height: 100%;
background: rgba(0,0,0,0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
}
.modal-content {
background: white;
padding: 20px;
border-radius: 8px;
text-align: center;
}
.modal-buttons {
margin-top: 15px;
}
.modal-buttons button {
margin: 0 5px;
padding: 5px 10px;
}
.date-picker-overlay {
position: absolute;
z-index: 100;
background: white;
border: 1px solid #ccc;
padding: 5px;
border-radius: 4px;
}
`]
})
export class PdfFormsExampleComponent {
pdfSrc = 'path/to/your/interactive-form.pdf';
showSignatureModal = false;
showDatePicker = false;
selectedDate: string;
datePosition = { x: 0, y: 0 };
@ViewChild('signatureCanvas') signatureCanvas: any;
onPageRendered(event: any) {
// Логика обработки, когда страницы PDF отрисованы
// Здесь можно настроить обнаружение полей
}
onFieldClick(event: any) {
// Обработка кликов по полям формы PDF
if (event.field && event.field.type === 'signature') {
this.openSignaturePad();
} else if (event.field && event.field.type === 'text') {
// Проверка, является ли это полем даты
if (event.field.name.toLowerCase().includes('date')) {
this.showDateAtPosition(event.x, event.y);
}
}
}
openSignaturePad() {
this.showSignatureModal = true;
this.setupSignatureCanvas();
}
setupSignatureCanvas() {
const canvas = this.signatureCanvas.nativeElement;
const ctx = canvas.getContext('2d');
let isDrawing = false;
const startDrawing = (e: any) => {
isDrawing = true;
ctx.beginPath();
ctx.moveTo(e.offsetX, e.offsetY);
};
const draw = (e: any) => {
if (!isDrawing) return;
ctx.lineTo(e.offsetX, e.offsetY);
ctx.stroke();
};
const stopDrawing = () => {
isDrawing = false;
};
canvas.addEventListener('mousedown', startDrawing);
canvas.addEventListener('mousemove', draw);
canvas.addEventListener('mouseup', stopDrawing);
}
clearSignature() {
const canvas = this.signatureCanvas.nativeElement;
const ctx = canvas.getContext('2d');
ctx.clearRect(0, 0, canvas.width, canvas.height);
}
saveSignature() {
const canvas = this.signatureCanvas.nativeElement;
const signatureData = canvas.toDataURL();
// Здесь сохраняется подпись в PDF
console.log('Подпись сохранена:', signatureData);
this.closeSignatureModal();
}
closeSignatureModal() {
this.showSignatureModal = false;
}
showDateAtPosition(x: number, y: number) {
this.datePosition = { x, y };
this.showDatePicker = true;
this.selectedDate = new Date().toISOString().split('T')[0];
}
onDateSelect(event: any) {
console.log('Выбрана дата:', this.selectedDate);
// Логика обновления поля формы PDF
}
hideDatePicker() {
setTimeout(() => this.showDatePicker = false, 200);
}
}
Лучшие практики и соображения по производительности
Оптимизация производительности
-
Ленивая загрузка: Загружайте модули просмотрщика PDF только при необходимости для уменьшения начального размера пакета.
-
Оптимизация canvas: Для планшетов подписи реализуйте правильную очистку canvas и управление памятью.
-
Дебаунсинг событий: Используйте дебаунсинг для событий взаимодействия с PDF для предотвращения проблем с производительностью.
-
Виртуальная прокрутка: Для больших PDF реализуйте виртуальную прокрутку для эффективной обработки множества страниц.
Соображения доступности
-
Навигация с клавиатуры: Убедитесь, что все интерактивные компоненты доступны с клавиатуры.
-
Поддержка скринридеров: Предоставьте правильные ARIA-метки и описания для элементов формы.
-
Управление фокусом: Правильно управляйте фокусом при показе/скрытии модальных окон и наложений.
Соображения безопасности
-
Проверка ввода: Проверяйте все пользовательские вводы, особенно для полей даты и данных подписи.
-
Обработка файлов: Безопасно обрабатывайте загрузку и скачивание PDF.
-
Очистка данных: Очищайте данные подписи и другие пользовательские вводы перед обработкой.
Советы по реализации
-
Начинайте с простого: Начните с базовых полей формы и постепенно добавляйте более сложные функции.
-
Используйте сервисы: Создайте специализированные сервисы для манипулирования PDF и обработки форм.
-
Тестирование: Тщательно тестируйте взаимодействия с PDF в разных браузерах и на устройствах.
-
Обработка ошибок: Реализуйте надежную обработку ошибок для сбоев загрузки PDF и отправки форм.
Источники
-
How to add interactive components like button/input on top of PDF in Angular? - Stack Overflow
-
Angular PDF Viewer | View, Annotate, Fill, Sign PDFs | Syncfusion
-
Building a Signature Pad in Angular: A Step-by-Step Guide | Medium
-
Help Syncfusion - Hand written signature in Angular PDF viewer Control
Заключение
Добавление интерактивных компонентов, таких как кнопки и поля ввода, поверх PDF в Angular достижимо через несколько проверенных подходов. Для ваших конкретных потребностей реализации функциональности подписи и полей ввода даты я рекомендую:
-
Начните с ngx-extended-pdf-viewer для комплексной поддержки форм и более простой реализации инструментов подписи.
-
Реализуйте пользовательские наложения для точного контроля над позиционированием компонентов и пользовательским опытом.
-
Используйте canvas-планшеты для подписи для естественного опыта подписи, который можно легко интегрировать с вашим рабочим процессом PDF.
-
Используйте реактивные формы Angular для надежной обработки форм и проверки.
-
Рассмотрите PDF.js Express для расширенных возможностей аннотирования, если ваш проект требует коммерческой лицензии.
Ключом к успеху является тщательное планирование логики обнаружения полей и обеспечение правильной обработки событий между вашим просмотрщиком PDF и компонентами Angular. Начните с базовых реализаций и постепенно добавляйте более сложные функции по мере знакомства с базовыми технологиями.