Элегантный трехкнопочный проигрыватель аудио-анимации HTML и JavaScript

Элегантный трехкнопочный проигрыватель аудио-анимации HTML и JavaScript

1 марта 2023 г.

В этой статье описывается код HTML и JavaScript, используемый для создания адаптивного адаптивного дизайна анимации и аудиоплеера для Интернета. Весь код и содержимое доступны на Репозиторий GitHub, и есть демонстрационная веб-страница в качестве хорошо.

В демонстрационном коде используются некоторые аспекты современного дизайна элементов HTML5, CSS3 и JavaScript.

Демо HTML

Демонстрация представляет собой последовательный документ с содержимым, в котором за одной «страницей» или панелью в документе следует другая, каждая по очереди. Документ может быть руководством, иллюстрированным романом или комиксом в качестве примера содержания.

По документу можно перемещаться с помощью клавиши TAB, а также с помощью мыши, при этом текущая панель будет выделена.

Весь HTML-код для демонстрационного сайта занимает всего 460 строк, что не является особенно длинным фрагментом, и вам может быть полезно просмотреть весь список в репозитории. Для нашего описания здесь мы рассмотрим только некоторые выдержки из кода вместе с соответствующими отображениями веб-страницы.

Здесь у нас есть дисплей первой панели из нашей демонстрации. Обратите внимание, что он масштабируется так, что заполняет весь экран, сохраняя соотношение сторон.

The demo first-page display

Непосредственно под панелью изображений у нас есть панель кнопок и текстовая панель, которая немного рассказывает нам о странице. Текстовая панель обычно содержит текст документа вместо описательного текста о содержимом.

The demo first-page caption and play button

Наконец, если мы нажмем на панель кнопок или само изображение, нам будет представлена ​​эта анимация на странице, как показано здесь далее.

The demo's first-panel animation

Ниже показан HTML-код этих страниц. Как видите, мы использовали возможности элемента в коде страницы.

Браузер проверяет каждый из предложенных списков изображений и выбирает первое изображение в списке элементов , которое он считает подходящим.

Вы также можете увидеть код, представляющий кнопки Воспроизведение и Обновить SVG-изображения, и код, отображающий альтернативную текстовую панель и альтернативное изображение.

Важно отметить, что браузер не загружает ни одно из перечисленных изображений, чтобы определить, какое использовать; вместо этого он делает свой выбор на основе значений списка элементов и загружает только это изображение.

<!-- ++++++++++++++++++++ -->
<span class="clickMeOverlay" id="c1" title="Click to show alternate content">
<picture class="playGIF src" id="pic1">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-s-0921.avif" media = "(max-width: 576px)" type = "image/avif">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-m-1228.avif" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-l-1587.avif" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-x-1920.avif" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-X-2240.avif" media = "(min-width: 1201px)" type = "image/avif">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-s-0921.webp" media = "(max-width: 576px)" type = "image/webp">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-m-1228.webp" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-l-1587.webp" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-x-1920.webp" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-X-2240.webp" media = "(min-width: 1201px)" type = "image/webp">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-s-0921.jpg" media = "(max-width: 576px)" type = "image/jpg">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-m-1228.jpg" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-l-1587.jpg" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-x-1920.jpg" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">
    <source srcset = "./AVPlayer/01_trilobe_flux_propulsion_unit-X-2240.jpg" media = "(min-width: 1201px)" type = "image/jpg">
    <img tabindex="0" id="s1" class="playGIF src" src = "./AVPlayer/01_trilobe_flux_propulsion_unit-X-2240.jpg" alt = "This image is 01_trilobe_flux_propulsion_unit." style="display: block;">
</picture>
<picture class="playGIF alt" id="apic1">
    <source srcset = "./AVPlayer/altImgs/01_trilobe_flux_propulsion_unit-w593-h374.avif" media = "(min-width: 320px)" type = "image/avif">
    <source srcset = "./AVPlayer/altImgs/01_trilobe_flux_propulsion_unit-w593-h374.webp" media = "(min-width: 320px)" type = "image/webp">
    <source srcset = "./AVPlayer/altImgs/01_trilobe_flux_propulsion_unit-w593-h374.gif" media = "(min-width: 320px)" media = "(min-width: 320px)" type = "image/gif">
    <img tabindex="0" id="a1" class="playGIF alt" src = "./AVPlayer/altImgs/01_trilobe_flux_propulsion_unit-w593-h374.gif" alt = "The shuttlecraft lands on the asteroid." style="display: none;">
</picture>
</span>
<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>
<div class="row">
    <div id="b1" tabindex="0" title="show alternate content" class="playButton clickMeOverlay card col-12 d-flex flex-column px-sm-0 align-items-center">
    <svg id="p1" title="Play Button" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-play" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" d="M10.804 8L5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z"/>
    </svg>
    <svg id="r1" title="Reload Button" width="7vw" height="7vw"  viewBox="0 -1 16 16" class="bi bi-arrow-counterclockwise" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" d="M12.83 6.706a5 5 0 0 0-7.103-3.16.5.5 0 1 1-.454-.892A6 6 0 1 1 2.545 5.5a.5.5 0 1 1 .91.417 5 5 0 1 0 9.375.789z"/>
    <path fill-rule="evenodd" d="M7.854.146a.5.5 0 0 0-.708 0l-2.5 2.5a.5.5 0 0 0 0 .708l2.5 2.5a.5.5 0 1 0 .708-.708L5.707 3 7.854.854a.5.5 0 0 0 0-.708z"/>
    </svg>
</div></div>
<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>
<div class="xmplc card col-12 d-flex shadow-md px-sm-0">
<div tabindex="0" class="card-body" id="caption1" style="display: block;"><p>This panel shows the trilobe quantum flux concentrator propulsion unit.<br><br> For our demo, this panel uses and displays just one of the AVPlayer buttons, and that PLAY button simply launches an animation with no audio. The alternate content can also be displayed by clicking the image itself.</p>
</div>
<div tabindex="0" class="card-body xmpla" id="altcap1" style="display: none;"><p>This panel shows an animation of a shuttlecraft using the trilobe quantum flux concentrator propulsion unit.<br><br>On this panel, the RELOAD button just unloads the animation and returns to the original page. The original image can also be reloaded by clicking this image.</p>
</div>
</div>
<div class="card col-12 px-sm-0" style="opacity: 0;"><br><br></div>
<!-- ++++++++++++++++++++ -->

Так действительно, какой из них выбирает браузер? Если к URL-адресу страницы, используемому для запуска демонстрационного сайта, добавлена ​​строка запроса ?info, это вызовет добавление к нашей странице, отображаемой в виде информационной аннотации к каждому изображению.

В этом примере показаны результаты двух страниц с изображениями, которые мы видели выше.

The first-panel info addition

The animation panel info display addition

Конечно, мы могли бы также посмотреть на экран отладчика браузера, который в данном случае показан здесь для начального отображения панели.

Debug info for first-panel image display

И то же самое для второй анимированной панели.

Debug info for the animation panel

Мы видим, что первоначальные закодированные значения, которые у нас были для элемента в каждом элементе , были изменены браузером, чтобы отразить его выбор из каждого элемента . Список элемента >.

Здесь, в моем настольном браузере Chrome, он решил использовать изображения типа файла AVIF как для исходной панели неподвижных изображений, так и для панели анимации.

В случае с неподвижным изображением на первой панели можно было выбрать один из трех типов файлов изображений: AVIF, WEBP и JPG и т. д. , существует 5 размеров размеров для каждого типа файла.

Для наших демонстрационных анимационных панелей у нас есть только один размер из трех типов файлов: AVIF, WEBP и GIF. Как оказалось, на сегодняшний день (24.02.23) EdgeBrowser не поддерживает изображения AVIF, но с помощью и элементы , он просто проигнорирует эти элементы списка.

Мне нравится такое поведение. Хуже себя ведут iPhone и Firefox. iPhone выберет использование файла AVIF из списка , но не отобразит его, а только замещающий текст. Firefox говорит, что поддерживает изображения AVIF и поддерживает неподвижные изображения. Он не показывает анимированные анимации AVIF.

Где-то я читал, что нужно использовать «обнаружение признаков». Я расширил свое определение «функции», включив в нее платформу или браузер, поэтому я решил использовать простую переадресацию для отправки пользователей iPhone и Firefox на альтернативный URL-адрес страницы без содержимого AVIF.

Чтобы совершить грязное дело, я поместил этот код JavaScript в раздел своего HTML-кода.

// safari mobile does not display AVIF animation files
window.addEventListener("load", () => {
    //console.info("index "+navigator.userAgent.indexOf("Edg"));
    console.info("user agent "+navigator.userAgent);
  if ((navigator.userAgent.indexOf("iPhone") != -1 ) || (navigator.userAgent.indexOf("Firefox") != -1 )) {
      console.info("its an iPhone or Firefox");
              window.location.replace("https://syntheticreality.net/Comics/AVPlayers.html");
  } else {
    console.info("not an iPhone or Firefox");
  }
  });
</script>

Прежде чем мы рассмотрим основную программу JavaScript reader.js, давайте посмотрим на вторую панель в нашей демонстрации, которая использует все три кнопки проигрывателя. Это та панель изображений, которая снова масштабируется по размеру экрана.

Demo panel two

Под этой панелью расположены кнопки проигрывателя и текстовая панель.

Panel two Player buttons and text panel

По умолчанию звук проигрывателя отключен; то есть звук отключен, как показано здесь. Если появляется красный значок «звук отключен», это также означает, что звук действительно есть. Чтобы услышать это, нам нужно щелкнуть значок звука, чтобы включить звук. Громкость будет вашей системной настройкой громкости; держит это просто.

Вторая кнопка с надписью CC позволяет пользователю отключить или включить отображение расшифровки текста субтитров для аудиоконтента.

Audio and closed captions enabled

Теперь нажатие кнопки Воспроизвести или самого изображения запустит анимацию со звуком, а альтернативная кнопка Обновить заменит кнопку Воспроизвести. кнопка, как показано здесь далее. Этот пример также включает отображение текста расшифровки, которое отображается в результате включения CC.

Second-panel animation info

Это HTML-код, используемый для отображения этой панели и ее анимации, и мы легко можем заметить его сходство с кодом для первой панели.

<span class="clickMeOverlay" id="c2" title="Click to show alternate content">
<picture class="playGIF playMP3 src" id="pic2">
    <source srcset = "./AVPlayer/02_oh_no-s-0921.avif" media = "(max-width: 576px)" type = "image/avif">
    <source srcset = "./AVPlayer/02_oh_no-m-1228.avif" media = "((min-width: 577px) and (max-width: 768px))" type = "image/avif">
    <source srcset = "./AVPlayer/02_oh_no-l-1587.avif" media = "((min-width: 769px) and (max-width: 992px))" type = "image/avif">
    <source srcset = "./AVPlayer/02_oh_no-x-1920.avif" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/avif">
    <source srcset = "./AVPlayer/02_oh_no-X-2240.avif" media = "(min-width: 1201px)" type = "image/avif">
    <source srcset = "./AVPlayer/02_oh_no-s-0921.webp" media = "(max-width: 576px)" type = "image/webp">
    <source srcset = "./AVPlayer/02_oh_no-m-1228.webp" media = "((min-width: 577px) and (max-width: 768px))" type = "image/webp">
    <source srcset = "./AVPlayer/02_oh_no-l-1587.webp" media = "((min-width: 769px) and (max-width: 992px))" type = "image/webp">
    <source srcset = "./AVPlayer/02_oh_no-x-1920.webp" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/webp">
    <source srcset = "./AVPlayer/02_oh_no-X-2240.webp" media = "(min-width: 1201px)" type = "image/webp">
    <source srcset = "./AVPlayer/02_oh_no-s-0921.jpg" media = "(max-width: 576px)" type = "image/jpg">
    <source srcset = "./AVPlayer/02_oh_no-m-1228.jpg" media = "((min-width: 577px) and (max-width: 768px))" type = "image/jpg">
    <source srcset = "./AVPlayer/02_oh_no-l-1587.jpg" media = "((min-width: 769px) and (max-width: 992px))" type = "image/jpg">
    <source srcset = "./AVPlayer/02_oh_no-x-1920.jpg" media = "((min-width: 993px) and (max-width: 1200px))" type = "image/jpg">
    <source srcset = "./AVPlayer/02_oh_no-X-2240.jpg" media = "(min-width: 1201px)" type = "image/jpg">
    <img tabindex="0" id="s2" class="playGIF src" src = "./AVPlayer/02_oh_no-X-2240.jpg" alt = "This image is 02_oh_no." style="display: block;">
</picture>
<picture class="playGIF playMP3 alt" id="apic2">
    <source srcset = "./AVPlayer/altImgs/02_oh_no-w513-h720.avif" media = "(min-width: 320px)" type = "image/avif">
    <source srcset = "./AVPlayer/altImgs/02_oh_no-w513-h720.webp" media = "(min-width: 320px)" type = "image/webp">
    <source srcset = "./AVPlayer/altImgs/02_oh_no-w513-h720.gif" media = "(min-width: 320px)" media = "(min-width: 320px)" type = "image/gif">
    <img tabindex="0" id="a2" class="playGIF alt" src = "./AVPlayer/altImgs/02_oh_no-w513-h720.gif" alt = "The shuttlecraft lands on the asteroid." style="display: none;">
</picture>
</span>
<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>
    <div class="row">
    <div tabindex="0" title="toggle audio mute" class="mute-audio card col-2 d-flex flex-column px-sm-0 MP3Overlay align-items-center">
    <svg title="mute-audio" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-volume-mute" fill="white" stroke="red" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
      <path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04zm7.854.606a.5.5 0 0 1 0 .708l-4 4a.5.5 0 0 1-.708-.708l4-4a.5.5 0 0 1 .708 0z"/>
      <path fill-rule="evenodd" d="M9.146 5.646a.5.5 0 0 0 0 .708l4 4a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0z"/>
    </svg>
    <svg title="enable-audio" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-volume-up" fill="white" stroke="green" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
      <path fill-rule="evenodd" d="M6.717 3.55A.5.5 0 0 1 7 4v8a.5.5 0 0 1-.812.39L3.825 10.5H1.5A.5.5 0 0 1 1 10V6a.5.5 0 0 1 .5-.5h2.325l2.363-1.89a.5.5 0 0 1 .529-.06zM6 5.04L4.312 6.39A.5.5 0 0 1 4 6.5H2v3h2a.5.5 0 0 1 .312.11L6 10.96V5.04z"/>
      <path d="M11.536 14.01A8.473 8.473 0 0 0 14.026 8a8.473 8.473 0 0 0-2.49-6.01l-.708.707A7.476 7.476 0 0 1 13.025 8c0 2.071-.84 3.946-2.197 5.303l.708.707z"/>
      <path d="M10.121 12.596A6.48 6.48 0 0 0 12.025 8a6.48 6.48 0 0 0-1.904-4.596l-.707.707A5.483 5.483 0 0 1 11.025 8a5.483 5.483 0 0 1-1.61 3.89l.706.706z"/>
      <path d="M8.707 11.182A4.486 4.486 0 0 0 10.025 8a4.486 4.486 0 0 0-1.318-3.182L8 5.525A3.489 3.489 0 0 1 9.025 8 3.49 3.49 0 0 1 8 10.475l.707.707z"/>
    </svg></div>
    <div tabindex="0" title="toggle transcript display" class="card col-2 d-flex flex-column px-sm-0 transcriptControl align-items-center">
    <svg style="padding-top: 1vw;" title="no-cc" xmlns="http://www.w3.org/2000/svg" width="7vw" height="7vw" stroke="red" stroke-width=".5" fill="white" class="bi bi-x-box" viewBox="0 0 16 16">
    <path d="M5.18 4.616a.5.5 0 0 1 .704.064L8 7.219l2.116-2.54a.5.5 0 1 1 .768.641L8.651 8l2.233 2.68a.5.5 0 0 1-.768.64L8 8.781l-2.116 2.54a.5.5 0 0 1-.768-.641L7.349 8 5.116 5.32a.5.5 0 0 1 .064-.704z"/>
    <path d="M4 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h8a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H4zm0 1h8a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1z"/>
    </svg>
    <svg title="show-cc" xmlns="http://www.w3.org/2000/svg" width="8vw" height="8vw"  stroke="blue" stroke-width=".5" fill="white" class="bi bi-badge-cc" viewBox="0 0 16 16">
      <path d="M3.708 7.755c0-1.111.488-1.753 1.319-1.753.681 0 1.138.47 1.186 1.107H7.36V7c-.052-1.186-1.024-2-2.342-2C3.414 5 2.5 6.05 2.5 7.751v.747c0 1.7.905 2.73 2.518 2.73 1.314 0 2.285-.792 2.342-1.939v-.114H6.213c-.048.615-.496 1.05-1.186 1.05-.84 0-1.319-.62-1.319-1.727v-.743zm6.14 0c0-1.111.488-1.753 1.318-1.753.682 0 1.139.47 1.187 1.107H13.5V7c-.053-1.186-1.024-2-2.342-2C9.554 5 8.64 6.05 8.64 7.751v.747c0 1.7.905 2.73 2.518 2.73 1.314 0 2.285-.792 2.342-1.939v-.114h-1.147c-.048.615-.497 1.05-1.187 1.05-.839 0-1.318-.62-1.318-1.727v-.743z"/>
      <path d="M14 3a1 1 0 0 1 1 1v8a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1h12zM2 2a2 2 0 0 0-2 2v8a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H2z"/>
    </svg></div>

    <div id="b2" tabindex="0" title="show alternate content" class="playButton clickMeOverlay card col-8 d-flex flex-column px-sm-0 align-items-center">
    <svg id="p2" title="Play Button" width="8vw" height="8vw" viewBox="0 0 16 16" class="bi bi-play" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" d="M10.804 8L5 4.633v6.734L10.804 8zm.792-.696a.802.802 0 0 1 0 1.392l-6.363 3.692C4.713 12.69 4 12.345 4 11.692V4.308c0-.653.713-.998 1.233-.696l6.363 3.692z"/>
    </svg>
    <svg id="r2" title="Reload Button" width="7vw" height="7vw"  viewBox="0 -1 16 16" class="bi bi-arrow-counterclockwise" fill="white" stroke="blue" stroke-width=".5" xmlns="http://www.w3.org/2000/svg">
    <path fill-rule="evenodd" d="M12.83 6.706a5 5 0 0 0-7.103-3.16.5.5 0 1 1-.454-.892A6 6 0 1 1 2.545 5.5a.5.5 0 1 1 .91.417 5 5 0 1 0 9.375.789z"/>
    <path fill-rule="evenodd" d="M7.854.146a.5.5 0 0 0-.708 0l-2.5 2.5a.5.5 0 0 0 0 .708l2.5 2.5a.5.5 0 1 0 .708-.708L5.707 3 7.854.854a.5.5 0 0 0 0-.708z"/>
    </svg></div>
    </div>
<div class="card col-12 px-sm-0" style="opacity: 0;"><br></div>
<div id="t2" class="col-12 px-sm-0 transcript card"><p>Audio Transcript: Telephone Intercept Tones and woman saying 'oh no'.</p></div>
<audio id="audio2" src="./AVPlayer/altImgs/02_oh_no.mp3" type="audio/mpeg" alt="Telephone Intercept Tones and woman saying 'oh no'">No Audio Support</audio>
<div class="xmplc card col-12 d-flex shadow-md px-sm-0"><div tabindex="0" class="card-body" id="caption2" style="display: block;"><p>This panel uses all three of the AVPlayer buttons.<br><br>The first button controls audio muting and the default is muted audio. This button enables or disables the audio playback for all of the panels.<br><br>The second button aids in the important area of content accessibility. It enables or disables the display of a &#34;closed caption&#34; or text transcript description of the audio content for all of the panels.<br><br>The third button is the PLAY button and it plays the audio and animation content. The alternate content can also be displayed by clicking the image itself.</p></div><div tabindex="0" class="card-body xmpla" id="altcap2" style="display: none;"><p>This panel also uses all three of the AVPlayer buttons.<br><br>But now the third button is the RELOAD button and it reloads the audio and animation content. The original image can also be reloaded by clicking this image.</p></div></div>
    <div class="card col-12 px-sm-0" style="opacity: 0;"><br><br></div>
    <!-- ++++++++++++++++++++ -->

Разделы практически одинаковы как для первой, так и для второй панели HTML. HTML-код второй панели добавляет код для кнопок SVG Audio и CC к кнопке Play/Reload, которую мы видели ранее.

Также добавлен небольшой бит для отображения текста стенограммы и воспроизведения звука, если он не отключен.

В демо есть еще три панели; давайте кратко опишем каждый из них. Третья панель на самом деле представляет собой неподвижное изображение, заменяющее другое неподвижное изображение, но оба изображения одинаковы, поэтому он просто воспроизводит аудиофайл без изменения внешнего вида.

Четвертая панель в демонстрации показывает уже запущенную анимацию, а проигрыватель используется для загрузки альтернативной анимации. Пятая и последняя панель не имеет исходного изображения, которое нужно заменить; обычно кнопка Воспроизвести вставляет и воспроизводит анимацию прямо над ней.

Вместо этого вы можете вставить анимацию НИЖЕ кнопки Воспроизвести, как это сделано в демоверсии.

Теперь браузер и элемент HTML достаточно хорошо справляются с выбором изображения для загрузки. У нас есть хороший диапазон детализации при выборе размеров изображения, поэтому есть большая вероятность, что он будет близок.

После того, как браузер выполнит тяжелую работу по выбору и загрузке лучшего изображения, мы можем использовать CSS, чтобы подогнать его под наш экран. Однако мы хотим большего, чем просто сжимать или растягивать содержимое, поэтому мы используем программу JavaScript.

Демонстрационный код JavaScript

Демонстрационная программа JavaScript, reader.js, включена в репозиторий GitHub. Наша программа JavaScript делает две вещи. В HTML-коде, который вызывает JavaScript, у нас есть параметр defer, который заставляет программу откладывать свое выполнение.

Сначала сценарий определяет, какое изображение браузер выбрал и загрузил из списка , а затем масштабирует это изображение до размеров области просмотра для наилучшего соответствия, сохраняя соотношение сторон изображения.

Он ищет событие изменения размера окна и соответствующим образом регулирует масштабирование. И он собирает некоторую информацию об изображении. Ниже приведена первая часть скрипта.

// Reader.js
// scale the images to fill the viewport and keep aspect
// this is for demo and has extra tagging/labeling features
// comment out the console.info messages once you figure it out
 var showDetails = 0;
 var showVPDetails = 0;
 var clicked = 2;
 var currentImgID = 0;
 var viewportWidth = $(window).width();
 var viewportHeight = $(window).height();
 var elWidth = 0;
 var elHeight = 0;
 var scale = 1;
 var mp3Count = 0;
 var AltDataMsg = "";
 //var audio;
 var currentImgFolder = "";
 var currentBase = "";
 var currentMP3 = "";

 // browser needs to decide which source image to load for each image element
 // before it can tell us which it is, use window onload instead of jquery ready

window.onload = function() {
let searchParams = new URLSearchParams(window.location.search);
if(searchParams.has('info')) { // show some details about the images on query
 var showDetails = 1;
    console.info("show details true");
} // true
$(window).resize(function() {
    if(showVPDetails == 1) {
        $("body").append('<div id="viewport-size" style="display:block;color:#fff;background:#08F;position:fixed;top:0;left:0;font-size:2vw;z-index:5;"></div>');}
    var viewportWidth = $(window).width();
    var viewportHeight = $(window).height();
    var VPaspectRatio = viewportWidth / viewportHeight;
    var VPaspectRounded = (Math.round(VPaspectRatio * 100)) / 100;
      // console.info("rounded VP aspect " + VPaspectRounded);
    if(showVPDetails == 1) {
    $("#viewport-size").html('<div class="dimensions">' + viewportWidth + ' &times; ' + viewportHeight + ' px &amp; w/h = ' + VPaspectRounded + ' </div>');}

    // delete old info cards on resize
    $(".info").each(function() {
      this.remove();
    });
    // get total src image count not including alt images
    var pnlmatched = $(".imgblock img.src");
    var pnlimgCount = pnlmatched.length;
    console.info("Number of src panels = " + pnlimgCount);
    // get total alt image count
    var altmatched = $(".imgblock img.alt");
    var altimgCount = altmatched.length;
    console.info("Number of alt panels = " + altimgCount);
    // get total alt audio count
    var mp3matched = $(".imgblock .playMP3");
    var mp3Count = mp3matched.length / 2;
    console.info("Number of alt panels with audio = " + mp3Count);

    var matched = $(".imgblock img");
    var imgCount = matched.length;
    console.info("Total Number of images/panels = " + imgCount);

    // loop through each image and tag it with an "id"
    matched.each(function() {
        console.info("================");
        console.info("currentSource "+ this.currentSrc);
        currentImgID = (this.getAttribute("id"));
            console.info("current img ID = "+ currentImgID);
        //});
        //console.info("next index "+ imgcounter);

    if (this.currentSrc.endsWith(".gif")) {
      // get the image dimensions, faster to have sizes already specified
        if (this.currentSrc.includes("-w") && this.currentSrc.includes("-h")) {
        var fnameLen = this.currentSrc.indexOf(".gif");
        var elWidth = this.currentSrc.substr((this.currentSrc.indexOf("-w") + 2), 3 );
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-h") + 2), 3 );
        }
    }
    if (this.currentSrc.endsWith(".avif")) {
      // get the image dimensions, faster to have sizes already specified
        if (this.currentSrc.includes("-w") && this.currentSrc.includes("-h")) {
        var fnameLen = this.currentSrc.indexOf(".avif");
        var elWidth = this.currentSrc.substr((this.currentSrc.indexOf("-w") + 2), 3 );
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-h") + 2), 3 );
        }
    }
    if (this.currentSrc.endsWith(".webp")) {
      // get the image dimensions, faster to have sizes already specified
        if (this.currentSrc.includes("-w") && this.currentSrc.includes("-h")) {
        var fnameLen = this.currentSrc.indexOf(".webp");
        var elWidth = this.currentSrc.substr((this.currentSrc.indexOf("-w") + 2), 3 );
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-h") + 2), 3 );
        }
    }
/*if (document.body.classList.includes("no-avif")) {    avifSpt = 'no-avif';
    console.info('avifSpt ' + avifSpt);}
    if (this.currentSrc.endsWith(".avif") && avifSpt == 'no-avif') {
    nameLength = (this.currentSrc.length ) - 4;
    nameString = this.currentSrc.substr(0, nameLength) + 'webp';
    console.info('nameString ' + nameString);
    this.currentSrc = nameString;
    }
*/

    if ((this.currentSrc.endsWith(".webp")) || (this.currentSrc.endsWith(".jpg")) || this.currentSrc.endsWith(".avif")) {

        if (this.currentSrc.includes("-s-")) {
      // get the image dimensions, faster to have sizes already specified
        var elWidth = 576;
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-s-") + 3 ), 4 );
        }
        if (this.currentSrc.includes("-m-")) {
      // get the image dimensions, faster to have sizes already specified
        var elWidth = 768;
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-m-") + 3 ), 4 );
        }
        if (this.currentSrc.includes("-l-")) {
      // get the image dimensions, faster to have sizes already specified
        var elWidth = 992;
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-l-") + 3 ), 4 );
        }
        if (this.currentSrc.includes("-x-")) {
      // get the image dimensions, faster to have sizes already specified
        var elWidth = 1200;
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-x-") + 3 ), 4 );
        }
        if (this.currentSrc.includes("-X-")) {
      // get the image dimensions, faster to have sizes already specified
        var elWidth = 1400;
        var elHeight = this.currentSrc.substr((this.currentSrc.indexOf("-X-") + 3 ), 4 );
        }
    }
        console.info("nW "+elWidth);
        console.info("nH "+ elHeight);
         elWidth = parseInt(elWidth);
         elHeight = parseInt(elHeight);
         viewportWidth = parseInt(viewportWidth);
         viewportHeight = parseInt(viewportHeight);
          //console.info("eW "+elWidth);
          //console.info("eH "+elHeight);
         // console.info("vW "+viewportWidth);
         // console.info("vH "+viewportHeight);
        var aspect = elWidth/elHeight;
        var aspectRounded = (Math.round(aspect * 100)) / 100;
         // console.info("rounded img aspect " + aspectRounded);
        var widthRatio = viewportWidth / elWidth;
        var heightRatio = viewportHeight / elHeight;
         // console.info("wR "+widthRatio);
         // console.info("hR "+heightRatio);
         // default to the width ratio until proven wrong
        var scale = widthRatio;
        if (widthRatio * elHeight > viewportHeight) {
            scale = heightRatio;};
        var scaleRounded = (Math.round(scale * 100)) / 100;
        //  console.info("rounded scale " + scaleRounded);
        //  fit the content into the window
    // checkpoint for 1x1 image
    if ((elHeight == 1) && (elHeight == 1)) {
        hsize = elWidth;
        vsize = elHeight;
    } else {
        var hsize  = Math.round(elWidth * scale);
        var vsize = Math.round(elHeight * scale);
    }
         console.info ("hsize "+hsize);
         console.info ("vsize "+vsize);
      // finally set the scaled image width and height attributes
        this.setAttribute("width", hsize);
        this.setAttribute("height", vsize);
        this.setAttribute("src", this.currentSrc);

      // for the demo show a bunch of info about the image as displayed
        // parse out the source name and folder for messages and to see if we have audio
        var currentImg = this.currentSrc;
        var currentImgSource = [];
        currentImgSource = this.currentSrc.split('/');
        var currentImgFilename = currentImgSource[currentImgSource.length - 1];
        console.info("currentImgFilename "+ currentImgFilename);
        var currentImgFolder = currentImgSource[currentImgSource.length - 2];
        console.info("currentImgFolder "+ currentImgFolder);
        //currentImgPath = currentImgSource.pop();
        //console.info("currentImgPath array "+ currentImgSource);
        var currentImgName = [];
        currentImgName = currentImgFilename.split('.');
        var currentImgNoExt = currentImgName[0];
        //console.info("current Img name no extension"+ currentImgNoExt);
        var currentBasePlus = [];
        currentBasePlus = currentImgNoExt.split('-');
        var currentBase = currentBasePlus[0];
        console.info("current Img basename "+ currentBase);
        var currentMP3 = "";
        currentImg = document.getElementById(currentImgID);
        if((currentImg.classList.contains("playMP3")) && (currentImg.classList.contains("playGIF"))) {
            currentMP3 = currentBase + '.mp3';
            console.info('audio file exists = ' + currentMP3);
            AltDataMsg = "<br>There is an audio file named <span  style="color: darkBlue;"><b><i>"+currentMP3+"</i></b></span> that will play if you click the panel or the play button with audio unmuted. By default the audio is muted. Click the audio icon to toggle audio muting.";
            //alert('file exists');
        } else {
            console.info('there is no audio file');
            AltDataMsg = "";
        }

        if((showDetails == 1) && (this.classList.contains("src")) && !(this.classList.contains("playGIF"))) {
            console.info("imageInfo");
            //this.setAttribute("id", imgcounter);
        // create info msg about the image
            srcid = "srcinfo"+currentImgID;
            console.info("srcinfo "+srcid);
            imageInfo = "<div id="+srcid+" class="info card imginfo col-12 shadow-md px-sm-0" style="background-color: #b0d0ec;"><p style="margin: 1vw;">This image above is named <span style="color: darkBlue;"><b><i>"+currentImgFilename+"</i></b></span> and it is panel number "+currentImgID+" of "+pnlimgCount+" total panels.<br>The source image size is "+elWidth+" X "+elHeight+" pixels for an aspect ratio of "+aspectRounded+". A scale multiplier of "+scaleRounded+" was then applied to fit the image to the viewport, resulting in the Image Display Size of "+hsize+" X "+vsize+" pixels seen here. There is no alternate image or audio for this panel.</p></div>";
        // display the info for this image
                $(this).after(imageInfo); 
                document.getElementById(srcid).style.display = "block";
        }
        if((showDetails == 1) && (currentImg.classList.contains("src")) && (currentImg.classList.contains("playGIF"))) {
        console.info("srcimginfo")
            //this.setAttribute("id", imgcounter);
        // create info msg about the image
            srcid = "srcinfo"+currentImgID;
            console.info("srcinfo "+srcid);
            srcimageInfo = "<div id="+srcid+" class="info card srcinfo col-12 shadow-md px-sm-0" style="background-color: #b0d0ec;"><p style="margin: 1vw;">This image above is named <span style="color: darkBlue;"><b><i>"+currentImgFilename+"</i></b></span> and it is panel number "+currentImgID+" of "+pnlimgCount+" total panels.<br>The source image size is "+elWidth+" X "+elHeight+" pixels for an aspect ratio of "+aspectRounded+". A scale multiplier of "+scaleRounded+" was then applied to fit the image to the viewport, resulting in the Image Display Size of "+hsize+" X "+vsize+" pixels seen here. This panel has an alternate image that will display if you click the panel or the play button."+AltDataMsg+"</p></div>";
        // display the info for this image
            $(this).after(srcimageInfo); 
            document.getElementById(srcid).style.display = "block";
        }

        if ((showDetails == 1) && (currentImg.classList.contains("alt")) && (currentImg.classList.contains("playGIF"))) {
        console.info("altimginfo")
            //this.setAttribute("id", imgcounter);
            altid = "altinfo"+currentImgID;
            console.info("altinfo "+altid);
            altimageInfo = "<div id="+altid+" class="info card altinfo col-12 shadow-md px-sm-0" style="background-color: #b0d0ec;"><p style="margin: 1vw;">This image above is named <span style="color: darkBlue;"><b><i>"+currentImgFolder+'/'+currentImgFilename+"</i></b></span> and it is panel number "+currentImgID+" of "+pnlimgCount+" total panels.<br>The source image size is "+elWidth+" X "+elHeight+" pixels for an aspect ratio of "+aspectRounded+". A scale multiplier of "+scaleRounded+" was then applied to fit the image to the viewport, resulting in the Image Display Size of "+hsize+" X "+vsize+" pixels seen here. This panel is an alternate image that displays from a click on the panel or the play button."+AltDataMsg+"</p></div>";
        // display the info for this image
            $(this).after(altimageInfo); 
            document.getElementById(altid).style.display = "none";
        }
    }) // processed each matched image
}).trigger('resize'); //rescale images on viewport resize

Этот сегмент сценария, показанный выше, выполняется или повторяется для каждого отображаемого изображения. В производственной среде вы, скорее всего, удалите или отключите сбор и отображение информации в демо-версии.

Этот второй и последний раздел программы JavaScript, показанный далее, номинально «где происходит действие». Первые две процедуры кода, показанные далее, предназначены для включения и включения воспроизведения звука, а также для включения и отключения отображения расшифровки текста скрытых субтитров.

/* ----------------------- */
// audio mute unmute toggle
$("audio").prop('muted', true); // muted by default
$(".bi-volume-up").hide(0);
$(".bi-volume-mute").show(0);
// toggle on click
  $(".mute-audio").click( function (){
    console.info("------ mute audio clicked -------");
    if( $("audio").prop('muted') ) {
          $("audio").prop('muted', false);
          $(".bi-volume-mute").hide(0);
          $(".bi-volume-up").show(0);
    } else {
      $("audio").prop('muted', true);
      $(".bi-volume-up").hide(0);
      $(".bi-volume-mute").show(0);                                 
    }
  });
// toggle on enter key
$(".mute-audio").keyup(function(event) {
  if (event.keyCode === 13) {
   event.preventDefault();
   $(".mute-audio").click();
  }
});

/* ----------------------- */
// Control transcript display
    var transtext = "active"; // active by default
      $(".transcript").hide(0);
      $(".bi-badge-cc").show(0);
      $(".bi-x-box").hide(0);
// toggle display on click
$('.transcriptControl').click( function() {
    console.info('----- transcriptControl clicked -----');
    console.info("currentImgID "+ currentImgID);
    if(transtext == "notactive") {
        transtext = "active";
      $(".bi-x-box").hide(0);
      $(".bi-badge-cc").show(0);
    } else {
        transtext = "notactive";
      $(".bi-badge-cc").hide(0);
      $(".bi-x-box").show(0);
    }
    console.info("transtext "+ transtext);
});
// toggle on enter key
$(".transcriptControl").keyup(function(event) {
  if (event.keyCode === 13) {
   event.preventDefault();
   $(".transcriptControl").click();
  }
});

Последняя процедура в нашем JavaScript показана ниже. Он ищет щелчок по элементам с классом clickMeOverlay, который, как правило, включает каждое изображение и связанную с ним кнопку Play/Reload.

По щелчку эта процедура меняет текущее изображение на альтернативное, обычно анимацию, а на стороне цикла Воспроизведение воспроизводит звук, если он не отключен.

/* ----------------------- */
// control alternate img display
      $(".bi-arrow-counterclockwise").hide(0);
      $(".bi-play").show(0);
// toggle image to alternate img on click
// if it is a GIF it plays GIF each time clicked
$('.clickMeOverlay').click( function(event) {
    imgid = $(this).attr('id');
    imgindex = imgid.substring(1);
    //clickaltinfo = $('.clickMeOverlay').children('.altinfo');
    //clicksrcinfo = $('.clickMeOverlay').children('.srcinfo');
    console.info('----- play has been clicked -----');
    console.info("transtext "+ transtext);
    console.info("imgid " + imgid);
    console.info("imgindex " + imgindex);
    //console.info("mp3ID "+ mp3ID);
    source = ("s"+imgindex);
    altsource = ("a"+imgindex);
    pbutton = ("p"+imgindex);
    rbutton = ("r"+imgindex);
    tbutton = ("t"+imgindex);
    console.info("sourcetag " + source);
    console.info("altsourcetag " + altsource);
    console.info("Pbuttontag " + pbutton);
    console.info("Rbuttontag " + rbutton);
    console.info("transcripttag " + tbutton);
    aimage = document.getElementById(altsource);
    simage = document.getElementById(source);
    play = document.getElementById(pbutton);
    reload = document.getElementById(rbutton);
    tscript = document.getElementById(tbutton);

    //console.info(image);
    if(aimage.style.display == "none") {
        simage.style.display = "none";
        aimage.style.display = "block";
        play.style.display = "none";
        reload.style.display = "block";
        if(transtext == "active") {
        if(typeof(tscript) != 'undefined' && tscript != null) {
        tscript.style.display = "block";}}
        if(showDetails == 1) {
            src = document.getElementById("srcinfo"+source);
            src.style.display = "none";
            alt = document.getElementById("altinfo"+altsource);
            alt.style.display = "block";}
        cap = document.getElementById("caption" + imgindex);
        $(cap).css("display", "none");
        acap = document.getElementById("altcap" + imgindex);
        $(acap).css("display", "block");
        clicked = 1;

        console.info('----- image and caption changed -----');
        console.info("altimgID "+ imgindex);
        if ( $("audio").prop('muted') == false ) {
            console.info("audio not muted. play the audio.");
            var audio_element = document.getElementById("audio" + imgindex);
            if(typeof(audio_element) != 'undefined' && audio_element != null) {
            audio_element.load();
            audio_element.playclip = function(){
                audio_element.pause();
                audio_element.currentTime=0;
                audio_element.play();}
            audio_element.playclip();
        }}
        } else {
        console.info('----- back to original image and caption -----');
        aimage.style.display = "none";
        simage.style.display = "block";
        reload.style.display = "none";
        play.style.display = "block";
        if(transtext == "active") {
        if(typeof(tscript) != 'undefined' && tscript != null) {
        tscript.style.display = "none";}}
        if(showDetails == 1) {
            src.style.display = "block";;
            alt.style.display = "none";}
        acap = document.getElementById("altcap" + imgindex);
        $(acap).css("display", "none");
        cap = document.getElementById("caption" + imgindex);
        $(cap).css("display", "block");
        clicked = 0;
        }
console.info("Clicked " + clicked);
});

И последнее замечание: программа reader.js отображает значительную часть информации в окне консоли отладчика браузера. В производственных средах вы, вероятно, урежете часть этой информации.

Заключение

Помимо учебного примера, этот код также послужит шаблоном для новой версии моего конструктора комиксов. Следите за обновлениями! Как упоминалось ранее, эту демонстрацию можно просмотреть на нашем веб-сайте, а код и содержимое все они представлены в репозитории GitHub.

Надеюсь, вы найдете что-то из этого полезным в своих собственных усилиях веб-разработчика. Как всегда, ваши комментарии, критика и предложения приветствуются.


Оригинал