1

I have made a click to reveal interactivity. The setup is a background image with 7 areas on top that have a hover state and are clickable.

When a clickable area is clicked, a separate image is displayed (1 out of 7) in the surrounding area until all 7 are revealed.

I've managed to achieve this already, however I think there could be a much more efficient way to do it without repeating similar functions over and over.

Here is a simplified version of my current code on js fiddle

HTML

   <!-- Background img of character-->
   <img src="https://s2.postimg.org/g9iokigk9/man_cartoon.jpg" width="500px" />

   <!-- Hover circles -->
   <a class="hover-btn-face" style="top:80px; left:225px;">
      <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
   </a>

   <a class="hover-btn-shoulder" style="top:150px; left:180px;">
      <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
   </a>

   <a class="hover-btn-hand" style="top:320px; left:170px;">
      <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
   </a>


   <!-- Icons that are revealed on click of hover circles -->
   <div class="icon icon-face" style="top:70px; left:0">
     <p>Face</p>
   </div>

   <div class="icon icon-shoulder" style="top:150px; left:0">
     <p>Shoulder</p>
   </div>

   <div class="icon icon-hand" style="top:320px; left:0">
     <p>Hand</p>
   </div>

</div>

CSS

    <style>

    .container {
        position:relative;
    }   

    a {
       position:absolute;
       width:50px;
       height:50px;
    }

    a img {
       display:none;
       width: 100%;
       height: 100%;
      }

    a:hover img {
      display:block;
    }

   .icon {
      width:100px;
      padding:5px;
      background:blue;
      position:absolute;
      color:#fff;
      text-align:center;
      font-family:arial;
      border-radius:10px;
    }

    </style>

JS

var parts = ['face', 'shoulder', 'hand'];

var icon, i;

for (i = 0; i < parts.length; i++) {
    icon = document.querySelector('.icon-' + parts[i]) ;
      icon.style.display = 'none';

}


document.querySelector('.hover-btn-' + parts[0]).addEventListener('click', function() {

    document.querySelector('.icon-' + parts[0]).style.display = "block";

});

document.querySelector('.hover-btn-' + parts[1]).addEventListener('click', function() {

    document.querySelector('.icon-' + parts[1]).style.display = "block";

});

document.querySelector('.hover-btn-' + parts[2]).addEventListener('click', function() {

document.querySelector('.icon-' + parts[2]).style.display = "block";

});
Nope
  • 22,147
  • 7
  • 47
  • 72
TommyR
  • 103
  • 1
  • 8
  • 4
    `however I think there could be a much more efficient way to do it without repeating similar functions over and over` I think this might be a better question for [**Codereview Stackexchange**](https://codereview.stackexchange.com) over here this question is most likely be closed as `primarily opinion-based` as `Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise.` – Nope Dec 18 '17 at 17:16
  • 3
    Learn about reusing code via functions: http://eloquentjavascript.net/03_functions.html . – Felix Kling Dec 18 '17 at 17:16
  • ^ Indeed! The resulting code might read `parts.forEach((part) => setDisplayOnClick(part));`, and `function setDisplayOnClick(part) { ... }` is pretty trivial to factor out. – 9000 Dec 18 '17 at 17:21
  • I assume you already tried setting the event listeners using a loop to minimize the code but you fell into [**this famous issue**](https://stackoverflow.com/questions/750486/javascript-closure-inside-loops-simple-practical-example). – ibrahim mahrir Dec 18 '17 at 17:23
  • Another way would be to give all elements the same `data-id="face"`, `data-id="head"` etc.. and then simply bind a click event to all `getElementsByTagName(a)` using the `data-id` value of the clicked element to interact with the matching `div` element with the same attribute value. Also, I would not set a style in code but rather add/remove classes as needed to apply the style. Though there is most likely better ways of doing it. – Nope Dec 18 '17 at 17:28

1 Answers1

0

One approach is the following; though it's worth noting that I have made a minor change to your HTML, adding a custom data-* attribute to each of the <a> elements which define, or appear over, a body-part, converting:

<a class="hover-btn-face" style="top:80px; left:225px;">
  <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
</a>

into:

<a class="hover-btn-face" data-anatomy="face" style="top:80px; left:225px;">
  <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
</a>

That said, however, my revision of your approach is below:

// creating a named function to handle the (repeated) behaviours:
function showDetails() {

  // declaring local variables using the 'let' statement,
  // retrieving the attribute-value of the 'data-anatomy'
  // custom attribute (set in the HTML) from the 'this'
  // DOM node (passed to the function from
  // eventTarget.addEventListener():
  let part = this.dataset.anatomy;

  // here we find the single element which has a class name
  // comprised of the string 'icon' concatenated  with the
  // anatomical 'part' retrieved earlier, and updating its
  // 'display' property to 'block':
  document.querySelector('.icon-' + part).style.display = 'block';
}

// here we use document.querySelectorAll() to retrieve all
// elements with a class of 'icon', that nodeList is passed
// to Array.from() and converted to an Array, in order to use
// Array methods:
Array.from(document.querySelectorAll('.icon'))

  // here we use Array.prototype.forEach() to iterate over
  // all elements in the resulting Array:
  .forEach(

    // using an Arrow function, 'icon' is a reference to the
    // current array-element of the array over which we're
    // iterating; and here we update its 'display' property
    // to 'none' (to hide the element(s) on page-load):
    icon => icon.style.display = 'none'
);

// retrieving a NodeList of all <a> elements with a custom
// 'data-anatomy' attribute, and converting that NodeList
// into an Array using Array.from():
let parts = Array.from(document.querySelectorAll('a[data-anatomy]'));

// iterating over that Array of elements using
// Array.prototype.forEach():
parts.forEach(

  // Using an Arrow function, here 'part' is a reference to the
  // current array element of the array of DOM nodes; here we
  // use EventTarget.addEventListener() to bind the showDetails
  // function (note the deliberate lack of parentheses) as the
  // event-handler for the 'click' event:
  part => part.addEventListener('click', showDetails)
);

function showDetails() {
  let part = this.dataset.anatomy;
  document.querySelector('.icon-' + part).style.display = 'block';
}

Array.from(document.querySelectorAll('.icon')).forEach(
  icon => icon.style.display = 'none'
);

let parts = Array.from(document.querySelectorAll('a[data-anatomy]'));

parts.forEach(
  part => part.addEventListener('click', showDetails)
);
.container {
  position: relative;
}

a {
  position: absolute;
  width: 50px;
  height: 50px;
}

a img {
  display: none;
  width: 100%;
  height: 100%;
}

a:hover img {
  display: block;
}

.icon {
  width: 100px;
  padding: 5px;
  background: blue;
  position: absolute;
  color: #fff;
  text-align: center;
  font-family: arial;
  border-radius: 10px;
}
<div class="container">

  <!-- Background img of character-->
  <img src="https://s2.postimg.org/g9iokigk9/man_cartoon.jpg" width="500px" />

  <!-- Hover circles -->
  <!-- note the addition of the custom data-* attribute, in order to
       avoid having to hard-code the 'part' in the JavaScript without
       having to parse the relevant 'part' from the class-name(s) -->
  <a class="hover-btn-face" data-anatomy="face" style="top:80px; left:225px;">
    <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
  </a>

  <a class="hover-btn-shoulder" data-anatomy="shoulder" style="top:150px; left:180px;">
    <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
  </a>

  <a class="hover-btn-hand" data-anatomy="hand" style="top:320px; left:170px;">
    <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
  </a>


  <!-- Icons that are revealed on click of hover circles -->
  <div class="icon icon-face" style="top:70px; left:0">
    <p>Face</p>
  </div>

  <div class="icon icon-shoulder" style="top:150px; left:0">
    <p>Shoulder</p>
  </div>

  <div class="icon icon-hand" style="top:320px; left:0">
    <p>Hand</p>
  </div>

</div>

JS Fiddle demo.

ES5-compatible approach, in order to support older versions Internet Explorer:

function showDetails() {
  let part = this.dataset.anatomy;
  document.querySelector('.icon-' + part).style.display = 'block';
}

// here we use Function.prototype.call(), to supply
// the array-like NodeList (returned from
// document.querySelectorAll()) as the object to be
// sliced into a new array by Array.prototype.call():
Array.prototype.slice.call(document.querySelectorAll('.icon'))

  // because we now have an Array, we can use the
  // Array.prototype.forEach() method to iterate over
  // the resulting Array:
  .forEach(function(icon) {

    // here we set the display property of the
    // current array-element ('icon') of the
    // array of DOM nodes/elements to 'none':
    icon.style.display = 'none'
});

// this works in exactly the same way as the above,
// but the returned Array is assigned to the 'parts'
// variable:
let parts = Array.prototype.slice.call(document.querySelectorAll('a[data-anatomy]'));

parts.forEach(function(part) {
  part.addEventListener('click', showDetails)
});

function showDetails() {
  let part = this.dataset.anatomy;
  document.querySelector('.icon-' + part).style.display = 'block';
}

Array.prototype.slice.call(document.querySelectorAll('.icon')).forEach(function(icon) {
  icon.style.display = 'none'
});

let parts = Array.prototype.slice.call(document.querySelectorAll('a[data-anatomy]'));

parts.forEach(function(part) {
  part.addEventListener('click', showDetails)
});
.container {
  position: relative;
}

a {
  position: absolute;
  width: 50px;
  height: 50px;
}

a img {
  display: none;
  width: 100%;
  height: 100%;
}

a:hover img {
  display: block;
}

.icon {
  width: 100px;
  padding: 5px;
  background: blue;
  position: absolute;
  color: #fff;
  text-align: center;
  font-family: arial;
  border-radius: 10px;
}
<div class="container">

  <!-- Background img of character-->
  <img src="https://s2.postimg.org/g9iokigk9/man_cartoon.jpg" width="500px" />

  <!-- Hover circles -->
  <a class="hover-btn-face" data-anatomy="face" style="top:80px; left:225px;">
    <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
  </a>

  <a class="hover-btn-shoulder" data-anatomy="shoulder" style="top:150px; left:180px;">
    <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
  </a>

  <a class="hover-btn-hand" data-anatomy="hand" style="top:320px; left:170px;">
    <img src="https://s2.postimg.org/mzz5tybft/red-dot.png" />
  </a>


  <!-- Icons that are revealed on click of hover circles -->
  <div class="icon icon-face" style="top:70px; left:0">
    <p>Face</p>
  </div>

  <div class="icon icon-shoulder" style="top:150px; left:0">
    <p>Shoulder</p>
  </div>

  <div class="icon icon-hand" style="top:320px; left:0">
    <p>Hand</p>
  </div>

</div>

References:

David Thomas
  • 249,100
  • 51
  • 377
  • 410
  • You're very welcome indeed, I'm glad to have helped. :) – David Thomas Dec 19 '17 at 14:27
  • Just a small question - I'm using this across all browsers and IE doesn't seem to like the arrow functions. How could I write these without arrow functions? Sorry if this seems like a dumb question - I'm really new to javascript. – TommyR Dec 19 '17 at 14:58
  • TommyR: that's covered in the reference link for 'Arrow functions,' but I'll edit the answer to include it once I've finished traveling; it'll be a few hours though, I'm sorry to say. Incidentally, does IE manage to work with `Array.from()`, or do you need an alternative to that as well? I might just edit to write an ES5-compatible answer :) – David Thomas Dec 19 '17 at 15:26
  • I managed to change the arrow functions to regular ones however you're guess was right about `Array.from()` not working in IE. If you could write a ES5 compatible solution when you have time I'd really appreciate it :) – TommyR Dec 19 '17 at 16:07
  • Please see the updated answer, the ES5-compatible approach is the final portion of the answer. Hope it helps! :) – David Thomas Dec 19 '17 at 22:44
  • Thank you, you've been a massive help and your thorough explanation of the code is really appreciated :) – TommyR Dec 20 '17 at 11:46