iLoveJS
iLoveJS

Reputation: 31

Could it be simpler?

I wrote a little vanilla JavaScript program, but I want to know if there is a possibility to write this simpler? With ES6+? But only with vanila JavaScript, no jQuery or other libraries / frameworks. Thanks in advance for the suggested solutions.

const red = document.getElementById('circleOne');
const green = document.getElementById('circleTwo');
const blue = document.getElementById('circleThree');

red.addEventListener("mouseover", () => {
  red.style.backgroundColor = "red";
});

red.addEventListener("mouseout", () => {
  red.style.backgroundColor = "white";
});

green.addEventListener("mouseover", () => {
  green.style.backgroundColor = "green";
  red.style.backgroundColor = "green";
});

green.addEventListener("mouseout", () => {
  green.style.backgroundColor = "white";
  red.style.backgroundColor = "white";
});

blue.addEventListener("mouseover", () => {
  green.style.backgroundColor = "blue";
  red.style.backgroundColor = "blue";
  blue.style.backgroundColor = "blue";
});

blue.addEventListener("mouseout", () => {
  green.style.backgroundColor = "white";
  red.style.backgroundColor = "white";
  blue.style.backgroundColor = "white";
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

section {
  margin: 100px auto 0 auto;
  max-width: 700px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

section .circle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  border: 1px solid #333;
  margin: 0 auto;
  cursor: pointer;
}
<section>
  <div id="circleOne" class="circle"></div>
  <div id="circleTwo" class="circle"></div>
  <div id="circleThree" class="circle"></div>
</section>

Thanks in advance for the suggested solutions.

Upvotes: 2

Views: 111

Answers (4)

Paul Rooney
Paul Rooney

Reputation: 21609

You could define a function which takes the dom element, the colour and additionally the other dom elements to change the colour of, and use that to define the two handlers.

const red = document.getElementById('circleOne');
const green = document.getElementById('circleTwo');
const blue = document.getElementById('circleThree');

function addCircleListeners(elem, col, ...others) {

    const allElems = [elem, ...others];

    elem.addEventListener("mouseover", () => {
        allElems.forEach(e => e.style.backgroundColor = col);
    });

    elem.addEventListener("mouseout", () => {
        allElems.forEach(e => e.style.backgroundColor = "white");
    });
}

addCircleListeners(red, "red");
addCircleListeners(green, "green", red);
addCircleListeners(blue, "blue", red, green);

Upvotes: 1

zfrisch
zfrisch

Reputation: 8660

You can use a function composition approach. This is done by looking over what you're trying to accomplish within your code, point by point, and then builds up functions to facilitate each minor task until the larger task is accomplished.

This approach breaks things down into two distinct parts: Setup and Execution.

Setup is where you design all your functions and variables, Execution is putting it all to work.

// SETUP:

const [ red, green, blue ] = document.querySelectorAll( ".circle" ),

    bg = n => c => n.style.backgroundColor = c,
    colorMatch = ( cs ) => ( fn, i ) => fn( cs[ i ] ? cs[ i ] : cs[ cs.length - 1 ] ),

    bgs = ( ...ns ) => ( ...cs ) => () => ns.map( bg ).map( colorMatch( cs ) ),

    ev = n => t => fn => n.addEventListener( t, () => fn() ),
    mout = n => fn => ev( n )( "mouseout" )( fn ),
    mover = n => fn => ev( n )( "mouseover" )( fn ),

    hover = ( n, {over, out} ) => ( mover( n )( over ), mout( n )( out ) );


// EXECUTION:

hover( red, {
    over: bgs( red )( "red" ),
    out: bgs( red )( "white" )
} );

hover( green, {
    over: bgs( red, green )( "green" ),
    out: bgs( red, green )( "white" )
} );

hover( blue, {
    over: bgs( red, green, blue )( "blue" ),
    out: bgs( red, green, blue )( "white" )
} );

Example:

const [ red, green, blue ] = document.querySelectorAll( ".circle" ),

	bg = n => c => n.style.backgroundColor = c,
	colorMatch = ( cs ) => ( fn, i ) => fn( cs[ i ] ? cs[ i ] : cs[ cs.length - 1 ] ),

	bgs = ( ...ns ) => ( ...cs ) => () => ns.map( bg ).map( colorMatch( cs ) ),

	ev = n => t => fn => n.addEventListener( t, () => fn() ),
	mout = n => fn => ev( n )( "mouseout" )( fn ),
	mover = n => fn => ev( n )( "mouseover" )( fn ),

	hover = ( n, {over, out} ) => ( mover( n )( over ), mout( n )( out ) );

hover( red, {
	over: bgs( red )( "red" ),
	out: bgs( red )( "white" )
} );

hover( green, {
	over: bgs( red, green )( "green" ),
	out: bgs( red, green )( "white" )
} );

hover( blue, {
	over: bgs( red, green, blue )( "blue" ),
	out: bgs( red, green, blue )( "white" )
} );
*{
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

section{
    margin: 100px auto 0 auto;
    max-width: 700px;
    display: grid;
    grid-template-columns: 1fr 1fr 1fr;
}

section .circle{
    width: 200px;
    height: 200px;
    border-radius: 50%;
    border: 1px solid #333;
    margin: 0 auto;
    cursor: pointer;
}
<!DOCTYPE html>
<html lang="en" dir="ltr">
<head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="main.css">
    <script defer src="main.js"></script>
</head>

<body>

    <section>
        <div id="circleOne" class="circle"></div>
        <div id="circleTwo" class="circle"></div>
        <div id="circleThree" class="circle"></div>
    </section>

</body>
</html>


Additional Info

Two points of note -

  1. I would definitely, definitely encourage you to not name your circles by their hover colors in your code. It's confusing and isn't really appropriate since the color isn't definitive of the element selected, and it makes the code immeasurably harder harder to read.
  2. Though this code will work, be aware that if this isn't within a scope all these variables will become global. This is not ideal. I would take care to place the variables within a smaller scope( into an object or something ), or place them inside an initialization event.

Upvotes: 0

CertainPerformance
CertainPerformance

Reputation: 370759

Here's one option: make a function which accepts an array of elements and sets each of their backgrounds, and add a single mouseout listener to the container which sets all to white. No need for IDs, you can put each circle into a variable quickly with querySelectorAll and destructuring:

const bgcolorAll = (arr, color) => arr.forEach(elm => elm.style.backgroundColor = color);
const section = document.querySelector('section');
section.addEventListener('mouseout', () => {
  bgcolorAll([red, green, blue], 'white');
});

const [red, green, blue] = document.querySelectorAll('section > div');

red.addEventListener("mouseover", () => {
  bgcolorAll([red], 'red');
});

green.addEventListener("mouseover", () => {
  bgcolorAll([red, green], 'green');
});

blue.addEventListener("mouseover", () => {
  bgcolorAll([red, green, blue], 'blue');
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

section {
  margin: 100px auto 0 auto;
  max-width: 700px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

section .circle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  border: 1px solid #333;
  margin: 0 auto;
  cursor: pointer;
}
<section>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
</section>

Or, even more DRY, don't select the individual circles at all, and instead use an array:

const bgcolorAll = (arr, color) => arr.forEach(elm => elm.style.backgroundColor = color);
const section = document.querySelector('section');
const circles = [...document.querySelectorAll('section > div')];
section.addEventListener('mouseout', () => {
  bgcolorAll(circles, 'white');
});

const colors = ['red', 'green', 'blue'];
section.addEventListener('mouseover', ({ target }) => {
  if (target.matches('.circle')) {
    const index = circles.indexOf(target)
    bgcolorAll(
      circles.slice(0, index + 1),
      colors[index]
    );
  }
});
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

section {
  margin: 100px auto 0 auto;
  max-width: 700px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

section .circle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  border: 1px solid #333;
  margin: 0 auto;
  cursor: pointer;
}
<section>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
</section>

You can also achieve this with CSS only:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

section {
  margin: 100px auto 0 auto;
  max-width: 700px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
  grid-template-areas: 'red green blue';
}

section .circle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  border: 1px solid #333;
  margin: 0 auto;
  cursor: pointer;
}

.circle:nth-child(1) {
  grid-area: blue;
}
.circle:nth-child(1):hover,
.circle:nth-child(1):hover ~ .circle:nth-child(2),
.circle:nth-child(1):hover ~ .circle:nth-child(3) {
  background-color: blue;
}

.circle:nth-child(2) {
  grid-area: green;
}
.circle:nth-child(2):hover,
.circle:nth-child(2):hover ~ .circle:nth-child(3) {
  background-color: green;
}

.circle:nth-child(3) {
  grid-area: red;
}
.circle:nth-child(3):hover {
  background-color: red;
}
<section>
  <div class="circle"></div>
  <div class="circle"></div>
  <div class="circle"></div>
</section>

Upvotes: 6

Jaromanda X
Jaromanda X

Reputation: 1

This is the simplest, DRY, code I could come up with at the spur of the moment

const circles = [...document.querySelectorAll('div.circle')];

circles.forEach(el => {
  el.addEventListener('mouseover', function(e) { // use function not arrow so this is current element
    const color = this.dataset.color;
    circles.some(x => {
      x.style.backgroundColor = color;
      return x === this;
    });
  });
  el.addEventListener('mouseout', function(e) { // use function not arrow so this is current element
    circles.some(x => {
      x.style.backgroundColor = 'white';
      return x === this;
    });
  });
})
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

section {
  margin: 100px auto 0 auto;
  max-width: 700px;
  display: grid;
  grid-template-columns: 1fr 1fr 1fr;
}

section .circle {
  width: 200px;
  height: 200px;
  border-radius: 50%;
  border: 1px solid #333;
  margin: 0 auto;
  cursor: pointer;
}
<section>
  <div id="circleOne" class="circle" data-color="red"></div>
  <div id="circleTwo" class="circle" data-color="green"></div>
  <div id="circleThree" class="circle" data-color="blue"></div>
</section>

Upvotes: 2

Related Questions