I’m experiencing an issue with my JavaScript script that is supposed to manage video playback on my platform. Currently, the script works correctly for video elements already present on the page, but it does not account for new video elements that are dynamically loaded. None of the scripts work for these new elements.
Dynamic Loading: Videos are added to the page via AJAX when a user scrolls down.
Using IntersectionObserver: I’m attempting to start or stop video playback based on their visibility using the IntersectionObserver.
Problems Encountered
No Playback for New Elements: When new video elements are loaded, the script does not work for these.
Complete Script Non-Functional: None of the script seems to function for these new elements.
What can I do to ensure that the script also works for newly loaded video elements?
Are there best practices I should follow to correctly observe these new elements?
My JS:
document.addEventListener('DOMContentLoaded', () => {
let currentVideo = null;
const pulseContainer = document.getElementById('pulse-container');
let currentPage = 1;
let loading = false;
let visibleVideoCount = 0;
function handleVideoPlayback(entries) {
entries.forEach(entry => {
const video = entry.target;
if (entry.isIntersecting) {
if (currentVideo !== video) {
video.play();
video.loop = true;
if (currentVideo) {
currentVideo.pause();
}
currentVideo = video;
}
visibleVideoCount++;
console.log(visibleVideoCount);
} else {
if (currentVideo === video) {
currentVideo = null;
}
video.pause();
}
});
// Vérifiez si le compteur de vidéos visibles atteint 5
if (visibleVideoCount >= 5) {
console.log('loadmore please');
loadMoreContent(); // Charge plus de contenu
visibleVideoCount = 0; // Réinitialise le compteur
}
}
const observer = new IntersectionObserver(handleVideoPlayback, {
root: null,
rootMargin: '0px',
threshold: 0.5
});
const videos = document.querySelectorAll('.pulse-video');
videos.forEach(video => {
observer.observe(video);
video.addEventListener('error', (e) => {
console.error('Erreur de chargement de la vidéo:', e);
});
video.src = video.getAttribute('data-src');
video.load();
});
function toggleGlobalSound() {
const newMutedState = !Array.from(videos).some(video => video.muted);
videos.forEach(video => {
video.muted = newMutedState;
});
const globalSoundButtons = document.querySelectorAll('#global-sound-toggle i');
globalSoundButtons.forEach(icon => {
icon.classList.toggle('fa-volume-xmark', newMutedState);
icon.classList.toggle('fa-volume-high', !newMutedState);
});
}
const globalSoundButtons = document.querySelectorAll('#global-sound-toggle');
globalSoundButtons.forEach(button => {
button.addEventListener('click', toggleGlobalSound);
});
function setupVideoClickHandler() {
const videos = document.querySelectorAll('.pulse-video'); // Récupère les vidéos à chaque appel
videos.forEach(video => {
video.addEventListener('click', () => {
video.paused ? video.play() : video.pause();
if (currentVideo && currentVideo !== video) {
currentVideo.pause();
}
currentVideo = video;
});
});
}
function handleVisibilityChange() {
if (document.hidden && currentVideo) {
currentVideo.pause();
} else if (!document.hidden && currentVideo) {
currentVideo.play();
}
}
const artistContents = document.querySelectorAll('.artist-content');
artistContents.forEach(content => {
const toggleButton = content.querySelector('.toggle-description');
if (toggleButton) {
toggleButton.addEventListener('click', () => {
content.classList.toggle('open');
});
}
});
const allArtistElements = document.querySelectorAll('.all_artist'); // Pour plusieurs artistes
allArtistElements.forEach(function(artistContent) {
const toggleButton = artistContent.querySelector('.toggle-description');
const descriptionContent = artistContent.querySelector('.description-content');
// Fonction pour ajuster la hauteur de .all_artist.open
function adjustHeight() {
const descriptionHeight = descriptionContent.scrollHeight; // Hauteur réelle du contenu
const additionalHeight = 0; // Hauteur supplémentaire pour que ça monte plus
artistContent.style.height = `${descriptionHeight + additionalHeight}px`; // Ajuste la hauteur en fonction du contenu
artistContent.style.transform = `translateY(-${descriptionHeight + additionalHeight}px)`;
}
// Fonction pour ouvrir et fermer le contenu
function toggleArtistContent() {
artistContent.classList.toggle('open');
if (artistContent.classList.contains('open')) {
descriptionContent.style.transform = 'translateY(0)'; // Annule le translateY
descriptionContent.style.opacity = '1'; // Affiche le contenu
descriptionContent.style.visibility = 'visible'; // Rend le contenu visible
adjustHeight(); // Ajuste la hauteur de .all_artist.open
} else {
descriptionContent.style.transform = 'translateY(-50px)'; // Cache l'élément
descriptionContent.style.opacity = '0'; // Rend le contenu invisible
descriptionContent.style.visibility = 'hidden'; // Cache le contenu
artistContent.style.height = 'auto'; // Réinitialise la hauteur
artistContent.style.transform = `translateY(0)`; // Réinitialise la position
}
}
// Écouteur d'événement sur le bouton pour basculer l'affichage
toggleButton.addEventListener('click', toggleArtistContent);
});
document.addEventListener('visibilitychange', handleVisibilityChange);
function adjustVideoSize() {
videos.forEach(video => {
video.style.width = '100%';
video.style.height = '100%';
video.style.objectFit = 'cover';
});
}
function preloadVideos() {
const videos = document.querySelectorAll('.pulse-video'); // Récupère les vidéos à chaque appel
videos.forEach(video => {
const rect = video.getBoundingClientRect();
if (rect.top < window.innerHeight && rect.bottom > 0) {
if (video.src === '') {
video.src = video.getAttribute('data-src');
video.load();
}
}
observer.observe(video); // Assure-toi que chaque vidéo est observée
});
}
const loadMoreUrl = '/pulses/load-more-pulses/';
function loadMoreContent() {
console.log('Trying to load more content...');
if (loading) return;
loading = true;
const url = `/pulses/load-more-pulses/?page=${currentPage}`;
fetch(url)
.then(response => {
console.log('Response status:', response.status);
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then(data => {
console.log('Data received:', data);
if (data.pulse_data) {
data.pulse_data.forEach(pulse => {
pulseContainer.insertAdjacentHTML('beforeend', pulse.html);
});
currentPage++;
initializeNewElements()
}
})
.catch(error => {
console.error('Error loading more content:', error);
})
.finally(() => {
loading = false;
});
}
// Appelle les fonctions au chargement initial
preloadVideos();
function setupInteractionButtons() {
const likeButtons = document.querySelectorAll('.like-button');
const shareButtons = document.querySelectorAll('.share-button');
likeButtons.forEach(button => {
button.addEventListener('click', () => {
const pulseId = button.dataset.pulseId;
fetch('{% url "pulses:toggle_like" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
'X-CSRFToken': getCookie('csrftoken')
},
body: new URLSearchParams({ 'pulse_id': pulseId })
})
.then(response => response.json())
.then(data => {
if (data.error) {
alert(data.error);
} else {
button.querySelector('i').classList.toggle('fa-solid', data.liked);
button.querySelector('i').classList.toggle('fa-regular', !data.liked);
button.querySelector('i').style.color = data.liked ? '#d20000' : 'floralwhite';
button.querySelector('.like-count').textContent = data.like_count;
}
})
.catch(error => console.error('Error:', error));
});
});
shareButtons.forEach(button => {
button.addEventListener('click', () => {
const pulseId = button.dataset.pulseId;
fetch('{% url "pulses:share_pulse" %}', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken')
},
body: JSON.stringify({ pulse_id: pulseId })
})
.then(response => response.json())
.then(data => {
if (data.success) {
document.querySelector(`.share-count[data-pulse-id="${pulseId}"]`).textContent = data.share_count;
navigator.clipboard.writeText(data.share_url)
.then(() => {
alert('Lien copié!');
})
.catch(err => {
console.error('Erreur lors de la copie du lien:', err);
});
}
})
.catch(error => console.error('Error:', error));
});
});
}
document.querySelectorAll('.toggle-comments').forEach(button => {
button.addEventListener('click', () => {
const commentsSection = button.closest('.pulse_item').querySelector('.comments-section');
commentsSection.style.display = commentsSection.style.display === 'none' || commentsSection.style.display === '' ? 'flex' : 'none';
});
});
setupVideoClickHandler();
setupInteractionButtons();
adjustVideoSize();
preloadVideos();
});
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let cookie of cookies) {
cookie = cookie.trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
function loadScript(scriptUrl) {
const script = document.createElement('script');
script.src = scriptUrl;
script.type = 'text/javascript';
document.body.appendChild(script);
}
function initializeNewElements() {
const videos = document.querySelectorAll('.pulse-video');
if (!videos.length) return; // Vérification de la présence de vidéos
videos.forEach(video => {
observer.observe(video);
});
setupInteractionButtons(); // Ajouter les interactions aux nouveaux boutons
}
My global HTML:
<div class="all_pulse">
<div class="pulse_title">Pulse</div>
<div class="pulse_contains" id="pulse-container">
{% for pulse in pulses %}
{% include 'pages/partial-pulse.html' with pulse=pulse %}
{% endfor %}
</div>
<div class="bar">
<div class="bar-item">
<a href="{% url 'content:index' %}" style="color:white;"><i class="fa-solid fa-house"></i></a>
<a href="{% url 'beats:new_explore' %}" style="color:white;"><i class="fa-solid fa-magnifying-glass"></i></a>
<a href="{% url 'pulses:pulse' %}" style="color:white;"><i class="fa-solid fa-compact-disc fa-lg" style="margin-top:5.5px;"></i></a>
<a href="{% url 'accounts:conversations' %}" style="color:white;"><i class="fa-solid fa-message"></i></a>
<a href="{% url 'accounts:profile' %}" style="color:white;"><i class="fa-solid fa-user"></i></a>
</div>
</div>
</div>
My HTML which is implemented with ajax:
{% load static %}
<script src="{% static 'js/new_pulse.js' %}"></script>
<div class="pulse_item">
<div class="video-wrapper">
<video data-src="{{ pulse.video.url }}" class="pulse-video" src="{{ pulse.video.url }}" muted playsinline></video>
</div>
<div class="comments-section" style="display: none;">
<div class="comment-list">
<h5 class="title_comments">Comments</h5>
{% if pulse.comments.all %}
{% for comment in pulse.comments.all %}
<div class="comment">
<strong>{{ comment.user.username }}:</strong>
<p>{{ comment.text }}</p>
<small style="color:grey;">{{ comment.created_at|date:"d M Y, H:i" }}</small>
</div>
{% endfor %}
{% else %}
<p style="color:white;">Aucun commentaire</p>
{% endif %}
</div>
<textarea class="comment-input" placeholder="Écrivez un commentaire..."></textarea>
<button class="button-up" data-pulse-id="{{ pulse.id }}">Envoyer</button>
</div>
<div class="interacts">
<div class="item-orga">
<button class="like-button" data-pulse-id="{{ pulse.id }}" style="background-color:transparent;color:white;border:none;">
<i class="fa-heart {% if pulse.liked %}fa-solid{% else %}fa-regular{% endif %} fa-xl" style="color: {% if pulse.liked %}#d20000{% else %}floralwhite{% endif %}; margin-right: 4px; box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);"></i>
<span class="like-count">{% if pulse.like_count %}{{ pulse.like_count }}{% else %}0{% endif %}</span>
</button>
<button class="share-button" data-pulse-id="{{ pulse.id }}" style="background-color:transparent;color:white;border:none;">
<i class="fa-solid fa-share fa-xl" style="color:floralwhite;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);"></i>
<span class="share-count" data-pulse-id="{{ pulse.id }}">{% if pulse.share_count %}{{ pulse.share_count }}{% else %}0{% endif %} </span>
</button>
<button class="comments-button" data-pulse-id="{{ pulse.id }}" style="background-color:transparent;color:white;border:none;">
<i class="fa-solid fa-comments fa-xl toggle-comments" style="color:floralwhite;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);"></i>
<span class="comments_count" data-pulse-id="{{ pulse.id }}">{% if pulse.comments %}{{ pulse.comments_count }}{% else %}0{% endif %} </span>
</button>
<button id="global-sound-toggle"><i class="fa-solid fa-volume-xmark" style="color:floralwhite;box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);"></i></button>
</div>
</div>
<div class="data_artist">
<div class="all_artist">
<div class="artist-content">
<div class="profile_picture_pulse">
{% if pulse.user.profile_picture %}
<img src="{{ pulse.user.profile_picture.url }}" class="img-fluid rounded-circle" style="min-width: 50px;max-width: 50px; height: 50px;" alt="Profile Picture">
{% else %}
<img src='/static/images/default_profile.png' class="img-fluid rounded-circle" style="width: 50px; height: 50px;" alt="Profile Picture">
{% endif %}
</div>
<div class="username_pulse">
<strong> <p id="superstrong" style="margin-bottom:0;">{{ pulse.user.username }} </p></strong>
{% if request.user != pulse.user %}
<button id="sub_pulse"
class="follow-toggle-btn btn-style-2 {% if is_followed %}btn-yes{% else %}btn-no{% endif %}"
data-user-id="{{ pulse.user.id }}"
>
{% if is_followed %}Unfollow{% else %}Follow{% endif %}
</button>
<span style="margin-left:5px;" class="follower-count">{{ pulse.follower_count }}</span>
<input type="hidden" name="csrfmiddlewaretoken" value="{{ csrf_token }}">
{% endif %}
<button class="toggle-description" aria-label="Show description">
<i class="fa-solid fa-chevron-down"></i>
</button>
</div>
</div>
<div class="description-content">
<p class="description-text">
{{ pulse.description }}<span class="more-text">{{ pulse.description|slice:"100:" }}</span>
{% for hashtag in pulse.hashtags.all %}
<a href="" alt="{{hashtag.name}}"style="text-decoration:none;">#{{hashtag}} </a>
{% endfor %}
</p>
</div>
</div>
<div class="song-content">
<div class="song_pulse">
<img src="/static/images/explore.webp" alt="Cover" class="square-image-x-fill beat-icon-beat_detail">
</div>
</div>
</div>
</div>`