-1

I wrote the following code in Bootstrap 4 in order to play/pause a video when it is clicked on, it also toggle a little speaker img that is below the video.

I don’t want to have to duplicate this function for each video/img pair I have on my page, is there a way to rewrite this function to make it only affect the video that was clicked on, and somehow make it change the corresponding img as well? Is there some way to do it use Class?

<div class="col bg-primary p-0" style="width: 100%; max-width: 787px;">
<div class="bg-danger">
<video id="c20-vid" style="width: 100%; max-width: 787px;" preload="metadata" poster="#.png" playsinline loop>
<source src="#.mp4" type="video/mp4">
</video>
</div>
<div class="text-center captions bg-warning">
<img src="img/muted_icon.png" width="15em" id="c20-mute-icon" />
</div>
</div>

and here's the JavaScript:


function playme() {
    // in this case it's the element that was clicked
    var video = this;
    var muteicon = document.getElementById("c20-mute-icon");
    
    if(video.paused) {
        video.play();
        muteicon.src="img/unmuted_icon.png";
    } 
    else {
        video.pause();
        muteicon.src="img/muted_icon.png";
    }
}

document.getElementById("c20-vid").addEventListener("click", playme);
robotmail
  • 31
  • 5

2 Answers2

0

In HTML:

<video id=“my-vid" onclick="toggleMute('my-vid', 'my-img');">
    <source src=“my.mp4”>
</video>
<img id="my-img" src="img/muted_icon.png" id=“speaker-icon"  />

In javascipt:

function toggleMute(vid, img) { // add parameters
  var video = document.getElementById(vid); // use parameter
  var muteicon = document.getElementById(img); // use parameter
    
  if(video.muted) {
    video.muted = false;
    muteicon.src="img/unmuted_icon.png";
  } 
  else {
    video.muted = true;
    muteicon.src="img/muted_icon.png";
  }
}

I'm not sure that this is how you access video to mute - That's up to you to find out/be sure.

iAmOren
  • 2,760
  • 2
  • 11
  • 23
  • Thanks, I will try this out tomorrow and post back. – robotmail Jul 12 '20 at 03:53
  • I'm not sure this solution makes sense. I want to be able to use one function that can be applied to whichever video element invokes the event. This looks similar to what I already have where I have to duplicate it for each video/img pair and a unique id. Am I not understanding something? – robotmail Jul 12 '20 at 18:40
  • You don't have to duplicate the function. For each video and img - add id and onclick. You could enclose video and img in a div and call onclick (or write an event listener) with the div's id, and in the function, look for video and img children of passed div id - and then toggle mute and icon. – iAmOren Jul 13 '20 at 03:54
0

It sounds like you want to do two things:

  1. Write an event handler that is aware of its invocation context, without hardcoding that context.
  2. Find an HTML element relative to another element.

Let's address these separately and then see an example that does both.

Writing an event handler that is aware of its invocation context

This is what this is for :)

From the MDN article on this:

When a function is used as an event handler, its this is set to the element on which the listener is placed (some browsers do not follow this convention for listeners added dynamically with methods other than addEventListener()).

In simple terms, this is a special variable whose value changes based on where a function is called from.

This SO question addresses scopes and contexts in JavaScript more thoroughly.

Finding an HTML element relative to another element

Some combination of the Element.querySelector function and DOM Node interface's parent/child/sibling properties is usually enough to get this job done.

Putting it all together

Here's a snippet that applies these ideas to your HTML structure (wrapped in an outer div):

// Define event handler
function toggleMute(e) {
    // "this" comes from the calling context
    // in this case it's the element that was clicked
    var video = this;
    // get the video's parent element and find the first img element inside it
    var muteicon = video.parentElement.querySelector('img');
    
    // do some stuff with these elements
    if(video.muted) {
        video.muted = false;
        video.style.background = "green"
        muteicon.style.background = "yellow";
    } 
    else {
        video.muted = true;
        video.style.background = "orange"
        muteicon.style.background = "teal";
    }
}

// Attach handler to video element, listening for "click" event
document.getElementById('my-vid').addEventListener("click", toggleMute);
video { height: 100px; width: 100px; background: green; }
img { height: 50px; width: 50px; background: yellow; }
<div>
  <video id="my-vid">
      <source src="my.mp4">
  </video>
  <br>
  <img src="img/muted_icon.png" id="speaker-icon" />
</div>

Additional possibilities

There are other ways to make an event handler aware of its context. You could also use the event object that is exposed when the event is dispatched, or pass this in explicitly in HTML. See the examples below. My preference is to avoid setting handlers inline in HTML, so I'd opt for the second or third methods.

function ping(target) {
  console.log(target);
  target.classList.toggle('pinged');
  const next = target.nextElementSibling;
  next.classList.toggle('pinged');
  next.nextElementSibling.classList.toggle('pinged');
  
}

function pong() {
  console.log(this);
  this.classList.toggle('ponged');
  this.nextElementSibling.classList.toggle('ponged');
  this.previousElementSibling.classList.toggle('ponged');
}

function pang(e) {
  console.log(e.target);
  e.target.classList.toggle('panged');
  const prev = e.target.previousElementSibling;
  prev.classList.toggle('panged');
  prev.previousElementSibling.classList.toggle('panged');
}

// 'ping' attached to element 'one' inline, in HTML
document.getElementById('two').addEventListener("click", pong);
document.getElementById('three').addEventListener("click", pang);
img {max-height: 200px; max-width: 200px;}
.pinged {filter: sepia(80%)}
.ponged {transform: scale(0.5)}
.panged {opacity: 0.5;}
<img id="one" src="https://www.thewholesomedish.com/wp-content/uploads/2019/06/The-Best-Classic-Tacos-550-500x500.jpg" onclick="ping(this)">

<img id="two" src="https://www.thewholesomedish.com/wp-content/uploads/2019/06/The-Best-Classic-Tacos-550-500x500.jpg">

<img id="three" src="https://www.thewholesomedish.com/wp-content/uploads/2019/06/The-Best-Classic-Tacos-550-500x500.jpg">
eberts
  • 121
  • 6
  • Thanks but I learn best by seeing examples and then adapting them. Would you be able to show your work? But yes what you’re describing above is what I’m looking to do. – robotmail Jul 12 '20 at 03:52
  • Thanks. And I mean to say this as graciously as possible, but this isn't helping! You have clearly written out an exhaustive and comprehensive example which I am extremely grateful for and maybe someday I'll understand it, but right now I'm hoping someone can just write out an example using the parameters I provided in my question so that I can dissect it and figure out how it works. This answer provides too many options and adapts my question in abstract ways. I really appreciate your time, but this is over my head and I can't even get it fully working properly. – robotmail Jul 12 '20 at 19:00
  • Also, if you do have time to rewrite the answer you can just give me the option you think is best, I don't need so many ways of doing it I'm just trying to understand first how it can be done, I will definitely look into best practices once I understand it though. – robotmail Jul 12 '20 at 19:05
  • Fair point, I can see how that was too much info. Edited to break down the question a bit more and provide an example that maps onto your existing structure. – eberts Jul 12 '20 at 21:06
  • Thanks! I've been working on this. I think one problem I'm having is that the img isn't in the parent element of the video, it's in its own div in the next parent up. So both the video and the img are in their own div inside of another div. Is it still possible to query the img in that div element? Also this doesn't have anything about play/pause, is it possible to do that? I can't get anything to work. – robotmail Jul 17 '20 at 01:07
  • I've just edited my original question to try to be more clear and also comprehensive – robotmail Jul 17 '20 at 01:45
  • I've updated the question, I can't figure it out, I've tried adapting all of the solutions you provided up above but none will work. – robotmail Jul 18 '20 at 01:22
  • When the icon shares a parent with the video (that is, they're siblings), you can use `video.parentElement.querySelector` to find the icon. If they share a grandparent you can try `video.parentElement.parentElement.querySelector`. Think about the relative locations of the elements in the DOM tree. Video playback issues are a separate question. Try searching for "HTMLMediaElement methods". – eberts Jul 18 '20 at 07:12