Modal built with alpinejs only working after page refresh in Firefox

144 Views Asked by At

I have a modal on my page which was built using Alpinejs. When you click the "See how it works" button a modal opens and a video should start playing automatically. You can then close the modal by clicking outside the modal or the "Close video" button.

All this works perfectly fine in Chrome and Safari both locally and in production and it also does so in Firefox when I test it locally - but the video won't start playing when in production in Firefox only - the modal opens and closes, but nothing happens inside it, unless I refresh my page and then the video does start playing.

Is there anything in particular I should to ensure it loads correctly in Firefox without the need of a page refresh?

Here's my code for this specific section within the page:

HTML

<div class="section-how-it-works" x-ignore ax-load x-data="{ modalOpen: false }">
<div x-ignore ax-load x-data="VideoModal">
    <!-- how it works video button -->
    <button id="open-modal-button" class="button button-secondary" aria-label="See how it works" @click="modalOpen = true" aria-controls="modal" >
        <span class="text">See how it works</span>
        <span class="icon">
            <Icon name="video-play" size="16" />
        </span>
    </button>
    <!-- how it works modal backdrop -->
    <div class="modal-backdrop"
            x-show="modalOpen"
            x-transition:enter="transition ease-out duration-200"
            x-transition:enter-start="opacity-0"
            x-transition:enter-end="opacity-100"
            x-transition:leave="transition ease-out duration-100"
            x-transition:leave-start="opacity-100"
            x-transition:leave-end="opacity-0" 
            aria-hidden="true"
            x-cloak
    ></div> 
    <!-- how it works video modal -->
    <div id="modal"
        role="dialog"
        aria-modal="true"
        x-show="modalOpen"
        x-transition:enter="transition ease-out duration-300"
        x-transition:enter-start="opacity-0 scale-75"
        x-transition:enter-end="opacity-100 scale-100"
        x-transition:leave="transition ease-out duration-200"
        x-transition:leave-start="opacity-100 scale-100"
        x-transition:leave-end="opacity-0 scale-75"
        x-cloak
        x-cloak 
    >
        <!-- video -->
        <div class="video" x-data="{ play: true, mute: false, subtitles: false }" x-init="$refs.video.onplaying = () => { play = true }; $refs.video.onended = () => { play = false }; $watch('play', (value) => {
            if (value) {
                    $refs.video.play();
            } else {
                    $refs.video.pause();
            }
    })">
            <figure class="video-inner" @click.outside="modalOpen = false" @keydown.escape.window="modalOpen = false">
                <video id="how-it-works-video" x-ignore ax-load="lazy" playsinline x-init="$watch('modalOpen', value => value ? $el.play() : $el.pause())" x-ref="video" @click="play = !play">
                        <source src={howItWorksVideo} type="video/mp4"/>
                        <track label="English" kind="subtitles" src={subs} srclang="en" x-show="subtitles" x-bind:default="subtitles">
                </video>
                <!-- Video controls -->
                <div id="video-controls" class="controls" data-state="hidden">
                    <div @click="play = !play">
                        <template x-if="!play">
                            <Icon name="vid-play" class="control"/>
                        </template>
                        <template x-if="play">
                            <Icon name="vid-pause" class="control"/>
                        </template>
                    </div>
                    <button class="progress" class="control">
                        <progress id="progress" value="0" min="0">
                            <span id="progress-bar"></span>
                        </progress>
                    </button>
                    <div @click="mute = !mute; $refs.video.muted = mute" class="mute">
                        <template x-if="!mute">
                            <Icon name="vol-on" class="control"/>
                        </template>
                        <template x-if="mute">
                            <Icon name="vol-off" class="control"/>
                        </template>
                    </div>
                    <div @click="subtitles = !subtitles; $refs.video.textTracks[0].mode = subtitles ? 'showing' : 'hidden'" class="subtitles">
                        <div x-show="!subtitles" class="tooltip">
                            <Icon name="subs-off" class="control"/>
                            <span class="tooltiptext">Enable captions</span>
                        </div>
                        <div x-show="subtitles" class="tooltip">
                            <Icon name="subs-on" class="control"/>
                        </div>
                    </div>
                </div>
                <!-- End of video controls -->
            </figure>
        </div>
        <!-- close button -->
        <button id="close-modal-button" class="button button-secondary" @click="modalOpen = false" aria-label="Close video">
            <span class="text">Close video</span>
            <span class="icon">
                <Icon name="close" size="16" />
            </span>
        </button>
    </div>  
</div>
</div>

JS

export default () => {
    
  return {
    init() {

      const heroVideo = document.getElementById('hero-video');
      const openModalButton = document.getElementById('open-modal-button');
            const closeModalButton = document.getElementById('close-modal-button');
      const modal = document.getElementById('modal');
      const header = document.getElementById('header');

      const disableScroll = () => {
        document.body.style.overflow = 'hidden';
        document.addEventListener('touchmove', preventScroll, { passive: false });
      };

            const enableScroll = () => {
        document.body.style.overflow = 'auto';
        document.removeEventListener('touchmove', preventScroll, { passive: false });
      };

      const preventScroll = (event) => {
        event.preventDefault();
      };

            //Open modal
      openModalButton.addEventListener('click', () => {
        heroVideo.pause();
        modal.classList.add('open');
        header.style.zIndex = '30'; 
        disableScroll(); 

                const video = document.getElementById("how-it-works-video");
                const progress = document.getElementById("progress");
                const progressBar = document.getElementById("progress-bar");

                // Ensuring progress bar reflects video duration
                video.addEventListener("loadedmetadata", () => {
                    progress.setAttribute("max", video.duration);
                });

                // Progress bar update to reflect current position in video
                video.addEventListener("timeupdate", () => {
                    if (!progress.getAttribute("max")) {
                        progress.setAttribute("max", video.duration);
                    }
                    progress.value = video.currentTime;
                    progressBar.style.width = `${Math.floor((video.currentTime * 100) / video.duration)}%`;
                });
            
                // Allowing users to interact with progress bar / seek specific point in video
                progress.addEventListener("click", (e) => {
                    const rect = progress.getBoundingClientRect();
                    const pos = (e.pageX - rect.left) / progress.offsetWidth;
                    video.currentTime = pos * video.duration;
                });

      });

            //Close modal through close button
            closeModalButton.addEventListener('click', () => {
        heroVideo.play();
        modal.classList.remove('open'); 
        header.style.zIndex = '100'; 
        enableScroll();
      });

            //Close modal with click utside of modal area
      modal.addEventListener('click', (event) => {
        if (event.target === modal) {
          heroVideo.play();
          modal.classList.remove('open'); 
          header.style.zIndex = '100'; 
          enableScroll(); 
        }
      });

        //Close modal by pressing ESC
      document.addEventListener('keydown', (event) => {
        if (event.key === 'Escape' && modal.classList.contains('open')) {
          heroVideo.play();
          modal.classList.remove('open'); 
          header.style.zIndex = '100'; 
          enableScroll(); 
        }
      });
            
    },
  };
};

Thanks!

1

There are 1 best solutions below

5
Yinci On

You're using x-ignore on 3 different occasions within your code. This prevents Alpine from interacting with this piece of the DOM (as per the docs). If you remove these attributes, the code works as expected.