Anonymous
Как исправить эту проблему с видеоанимацией iOS при динамической настройке currentTime с помощью JS?
Сообщение
Anonymous » 01 янв 2026, 17:33
Я реализую анимацию на основе прокрутки на витрине магазина Shopify, где фоновое видео воспроизводится шаг за шагом (кадр за кадром) по мере прокрутки пользователем. Он отлично работает на большинстве платформ, включая настольные компьютеры (Chrome, Firefox, Safari), телефоны Android, MacBook и iPad, но очень плохо работает на iPhone, особенно в Safari.
Проблема на iPhone:
Первый видеокадр появляется случайно, не синхронизируется с прокруткой.
Прокрутка происходит с задержкой или заикается, а видео зависает. кажется
неотзывчивым.
Иногда видео вообще не загружается должным образом, и для его появления требуется ручное
подталкивание прокрутки.
Детали реализации:
Я использую элемент и вручную обновляю его currentTime
на основе прогресса прокрутки.
После определенного порога прокрутки видео воспроизводится автоматически в
цикле.
Я предварительно загружаю видео, отключаю его и проверяю, что оно использует playInline для совместимости с iOS.
На iOS я «запускаю» видео с тихим воспроизведением/паузой при сенсорном запуске, чтобы
соответствовать автозапуску политики.
Код: Выделить всё
class ScrollVideoController {
constructor() {
this.video = document.querySelector('.animate-on-scroll');
this.sticky = document.querySelector('.scroll-sticky');
this.container = document.querySelector('.scroll-video-animation');
this.progressBar = document.querySelector('.progress-bar');
this.content = document.querySelector('.scroll-video-animation .content');
this.contentHalfHalf = document.querySelector('.scroll-video-animation .content-half-half');
this.header = document.querySelector("#new-fixed-header-styling");
if (!this.video || !this.sticky || !this.container) return;
this.isIOS = /iPhone|iPad|iPod/.test(navigator.userAgent);
this.isMobile = window.innerWidth {
this.isReady = true;
this.video.classList.add('ready');
this.loadingOverlay.classList.add('hidden');
});
this.video.addEventListener('canplay', () => {
this.loadingOverlay.classList.add('hidden');
});
this.video.addEventListener('error', () => {
this.loadingOverlay.classList.add('hidden');
this.errorOverlay.classList.remove('hidden');
});
this.video.addEventListener('ended', () => {
const frameTime = this.loopStartFrame / this.frameRate;
this.video.currentTime = frameTime;
if (this.isAutoplaying) {
this.video.play().catch(() => {});
}
});
if (this.isIOS) {
const primeVideo = async () => {
try {
await this.video.play();
this.video.pause();
this.video.currentTime = 0;
} catch (error) {
console.warn('Video priming failed:', error);
}
window.removeEventListener('touchstart', primeVideo);
};
window.addEventListener('touchstart', primeVideo, {
once: true
});
}
window.addEventListener('resize', () => {
this.isMobile = window.innerWidth = 0.9) {
this.header.style.opacity = "0";
this.header.style.pointerEvents = "none";
} else {
this.header.style.opacity = "1";
this.header.style.pointerEvents = "auto";
}
}
// Content opacity
if (this.content) {
const contentOpacity = Math.max(0, 1 - (scrollData.progress * (1 / 0.45)));
this.content.style.opacity = contentOpacity.toString();
}
// Content-half-half fade in
if (this.contentHalfHalf) {
const halfHalfOpacity = scrollData.progress >= 0.6 ? Math.min(1, (scrollData.progress - 0.6) * (1 / 0.2)) : 0;
this.contentHalfHalf.style.opacity = halfHalfOpacity.toString();
}
if (scrollData.stickyTop > 0) {
this.isAutoplaying = false;
this.video.pause();
this.video.currentTime = 0;
return;
}
if (scrollData.progress < 0.55) {
const frameNumber = Math.floor(scrollData.progress * (this.scrollThreshold / 0.55));
const frameTime = frameNumber / this.frameRate;
if (this.isAutoplaying) {
this.isAutoplaying = false;
this.video.pause();
}
const timeDiff = Math.abs(this.video.currentTime - frameTime);
if (timeDiff > (this.isMobile ? 0.5 : 0.1)) {
this.video.currentTime = frameTime;
}
} else if (!this.isAutoplaying) {
this.isAutoplaying = true;
const startFrame = this.scrollThreshold / this.frameRate;
this.video.currentTime = startFrame;
this.video.play().catch(() => {});
}
if (this.progressBar) {
this.progressBar.style.width = `${scrollData.progress * 100}%`;
const progressBarContainer = this.progressBar.parentElement;
if (progressBarContainer) {
if (scrollData.progress >= 0.98) {
progressBarContainer.style.opacity = '0';
progressBarContainer.style.transition = 'opacity 0.5s ease-out';
} else if (scrollData.progress < 0.95) {
progressBarContainer.style.opacity = '1';
progressBarContainer.style.transition = 'opacity 0.3s ease-in';
}
}
}
}
animate() {
const scrollData = this.getScrollProgress();
this.updateVideo(scrollData);
this.rafId = requestAnimationFrame(() => this.animate());
}
}
Код: Выделить всё
//ONLY TEXT CONTENT HERE
//ONLY TEXT CONTENT HERE
//ONLY TEXT CONTENT HERE
Подробнее здесь:
https://stackoverflow.com/questions/795 ... amically-w
1767277995
Anonymous
Я реализую анимацию на основе прокрутки на витрине магазина Shopify, где фоновое видео воспроизводится шаг за шагом (кадр за кадром) по мере прокрутки пользователем. Он отлично работает на большинстве платформ, включая настольные компьютеры (Chrome, Firefox, Safari), телефоны Android, MacBook и iPad, но очень плохо работает на iPhone, особенно в Safari. [b]Проблема на iPhone:[/b] [list] [*]Первый видеокадр появляется случайно, не синхронизируется с прокруткой. [*]Прокрутка происходит с задержкой или заикается, а видео зависает. кажется неотзывчивым. [*]Иногда видео вообще не загружается должным образом, и для его появления требуется ручное подталкивание прокрутки. [/list] [b]Детали реализации:[/b] [list] [*]Я использую элемент и вручную обновляю его currentTime на основе прогресса прокрутки. [*]После определенного порога прокрутки видео воспроизводится автоматически в цикле. [*]Я предварительно загружаю видео, отключаю его и проверяю, что оно использует playInline для совместимости с iOS. [*]На iOS я «запускаю» видео с тихим воспроизведением/паузой при сенсорном запуске, чтобы соответствовать автозапуску политики. [/list] [code]class ScrollVideoController { constructor() { this.video = document.querySelector('.animate-on-scroll'); this.sticky = document.querySelector('.scroll-sticky'); this.container = document.querySelector('.scroll-video-animation'); this.progressBar = document.querySelector('.progress-bar'); this.content = document.querySelector('.scroll-video-animation .content'); this.contentHalfHalf = document.querySelector('.scroll-video-animation .content-half-half'); this.header = document.querySelector("#new-fixed-header-styling"); if (!this.video || !this.sticky || !this.container) return; this.isIOS = /iPhone|iPad|iPod/.test(navigator.userAgent); this.isMobile = window.innerWidth { this.isReady = true; this.video.classList.add('ready'); this.loadingOverlay.classList.add('hidden'); }); this.video.addEventListener('canplay', () => { this.loadingOverlay.classList.add('hidden'); }); this.video.addEventListener('error', () => { this.loadingOverlay.classList.add('hidden'); this.errorOverlay.classList.remove('hidden'); }); this.video.addEventListener('ended', () => { const frameTime = this.loopStartFrame / this.frameRate; this.video.currentTime = frameTime; if (this.isAutoplaying) { this.video.play().catch(() => {}); } }); if (this.isIOS) { const primeVideo = async () => { try { await this.video.play(); this.video.pause(); this.video.currentTime = 0; } catch (error) { console.warn('Video priming failed:', error); } window.removeEventListener('touchstart', primeVideo); }; window.addEventListener('touchstart', primeVideo, { once: true }); } window.addEventListener('resize', () => { this.isMobile = window.innerWidth = 0.9) { this.header.style.opacity = "0"; this.header.style.pointerEvents = "none"; } else { this.header.style.opacity = "1"; this.header.style.pointerEvents = "auto"; } } // Content opacity if (this.content) { const contentOpacity = Math.max(0, 1 - (scrollData.progress * (1 / 0.45))); this.content.style.opacity = contentOpacity.toString(); } // Content-half-half fade in if (this.contentHalfHalf) { const halfHalfOpacity = scrollData.progress >= 0.6 ? Math.min(1, (scrollData.progress - 0.6) * (1 / 0.2)) : 0; this.contentHalfHalf.style.opacity = halfHalfOpacity.toString(); } if (scrollData.stickyTop > 0) { this.isAutoplaying = false; this.video.pause(); this.video.currentTime = 0; return; } if (scrollData.progress < 0.55) { const frameNumber = Math.floor(scrollData.progress * (this.scrollThreshold / 0.55)); const frameTime = frameNumber / this.frameRate; if (this.isAutoplaying) { this.isAutoplaying = false; this.video.pause(); } const timeDiff = Math.abs(this.video.currentTime - frameTime); if (timeDiff > (this.isMobile ? 0.5 : 0.1)) { this.video.currentTime = frameTime; } } else if (!this.isAutoplaying) { this.isAutoplaying = true; const startFrame = this.scrollThreshold / this.frameRate; this.video.currentTime = startFrame; this.video.play().catch(() => {}); } if (this.progressBar) { this.progressBar.style.width = `${scrollData.progress * 100}%`; const progressBarContainer = this.progressBar.parentElement; if (progressBarContainer) { if (scrollData.progress >= 0.98) { progressBarContainer.style.opacity = '0'; progressBarContainer.style.transition = 'opacity 0.5s ease-out'; } else if (scrollData.progress < 0.95) { progressBarContainer.style.opacity = '1'; progressBarContainer.style.transition = 'opacity 0.3s ease-in'; } } } } animate() { const scrollData = this.getScrollProgress(); this.updateVideo(scrollData); this.rafId = requestAnimationFrame(() => this.animate()); } }[/code] [code] //ONLY TEXT CONTENT HERE //ONLY TEXT CONTENT HERE //ONLY TEXT CONTENT HERE [/code] Подробнее здесь: [url]https://stackoverflow.com/questions/79571884/how-to-fix-this-ios-video-animation-issue-when-setting-currenttime-dynamically-w[/url]