Продолжение: Дополнительные утилиты
13. Прелоадер (Preloader)
HTML структура:
<!-- Прелоадер -->
<div class="preloader" id="preloader">
<div class="preloader-content">
<div class="preloader-logo">
<svg viewBox="0 0 100 100" class="logo-svg">
<circle cx="50" cy="50" r="45" class="logo-circle"/>
<path d="M30,50 L45,65 L70,35" class="logo-check"/>
</svg>
</div>
<div class="preloader-spinner">
<div class="spinner-ring"></div>
<div class="spinner-ring"></div>
<div class="spinner-ring"></div>
</div>
<p class="preloader-text">Загрузка<span class="dots"></span></p>
<div class="preloader-progress">
<div class="progress-bar-preloader">
<div class="progress-fill-preloader" id="preloaderProgress"></div>
</div>
<span class="progress-percent" id="preloaderPercent">0%</span>
</div>
</div>
</div>
CSS стили для прелоадера:
/* Прелоадер */
.preloader {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: linear-gradient(135deg, #ffffff 0%, #f8f9fa 100%);
display: flex;
align-items: center;
justify-content: center;
z-index: 99999;
transition: opacity 0.5s ease, visibility 0.5s ease;
}
.preloader.hidden {
opacity: 0;
visibility: hidden;
}
.preloader-content {
text-align: center;
max-width: 300px;
}
/* Логотип */
.preloader-logo {
width: 100px;
height: 100px;
margin: 0 auto 30px;
}
.logo-svg {
width: 100%;
height: 100%;
}
.logo-circle {
fill: none;
stroke: var(--primary-color);
stroke-width: 3;
stroke-dasharray: 283;
stroke-dashoffset: 283;
animation: drawCircle 2s ease-in-out infinite;
}
.logo-check {
fill: none;
stroke: var(--primary-color);
stroke-width: 4;
stroke-linecap: round;
stroke-linejoin: round;
stroke-dasharray: 60;
stroke-dashoffset: 60;
animation: drawCheck 2s ease-in-out infinite;
animation-delay: 0.5s;
}
/* Спиннер */
.preloader-spinner {
position: relative;
width: 80px;
height: 80px;
margin: 0 auto 20px;
}
.spinner-ring {
position: absolute;
width: 100%;
height: 100%;
border: 3px solid transparent;
border-top-color: var(--primary-color);
border-radius: 50%;
animation: spin 1.5s cubic-bezier(0.68, -0.55, 0.265, 1.55) infinite;
}
.spinner-ring:nth-child(2) {
width: 70%;
height: 70%;
top: 15%;
left: 15%;
border-top-color: var(--secondary-color);
animation-delay: 0.2s;
}
.spinner-ring:nth-child(3) {
width: 40%;
height: 40%;
top: 30%;
left: 30%;
border-top-color: #ffa94d;
animation-delay: 0.4s;
}
/* Текст загрузки */
.preloader-text {
font-size: 18px;
font-weight: 600;
color: var(--text-color);
margin-bottom: 20px;
}
.dots::after {
content: '';
animation: dots 1.5s steps(4, end) infinite;
}
/* Прогресс-бар */
.preloader-progress {
width: 100%;
}
.progress-bar-preloader {
width: 100%;
height: 4px;
background: #e9ecef;
border-radius: 2px;
overflow: hidden;
margin-bottom: 10px;
}
.progress-fill-preloader {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
width: 0%;
transition: width 0.3s ease;
border-radius: 2px;
}
.progress-percent {
font-size: 14px;
font-weight: 600;
color: var(--primary-color);
}
/* Анимации */
@keyframes drawCircle {
0% {
stroke-dashoffset: 283;
}
50% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: 283;
}
}
@keyframes drawCheck {
0% {
stroke-dashoffset: 60;
}
50% {
stroke-dashoffset: 0;
}
100% {
stroke-dashoffset: 60;
}
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
@keyframes dots {
0%, 20% {
content: '';
}
40% {
content: '.';
}
60% {
content: '..';
}
80%, 100% {
content: '...';
}
}
—
14. Система уведомлений (Toast Notifications)
HTML структура:
<!-- Контейнер для уведомлений -->
<div class="toast-container" id="toastContainer"></div>
CSS стили для уведомлений:
/* Контейнер уведомлений */
.toast-container {
position: fixed;
top: 20px;
right: 20px;
z-index: 99998;
display: flex;
flex-direction: column;
gap: 15px;
max-width: 400px;
}
/* Уведомление */
.toast {
background: white;
border-radius: 12px;
padding: 16px 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
display: flex;
align-items: flex-start;
gap: 15px;
min-width: 300px;
animation: slideInRight 0.4s cubic-bezier(0.68, -0.55, 0.265, 1.55);
position: relative;
overflow: hidden;
}
.toast.removing {
animation: slideOutRight 0.3s ease forwards;
}
/* Иконка уведомления */
.toast-icon {
width: 24px;
height: 24px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
font-size: 18px;
}
/* Контент уведомления */
.toast-content {
flex: 1;
}
.toast-title {
font-size: 15px;
font-weight: 600;
color: var(--text-color);
margin-bottom: 4px;
}
.toast-message {
font-size: 14px;
color: #6c757d;
line-height: 1.5;
margin: 0;
}
/* Кнопка закрытия */
.toast-close {
width: 24px;
height: 24px;
flex-shrink: 0;
background: transparent;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #6c757d;
transition: all 0.3s ease;
border-radius: 6px;
padding: 0;
}
.toast-close:hover {
background: #f8f9fa;
color: var(--text-color);
}
/* Прогресс-бар */
.toast-progress {
position: absolute;
bottom: 0;
left: 0;
height: 3px;
background: currentColor;
width: 100%;
transform-origin: left;
animation: progressBar linear forwards;
}
/* Типы уведомлений */
.toast.success {
border-left: 4px solid #4CAF50;
}
.toast.success .toast-icon {
color: #4CAF50;
}
.toast.success .toast-progress {
color: #4CAF50;
}
.toast.error {
border-left: 4px solid #f44336;
}
.toast.error .toast-icon {
color: #f44336;
}
.toast.error .toast-progress {
color: #f44336;
}
.toast.warning {
border-left: 4px solid #ff9800;
}
.toast.warning .toast-icon {
color: #ff9800;
}
.toast.warning .toast-progress {
color: #ff9800;
}
.toast.info {
border-left: 4px solid #2196F3;
}
.toast.info .toast-icon {
color: #2196F3;
}
.toast.info .toast-progress {
color: #2196F3;
}
/* Анимации */
@keyframes slideInRight {
from {
transform: translateX(400px);
opacity: 0;
}
to {
transform: translateX(0);
opacity: 1;
}
}
@keyframes slideOutRight {
from {
transform: translateX(0);
opacity: 1;
}
to {
transform: translateX(400px);
opacity: 0;
}
}
@keyframes progressBar {
from {
transform: scaleX(1);
}
to {
transform: scaleX(0);
}
}
/* Адаптивность */
@media (max-width: 768px) {
.toast-container {
top: auto;
bottom: 20px;
right: 20px;
left: 20px;
max-width: none;
}
.toast {
min-width: auto;
width: 100%;
}
}
—
15. Плавная прокрутка и навигация
HTML структура:
<!-- Кнопка "Наверх" -->
<button class="scroll-to-top" id="scrollToTop" aria-label="Прокрутить наверх">
<i class="fas fa-arrow-up"></i>
</button>
<!-- Индикатор прогресса чтения -->
<div class="reading-progress" id="readingProgress">
<div class="reading-progress-bar"></div>
</div>
<!-- Быстрая навигация -->
<nav class="quick-nav" id="quickNav">
<div class="quick-nav-header">
<span>Навигация</span>
<button class="quick-nav-toggle" id="quickNavToggle">
<i class="fas fa-chevron-down"></i>
</button>
</div>
<ul class="quick-nav-list">
<li><a href="#services" data-scroll>Услуги</a></li>
<li><a href="#portfolio" data-scroll>Портфолио</a></li>
<li><a href="#process" data-scroll>Процесс работы</a></li>
<li><a href="#reviews" data-scroll>Отзывы</a></li>
<li><a href="#faq" data-scroll>Вопросы</a></li>
<li><a href="#contacts" data-scroll>Контакты</a></li>
</ul>
</nav>
CSS стили:
/* Кнопка "Наверх" */
.scroll-to-top {
position: fixed;
bottom: 30px;
right: 30px;
width: 50px;
height: 50px;
background: linear-gradient(135deg, var(--primary-color), var(--secondary-color));
border: none;
border-radius: 50%;
color: white;
font-size: 20px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
box-shadow: 0 5px 20px rgba(255, 107, 107, 0.3);
z-index: 9998;
opacity: 0;
visibility: hidden;
transform: translateY(20px);
transition: all 0.3s ease;
}
.scroll-to-top.visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
}
.scroll-to-top:hover {
transform: translateY(-5px);
box-shadow: 0 8px 25px rgba(255, 107, 107, 0.4);
}
.scroll-to-top:active {
transform: translateY(-2px);
}
/* Индикатор прогресса чтения */
.reading-progress {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: rgba(0, 0, 0, 0.05);
z-index: 9999;
opacity: 0;
transition: opacity 0.3s ease;
}
.reading-progress.visible {
opacity: 1;
}
.reading-progress-bar {
height: 100%;
background: linear-gradient(90deg, var(--primary-color), var(--secondary-color));
width: 0%;
transition: width 0.1s ease;
}
/* Быстрая навигация */
.quick-nav {
position: fixed;
left: 30px;
top: 50%;
transform: translateY(-50%);
background: white;
border-radius: 15px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.1);
padding: 15px;
z-index: 9997;
opacity: 0;
visibility: hidden;
transition: all 0.3s ease;
}
.quick-nav.visible {
opacity: 1;
visibility: visible;
}
.quick-nav.collapsed .quick-nav-list {
max-height: 0;
opacity: 0;
margin-top: 0;
}
.quick-nav-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 10px;
padding-bottom: 10px;
border-bottom: 1px solid #e9ecef;
margin-bottom: 10px;
}
.quick-nav-header span {
font-size: 14px;
font-weight: 600;
color: var(--text-color);
}
.quick-nav-toggle {
width: 24px;
height: 24px;
background: transparent;
border: none;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
color: #6c757d;
transition: all 0.3s ease;
border-radius: 6px;
}
.quick-nav-toggle:hover {
background: #f8f9fa;
}
.quick-nav.collapsed .quick-nav-toggle i {
transform: rotate(-180deg);
}
.quick-nav-list {
list-style: none;
padding: 0;
margin: 0;
max-height: 300px;
opacity: 1;
overflow: hidden;
transition: all 0.3s ease;
}
.quick-nav-list li {
margin-bottom: 5px;
}
.quick-nav-list li:last-child {
margin-bottom: 0;
}
.quick-nav-list a {
display: block;
padding: 8px 12px;
color: #6c757d;
text-decoration: none;
font-size: 14px;
border-radius: 8px;
transition: all 0.3s ease;
position: relative;
}
.quick-nav-list a::before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
width: 3px;
height: 0;
background: var(--primary-color);
border-radius: 0 2px 2px 0;
transition: height 0.3s ease;
}
.quick-nav-list a:hover,
.quick-nav-list a.active {
color: var(--primary-color);
background: rgba(255, 107, 107, 0.05);
padding-left: 18px;
}
.quick-nav-list a:hover::before,
.quick-nav-list a.active::before {
height: 100%;
}
/* Адаптивность */
@media (max-width: 1200px) {
.quick-nav {
left: 20px;
}
}
@media (max-width: 992px) {
.quick-nav {
display: none;
}
.scroll-to-top {
bottom: 20px;
right: 20px;
width: 45px;
height: 45px;
font-size: 18px;
}
}
@media (max-width: 576px) {
.scroll-to-top {
bottom: 15px;
right: 15px;
width: 40px;
height: 40px;
font-size: 16px;
}
}
—
16. JavaScript для всех утилит
JavaScript код:
// ============================================
// ПРЕЛОАДЕР
// ============================================
class Preloader {
constructor() {
this.preloader = document.getElementById('preloader');
this.progressBar = document.getElementById('preloaderProgress');
this.progressPercent = document.getElementById('preloaderPercent');
this.progress = 0;
this.isLoaded = false;
if (this.preloader) {
this.init();
}
}
init() {
// Симуляция загрузки
this.simulateLoading();
// Реальная загрузка ресурсов
this.trackRealLoading();
// Минимальное время показа прелоадера
setTimeout(() => {
this.isLoaded = true;
this.checkComplete();
}, 1000);
}
simulateLoading() {
const interval = setInterval(() => {
if (this.progress < 90) {
this.progress += Math.random() * 10;
this.updateProgress(Math.min(this.progress, 90));
} else {
clearInterval(interval);
}
}, 200);
}
trackRealLoading() {
if (document.readyState === 'complete') {
this.onLoadComplete();
} else {
window.addEventListener('load', () => this.onLoadComplete());
}
}
onLoadComplete() {
this.updateProgress(100);
setTimeout(() => {
this.isLoaded = true;
this.checkComplete();
}, 300);
}
checkComplete() {
if (this.isLoaded && this.progress >= 100) {
this.hide();
}
}
updateProgress(value) {
this.progress = value;
if (this.progressBar) {
this.progressBar.style.width = value + '%';
}
if (this.progressPercent) {
this.progressPercent.textContent = Math.round(value) + '%';
}
if (value >= 100) {
this.checkComplete();
}
}
hide() {
this.preloader.classList.add('hidden');
document.body.style.overflow = '';
setTimeout(() => {
this.preloader.style.display = 'none';
}, 500);
}
}
// ============================================
// СИСТЕМА УВЕДОМЛЕНИЙ
// ============================================
class ToastNotification {
constructor() {
this.container = document.getElementById('toastContainer');
if (!this.container) {
this.createContainer();
}
this.toasts = [];
}
createContainer() {
this.container = document.createElement('div');
this.container.id = 'toastContainer';
this.container.className = 'toast-container';
document.body.appendChild(this.container);
}
show(options = {}) {
const {
type = 'info',
title = '',
message = '',
duration = 5000,
closable = true
} = options;
const toast = this.createToast(type, title, message, closable);
this.container.appendChild(toast);
this.toasts.push(toast);
// Автоматическое удаление
if (duration > 0) {
const progressBar = toast.querySelector('.toast-progress');
if (progressBar) {
progressBar.style.animationDuration = duration + 'ms';
}
setTimeout(() => {
this.remove(toast);
}, duration);
}
return toast;
}
createToast(type, title, message, closable) {
const toast = document.createElement('div');
toast.className = `toast ${type}`;
const icons = {
success: 'fa-check-circle',
error: 'fa-times-circle',
warning: 'fa-exclamation-triangle',
info: 'fa-info-circle'
};
toast.innerHTML = `
<div class="toast-icon">
<i class="fas ${icons[type] || icons.info}"></i>
</div>
<div class="toast-content">
${title ? `<div class="toast-title">${title}</div>` : ''}
${message ? `<p class="toast-message">${message}</p>` : ''}
</div>
${closable ? `
<button class="toast-close" aria-label="Закрыть">
<i class="fas fa-times"></i>
</button>
` : ''}
<div class="toast-progress"></div>
`;
// Обработчик закрытия
if (closable) {
const closeBtn = toast.querySelector('.toast-close');
closeBtn.addEventListener('click', () => this.remove(toast));
}
return toast;
}
remove(toast) {
toast.classList.add('removing');
setTimeout(() => {
if (toast.parentNode) {
toast.parentNode.removeChild(toast);
}
this.toasts = this.toasts.filter(t => t !== toast);
}, 300);
}
// Удобные методы
success(title, message, duration) {
return this.show({ type: 'success', title, message, duration });
}
error(title, message, duration) {
return this.show({ type: 'error', title, message, duration });
}
warning(title, message, duration) {
return this.show({ type: 'warning', title, message, duration });
}
info(title, message, duration) {
return this.show({ type: 'info', title, message, duration });
}
clearAll() {
this.toasts.forEach(toast => this.remove(toast));
}
}
// Глобальный экземпляр
window.toast = new ToastNotification();
// ============================================
// ПЛАВНАЯ ПРОКРУТКА
// ============================================
class SmoothScroll {
constructor() {
this.init();
}
init() {
// Обработка всех ссылок с data-scroll
document.querySelectorAll('[data-scroll]').forEach(link => {
link.addEventListener('click', (e) => {
e.preventDefault();
const targetId = link.getAttribute('href');
this.scrollTo(targetId);
});
});
// Обработка якорных ссылок
document.querySelectorAll('a[href^="#"]').forEach(link => {
if (!link.hasAttribute('data-scroll')) {
link.addEventListener('click', (e) => {
const targetId = link.getAttribute('href');
if (targetId !== '#' && document.querySelector(targetId)) {
e.preventDefault();
this.scrollTo(targetId);
}
});
}
});
}
scrollTo(target, offset = 80) {
const element = typeof target === 'string'
? document.querySelector(target)
: target;
if (!element) return;
const targetPosition = element.getBoundingClientRect().top + window.pageYOffset;
const startPosition = window.pageYOffset;
const distance = targetPosition - startPosition - offset;
const duration = 1000;
let start = null;
const animation = (currentTime) => {
if (start === null) start = currentTime;
const timeElapsed = currentTime - start;
const run = this.ease(timeElapsed, startPosition, distance, duration);
window.scrollTo(0, run);
if (timeElapsed < duration) {
requestAnimationFrame(animation);
}
};
requestAnimationFrame(animation);
}
ease(t, b, c, d) {
t /= d / 2;
if (t < 1) return c / 2 * t * t + b;
t--;
return -c / 2 * (t * (t - 2) - 1) + b;
}
}
// ============================================
// КНОПКА "НАВЕРХ"
// ============================================
class ScrollToTop {
constructor() {
this.button = document.getElementById('scrollToTop');
if (this.button) {
this.init();
}
}
init() {
// Показ/скрытие кнопки при прокрутке
window.addEventListener('scroll', () => {
if (window.pageYOffset > 300) {
this.button.classList.add('visible');
} else {
this.button.classList.remove('visible');
}
});
// Клик по кнопке
this.button.addEventListener('click', () => {
window.scrollTo({
top: 0,
behavior: 'smooth'
});
});
}
}
// ============================================
// ИНДИКАТОР ПРОГРЕССА ЧТЕНИЯ
// ============================================
class ReadingProgress {
constructor() {
this.indicator = document.getElementById('readingProgress');
if (this.indicator) {
this.bar = this.indicator.querySelector('.reading-progress-bar');
this.init();
}
}
init() {
window.addEventListener('scroll', () => {
this.updateProgress();
});
this.updateProgress();
}
updateProgress() {
const windowHeight = window.innerHeight;
const documentHeight = document.documentElement.scrollHeight - windowHeight;
const scrolled = window.pageYOffset;
const progress = (scrolled / documentHeight) * 100;
if (this.bar) {
this.bar.style.width = progress + '%';
}
// Показываем индикатор только при прокрутке
if (scrolled > 100) {
this.indicator.classList.add('visible');
} else {
this.indicator.classList.remove('visible');
}
}
}
// ============================================
// БЫСТРАЯ НАВИГАЦИЯ
// ============================================
class QuickNavigation {
constructor() {
this.nav = document.getElementById('quickNav');
if (this.nav) {
this.toggle = document.getElementById('quickNavToggle');
this.links = this.nav.querySelectorAll('.quick-nav-list a');
this.init();
}
}
init() {
// Показ/скрытие навигации
window.addEventListener('scroll', () => {
if (window.pageYOffset > 500) {
this.nav.classList.add('visible');
} else {
this.nav.classList.remove('visible');
}
this.updateActiveLink();
});
// Сворачивание/разворачивание
this.toggle?.addEventListener('click', () => {
this.nav.classList.toggle('collapsed');
});
// Обновление активной ссылки при загрузке
this.updateActiveLink();
}
updateActiveLink() {
let currentSection = '';
this.links.forEach(link => {
const section = document.querySelector(link.getAttribute('href'));
if (section) {
const sectionTop = section.offsetTop;
const sectionHeight = section.clientHeight;
if (window.pageYOffset >= sectionTop - 200) {
currentSection = link.getAttribute('href');
}
}
});
this.links.forEach(link => {
link.classList.remove('active');
if (link.getAttribute('href') === currentSection) {
link.classList.add('active');
}
});
}
}
// ============================================
// ЛЕНИВАЯ ЗАГРУЗКА ИЗОБРАЖЕНИЙ
// ============================================
class LazyLoad {
constructor() {
this.images = document.querySelectorAll('img[data-src], img[loading="lazy"]');
this.init();
}
init() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
this.loadImage(entry.target);
}
});
}, {
rootMargin: '50px'
});
this.images.forEach(img => this.observer.observe(img));
} else {
// Fallback для старых браузеров
this.images.forEach(img => this.loadImage(img));
}
}
loadImage(img) {
const src = img.dataset.src || img.src;
if (!src) return;
img.src = src;
img.classList.add('loaded');
if (this.observer) {
this.observer.unobserve(img);
}
}
}
// ============================================
// АНИМАЦИЯ ПРИ ПОЯВЛЕНИИ
// ============================================
class ScrollReveal {
constructor() {
this.elements = document.querySelectorAll('[data-reveal]');
this.init();
}
init() {
if ('IntersectionObserver' in window) {
this.observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('revealed');
this.observer.unobserve(entry.target);
}
});
}, {
threshold: 0.15
});
this.elements.forEach(el => {
el.classList.add('reveal');
this.observer.observe(el);
});
} else {
// Fallback
this.elements.forEach(el => el.classList.add('revealed'));
}
}
}
// CSS для анимации появления (добавить в styles.css)
/*
.reveal {
opacity: 0;
transform: translateY(30px);
transition: all 0.6s ease;
}
.reveal.revealed {
opacity: 1;
transform: translateY(0);
}
*/
// ============================================
// ПАРАЛЛАКС ЭФФЕКТ
// ============================================
class Parallax {
constructor() {
this.elements = document.querySelectorAll('[data-parallax]');
if (this.elements.length > 0) {
this.init();
}
}
init() {
window.addEventListener('scroll', () => {
this.update();
});
this.update();
}
update() {
const scrolled = window.pageYOffset;
this.elements.forEach(element => {
const speed = parseFloat(element.dataset.parallax) || 0.5;
const yPos = -(scrolled * speed);
element.style.transform = `translateY(${yPos}px)`;
});
}
}
// ============================================
// КОПИРОВАНИЕ В БУФЕР ОБМЕНА
// ============================================
class ClipboardCopy {
constructor() {
this.buttons = document.querySelectorAll('[data-clipboard]');
this.init();
}
init() {
this.buttons.forEach(button => {
button.addEventListener('click', () => {
const text = button.dataset.clipboard;
this.copy(text, button);
});
});
}
async copy(text, button) {
try {
await navigator.clipboard.writeText(text);
// Визуальная обратная связь
const originalText = button.innerHTML;
button.innerHTML = '<i class="fas fa-check"></i> Скопировано!';
button.classList.add('copied');
setTimeout(() => {
button.innerHTML = originalText;
button.classList.remove('copied');
}, 2000);
// Уведомление
if (window.toast) {
window.toast.success('Скопировано', 'Текст скопирован в буфер обмена');
}
} catch (err) {
console.error('Ошибка копирования:', err);
if (window.toast) {
window.toast.error('Ошибка', 'Не удалось скопировать текст');
}
}
}
}
// ============================================
// ИНИЦИАЛИЗАЦИЯ ВСЕХ УТИЛИТ
// ============================================
document.addEventListener('DOMContentLoaded', () => {
// Прелоадер
new Preloader();
// Плавная прокрутка
new SmoothScroll();
// Кнопка "Наверх"
new ScrollToTop();
// Индикатор прогресса
new ReadingProgress();
// Быстрая навигация
new QuickNavigation();
// Ленивая загрузка
new LazyLoad();
// Анимация появления
new ScrollReveal();
// Параллакс
new Parallax();
// Копирование в буфер
new ClipboardCopy();
});
// ============================================
// ВСПОМОГАТЕЛЬНЫЕ ФУНКЦИИ
// ============================================
// Дебаунс
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// Троттлинг
function throttle(func, limit) {
let inThrottle;
return function(...args) {
if (!inThrottle) {
func.apply(this, args);
inThrottle = true;
setTimeout(() => inThrottle = false, limit);
}
};
}
// Проверка видимости элемента
function isElementInViewport(el) {
const rect = el.getBoundingClientRect();
return (
rect.top >= 0 &&
rect.left >= 0 &&
rect.bottom <= (window.innerHeight || document.documentElement.clientHeight) &&
rect.right <= (window.innerWidth || document.documentElement.clientWidth)
);
}
// Получение параметров URL
function getUrlParams() {
const params = {};
const queryString = window.location.search.substring(1);
const pairs = queryString.split('&');
pairs.forEach(pair => {
const [key, value] = pair.split('=');
if (key) {
params[decodeURIComponent(key)] = decodeURIComponent(value || '');
}
});
return params;
}
// Форматирование числа
function formatNumber(num) {
return num.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ' ');
}
// Форматирование телефона
function formatPhone(phone) {
const cleaned = phone.replace(/\D/g, '');
const match = cleaned.match(/^(\d{1})(\d{3})(\d{3})(\d{2})(\d{2})$/);
if (match) {
return `+${match[1]} (${match[2]}) ${match[3]}-${match[4]}-${match[5]}`;
}
return phone;
}
// Валидация email
function isValidEmail(email) {
const re = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return re.test(email);
}
// Валидация телефона
function isValidPhone(phone) {
const cleaned = phone.replace(/\D/g, '');
return cleaned.length === 11;
}
// Получение cookie
function getCookie(name) {
const value = `; ${document.cookie}`;
const parts = value.split(`; ${name}=`);
if (parts.length === 2) return parts.pop().split(';').shift();
}
// Установка cookie
function setCookie(name, value, days = 365) {
const date = new Date();
date.setTime(date.getTime() + (days * 24 * 60 * 60 * 1000));
const expires = `expires=${date.toUTCString()}`;
document.cookie = `${name}=${value};${expires};path=/`;
}
// Удаление cookie
function deleteCookie(name) {
document.cookie = `${name}=;expires=Thu, 01 Jan 1970 00:00:00 UTC;path=/;`;
}
// Генерация случайного ID
function generateId(length = 8) {
return Math.random().toString(36).substring(2, length + 2);
}
// Задержка (Promise)
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// Экспорт функций для глобального использования
window.utils = {
debounce,
throttle,
isElementInViewport,
getUrlParams,
formatNumber,
formatPhone,
isValidEmail,
isValidPhone,
getCookie,
setCookie,
deleteCookie,
generateId,
delay
};




