Bootstrap 5
Pdf Viewer
Ebook Template
Pdf Embed
Snippets Html
Exemples de présentation d’eBook PDF avec intégration d’un visualiseur PDF responsive en Bootstrap 5.
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="copyright" content="MEZGANI Said" />
<meta name="author" content="AngularForAll" />
<meta name="robots" content="noindex, nofollow" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Snippets Ebook Pdf Bootstrap 5 01 | AngularForAll</title>
<!-- Bootstrap 5 CSS -->
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
<!-- Bootstrap Icons -->
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<!-- Google Fonts -->
<link href="https://fonts.googleapis.com/css2?family=Inter:opsz,wght@14..32,300;14..32,400;14..32,500;14..32,600;14..32,700&display=swap" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Inter', sans-serif;
background: #f5f5f0;
overflow-x: hidden;
}
/* Navbar Style */
.navbar {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
box-shadow: 0 2px 20px rgba(0,0,0,0.1);
padding: 0.8rem 0;
}
.navbar-brand {
font-weight: 700;
font-size: 1.5rem;
color: white !important;
}
/* Sidebar Bibliothèque */
.library-sidebar {
background: white;
border-right: 1px solid #e0e0e0;
height: calc(100vh - 65px);
position: fixed;
top: 65px;
left: 0;
width: 280px;
overflow-y: auto;
padding: 1.5rem;
transition: all 0.3s ease;
z-index: 999;
box-shadow: 2px 0 10px rgba(0,0,0,0.05);
}
/* Cartes des livres */
.book-card {
background: white;
border-radius: 12px;
padding: 1rem;
margin-bottom: 1rem;
cursor: pointer;
transition: all 0.3s ease;
border: 2px solid transparent;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.book-card:hover {
transform: translateX(5px);
box-shadow: 0 5px 15px rgba(0,0,0,0.15);
background: #f8f9fa;
}
.book-card.active {
border-color: #667eea;
background: linear-gradient(135deg, rgba(102,126,234,0.1) 0%, rgba(118,75,162,0.1) 100%);
}
.book-cover {
width: 60px;
height: 80px;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
border-radius: 8px;
display: flex;
align-items: center;
justify-content: center;
color: white;
font-size: 1.8rem;
}
.book-title {
font-weight: 600;
font-size: 0.9rem;
margin-bottom: 0.25rem;
}
.book-author {
font-size: 0.75rem;
color: #7f8c8d;
}
/* Zone de lecture PDF */
.pdf-viewer {
margin-left: 280px;
padding: 20px;
min-height: calc(100vh - 65px);
background: #e8e6e1;
}
.pdf-container {
background: white;
border-radius: 16px;
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
overflow: hidden;
}
.pdf-toolbar {
background: #2c3e50;
color: white;
padding: 1rem;
display: flex;
justify-content: space-between;
align-items: center;
flex-wrap: wrap;
gap: 0.5rem;
}
.pdf-controls {
display: flex;
gap: 0.5rem;
align-items: center;
flex-wrap: wrap;
}
.pdf-controls button, .zoom-controls button {
background: rgba(255,255,255,0.15);
border: none;
color: white;
padding: 0.5rem 1rem;
border-radius: 8px;
transition: all 0.3s;
}
.pdf-controls button:hover, .zoom-controls button:hover {
background: rgba(255,255,255,0.3);
transform: translateY(-2px);
}
.page-input {
width: 70px;
text-align: center;
background: rgba(255,255,255,0.15);
border: 1px solid rgba(255,255,255,0.3);
color: white;
border-radius: 8px;
padding: 0.5rem;
}
.page-input:focus {
outline: none;
background: rgba(255,255,255,0.25);
}
.pdf-canvas-container {
background: #525659;
padding: 2rem;
display: flex;
justify-content: center;
align-items: center;
min-height: 500px;
overflow: auto;
}
canvas {
box-shadow: 0 5px 20px rgba(0,0,0,0.2);
max-width: 100%;
height: auto;
display: block;
margin: 0 auto;
}
/* Mode nuit */
body.night-mode {
background: #1a1a1a;
}
body.night-mode .library-sidebar {
background: #2c2c2c;
border-color: #404040;
}
body.night-mode .book-card {
background: #363636;
color: #e0e0e0;
}
body.night-mode .book-card.active {
background: #2c3e50;
}
body.night-mode .book-title {
color: #ecf0f1;
}
body.night-mode .pdf-viewer {
background: #2c2c2c;
}
body.night-mode .pdf-container {
background: #363636;
}
/* Progress bar */
.reading-progress {
position: fixed;
top: 65px;
left: 0;
width: 100%;
height: 3px;
background: rgba(102,126,234,0.2);
z-index: 1000;
}
.progress-bar-custom {
height: 100%;
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
transition: width 0.3s ease;
width: 0%;
}
/* Loading spinner */
.loading {
text-align: center;
padding: 2rem;
}
/* Message d'erreur */
.error-message {
background: #dc3545;
color: white;
padding: 1rem;
border-radius: 8px;
text-align: center;
margin: 1rem;
}
/* Responsive */
@media (max-width: 768px) {
.library-sidebar {
transform: translateX(-100%);
position: fixed;
top: 65px;
left: 0;
width: 280px;
height: calc(100vh - 65px);
z-index: 1000;
}
.library-sidebar.show {
transform: translateX(0);
}
.pdf-viewer {
margin-left: 0;
padding: 10px;
}
.pdf-toolbar {
flex-direction: column;
}
.pdf-controls {
justify-content: center;
}
}
/* Animations */
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.book-card {
animation: fadeIn 0.5s ease forwards;
}
/* Scrollbar */
::-webkit-scrollbar {
width: 8px;
height: 8px;
}
::-webkit-scrollbar-track {
background: #f1f1f1;
}
::-webkit-scrollbar-thumb {
background: #888;
border-radius: 4px;
}
::-webkit-scrollbar-thumb:hover {
background: #555;
}
/* Bouton toggle sidebar mobile */
.toggle-sidebar-btn {
background: rgba(255,255,255,0.2);
border: none;
color: white;
padding: 0.5rem 1rem;
border-radius: 8px;
}
</style>
</head>
<body>
<!-- Progress Bar -->
<div class="reading-progress">
<div class="progress-bar-custom" id="readingProgress"></div>
</div>
<!-- Navbar -->
<nav class="navbar navbar-expand-lg fixed-top">
<div class="container-fluid">
<a class="navbar-brand" href="#">
<i class="bi bi-book-half"></i> Ebook Reader Pro
</a>
<button class="toggle-sidebar-btn d-md-none" id="toggleLibraryBtn">
<i class="bi bi-list fs-5"></i> Bibliothèque
</button>
<div class="ms-auto d-flex gap-2">
<button class="btn btn-light btn-sm" id="fullscreenBtn" title="Plein écran">
<i class="bi bi-arrows-fullscreen"></i>
</button>
<button class="btn btn-light btn-sm" id="nightModeBtn" title="Mode nuit">
<i class="bi bi-moon"></i>
</button>
</div>
</div>
</nav>
<div class="row g-0">
<!-- Bibliothèque latérale -->
<div class="library-sidebar" id="librarySidebar">
<h5 class="mb-3">
<i class="bi bi-journal-bookmark-fill"></i> Ma Bibliothèque
</h5>
<div id="bookList">
<!-- Les livres seront chargés ici -->
</div>
</div>
<!-- Zone de lecture -->
<div class="pdf-viewer" id="pdfViewer">
<div class="pdf-container">
<div class="pdf-toolbar">
<div class="pdf-controls">
<button id="prevPage">
<i class="bi bi-chevron-left"></i> Précédent
</button>
<span>
Page <input type="number" id="pageNum" class="page-input" value="1" min="1">
/ <span id="pageCount">0</span>
</span>
<button id="nextPage">
Suivant <i class="bi bi-chevron-right"></i>
</button>
</div>
<div class="pdf-controls">
<button id="zoomOut" title="Zoom arrière">
<i class="bi bi-zoom-out"></i>
</button>
<span id="zoomLevel" style="min-width: 50px;">100%</span>
<button id="zoomIn" title="Zoom avant">
<i class="bi bi-zoom-in"></i>
</button>
</div>
</div>
<div class="pdf-canvas-container" id="canvasContainer">
<div class="loading" id="loadingIndicator">
<div class="spinner-border text-light" role="status">
<span class="visually-hidden">Chargement...</span>
</div>
<p class="mt-2 text-light">Chargement du PDF...</p>
</div>
<canvas id="pdfCanvas" style="display: none;"></canvas>
</div>
</div>
</div>
</div>
<!-- PDF.js Library -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.min.js"></script>
<script>
pdfjsLib.GlobalWorkerOptions.workerSrc = 'https://cdnjs.cloudflare.com/ajax/libs/pdf.js/2.16.105/pdf.worker.min.js';
</script>
<script>
// Configuration - Utilisation d'un PDF de démonstration valide
const DEMO_PDF_URL = 'public/exemple.pdf';
// Bibliothèque de livres (avec des PDFs valides)
const books = [
{
id: 1,
title: "Introduction à JavaScript",
author: "Mozilla Foundation",
cover: "📘",
url: DEMO_PDF_URL,
progress: 0
},
{
id: 2,
title: "Guide CSS Moderne",
author: "Web.dev",
cover: "🎨",
url: DEMO_PDF_URL,
progress: 0
},
{
id: 3,
title: "HTML5 Avancé",
author: "W3C",
cover: "🌐",
url: DEMO_PDF_URL,
progress: 0
},
{
id: 4,
title: "Bootstrap 5 Mastery",
author: "Twitter",
cover: "🚀",
url: DEMO_PDF_URL,
progress: 0
},
{
id: 5,
title: "React pour Débutants",
author: "Facebook",
cover: "⚛️",
url: DEMO_PDF_URL,
progress: 0
}
];
// Variables globales
let pdfDoc = null;
let currentPage = 1;
let currentScale = 1.2;
let totalPages = 0;
let currentBookId = 1;
// Initialisation
async function init() {
loadBookList();
await loadPDF(DEMO_PDF_URL);
attachEventListeners();
loadSavedSettings();
}
// Charger la liste des livres
function loadBookList() {
const bookListDiv = document.getElementById('bookList');
bookListDiv.innerHTML = books.map(book => `
<div class="book-card" data-id="${book.id}" data-url="${book.url}">
<div class="d-flex align-items-center">
<div class="book-cover me-3">
${book.cover}
</div>
<div class="flex-grow-1">
<div class="book-title">${book.title}</div>
<div class="book-author">${book.author}</div>
<div class="progress mt-2" style="height: 4px;">
<div class="progress-bar bg-success" style="width: ${book.progress}%"></div>
</div>
<small class="text-muted">${book.progress}% lu</small>
</div>
</div>
</div>
`).join('');
// Ajouter les événements de clic
document.querySelectorAll('.book-card').forEach(card => {
card.addEventListener('click', async () => {
document.querySelectorAll('.book-card').forEach(c => c.classList.remove('active'));
card.classList.add('active');
const url = card.dataset.url;
const id = parseInt(card.dataset.id);
currentBookId = id;
await loadPDF(url);
// Fermer la sidebar sur mobile
if (window.innerWidth < 768) {
document.getElementById('librarySidebar').classList.remove('show');
}
});
});
// Activer le premier livre
if (document.querySelector('.book-card')) {
document.querySelector('.book-card').classList.add('active');
}
}
// Charger un PDF
async function loadPDF(url) {
const loadingIndicator = document.getElementById('loadingIndicator');
const canvas = document.getElementById('pdfCanvas');
try {
// Afficher le chargement
loadingIndicator.style.display = 'block';
canvas.style.display = 'none';
// Charger le PDF
const loadingTask = pdfjsLib.getDocument(url);
pdfDoc = await loadingTask.promise;
totalPages = pdfDoc.numPages;
document.getElementById('pageCount').textContent = totalPages;
document.getElementById('pageNum').max = totalPages;
document.getElementById('pageNum').value = 1;
currentPage = 1;
await renderPage(currentPage);
// Cacher le chargement
loadingIndicator.style.display = 'none';
canvas.style.display = 'block';
} catch (error) {
console.error('Erreur de chargement PDF:', error);
loadingIndicator.innerHTML = `
<div class="alert alert-danger">
<i class="bi bi-exclamation-triangle-fill"></i>
Erreur de chargement du PDF. Vérifiez votre connexion internet.
</div>
`;
}
}
// Rendre une page
async function renderPage(pageNum) {
if (!pdfDoc) return;
try {
const page = await pdfDoc.getPage(pageNum);
const viewport = page.getViewport({ scale: currentScale });
const canvas = document.getElementById('pdfCanvas');
const context = canvas.getContext('2d');
canvas.height = viewport.height;
canvas.width = viewport.width;
const renderContext = {
canvasContext: context,
viewport: viewport
};
await page.render(renderContext).promise;
updateProgress(pageNum);
} catch (error) {
console.error('Erreur de rendu:', error);
}
}
// Mettre à jour la progression
function updateProgress(pageNum) {
if (totalPages > 0) {
const progress = (pageNum / totalPages) * 100;
document.getElementById('readingProgress').style.width = `${progress}%`;
// Sauvegarder la progression
localStorage.setItem(`book_${currentBookId}_page`, pageNum);
localStorage.setItem(`book_${currentBookId}_progress`, progress);
}
}
// Page suivante
async function nextPage() {
if (currentPage < totalPages) {
currentPage++;
document.getElementById('pageNum').value = currentPage;
await renderPage(currentPage);
}
}
// Page précédente
async function prevPage() {
if (currentPage > 1) {
currentPage--;
document.getElementById('pageNum').value = currentPage;
await renderPage(currentPage);
}
}
// Zoom avant
async function zoomIn() {
if (currentScale < 3) {
currentScale += 0.2;
document.getElementById('zoomLevel').textContent = `${Math.round(currentScale * 100)}%`;
await renderPage(currentPage);
}
}
// Zoom arrière
async function zoomOut() {
if (currentScale > 0.6) {
currentScale -= 0.2;
document.getElementById('zoomLevel').textContent = `${Math.round(currentScale * 100)}%`;
await renderPage(currentPage);
}
}
// Mode nuit
function toggleNightMode() {
document.body.classList.toggle('night-mode');
const nightBtn = document.getElementById('nightModeBtn');
if (document.body.classList.contains('night-mode')) {
nightBtn.innerHTML = '<i class="bi bi-sun"></i>';
localStorage.setItem('nightMode', 'true');
} else {
nightBtn.innerHTML = '<i class="bi bi-moon"></i>';
localStorage.setItem('nightMode', 'false');
}
}
// Plein écran
function toggleFullscreen() {
const viewer = document.getElementById('pdfViewer');
if (!document.fullscreenElement) {
viewer.requestFullscreen();
} else {
document.exitFullscreen();
}
}
// Charger les paramètres sauvegardés
function loadSavedSettings() {
const nightMode = localStorage.getItem('nightMode');
if (nightMode === 'true') {
document.body.classList.add('night-mode');
document.getElementById('nightModeBtn').innerHTML = '<i class="bi bi-sun"></i>';
}
}
// Attacher les événements
function attachEventListeners() {
document.getElementById('prevPage').addEventListener('click', prevPage);
document.getElementById('nextPage').addEventListener('click', nextPage);
document.getElementById('zoomIn').addEventListener('click', zoomIn);
document.getElementById('zoomOut').addEventListener('click', zoomOut);
document.getElementById('nightModeBtn').addEventListener('click', toggleNightMode);
document.getElementById('fullscreenBtn').addEventListener('click', toggleFullscreen);
document.getElementById('pageNum').addEventListener('change', async function() {
let pageNum = parseInt(this.value);
if (pageNum >= 1 && pageNum <= totalPages) {
currentPage = pageNum;
await renderPage(currentPage);
} else {
this.value = currentPage;
}
});
// Toggle sidebar mobile
document.getElementById('toggleLibraryBtn').addEventListener('click', () => {
document.getElementById('librarySidebar').classList.toggle('show');
});
// Raccourcis clavier
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
prevPage();
} else if (e.key === 'ArrowRight') {
nextPage();
} else if (e.key === '+' || e.key === '=') {
zoomIn();
} else if (e.key === '-') {
zoomOut();
}
});
// Fermer la sidebar en cliquant à l'extérieur sur mobile
document.addEventListener('click', (e) => {
if (window.innerWidth < 768) {
const sidebar = document.getElementById('librarySidebar');
const toggleBtn = document.getElementById('toggleLibraryBtn');
if (!sidebar.contains(e.target) && !toggleBtn.contains(e.target) && sidebar.classList.contains('show')) {
sidebar.classList.remove('show');
}
}
});
}
// Démarrer l'application
init();
</script>
</body>
</html>
Ouvrir cet aperçu dans un nouvel onglet du navigateur
🔗 Ouvrir dans le navigateur