3
votes

Basically I have a div with a comma-separated list of box-shadows like in the appended code snippet and I need to change the color of an individual "shadow block" when hovering it.

Desired outcome

I searched for ways on how to find out if a specific rendered CSS property is hovered but the only useful thing I found was a similar question on how to detect if the border of a cell is hovered. Here the answer seems clear: you have the position of the hovered cell and it's border-width, so check the offset in four different directions. I wasn't able to transfer this principle to the (comma-separated) box-shadow property.

Note that I don't want to replicate the positions of the box-shadows in Javascript. The solution should still work if I change the positions of individual shadows in the CSS. If required you can assume that the width and height of the div does not change. Any clever ideas on this? Bonus task would be to color the hovered shadow block as shown in the above image, but a solution that logs a message to the console if any of the box-shadows is hovered would already be a useful step.

.box-with-shadow {
  display: block;
  width: 10px;
  height: 10px;
  box-shadow: 
		100px 130px #000,
		90px 140px #000,
		100px 140px #000,
		110px 140px #000,
		80px 150px #000,
		90px 150px #000,
		110px 150px #000,
		120px 150px #000,
		90px 160px #000,
		100px 160px #000,
		110px 160px #000,
		100px 170px #000;
}
<div class="box-with-shadow">
</div>
1
Why does it have to be multiple shadows on a single element? Doing with elements instead of shadows would make it quite simpler.marekful
Good point. To be honest there is no need to do it with box-shadows and my only concerns are that it would be a less clean setup (probably putting a large number of spans in the div and accessing them by nth-child in CSS then). For the sake of learning I would be interested in an algorithm solving this specific problem anyway.Marvin
I would be very surprised if this can be donevals

1 Answers

1
votes

As correctly pointed out by marekful the easiest way to achieve the desired effect would be to use multiple HTML elements instead of multiple shadows. However, I will answer the question with a trivial solution that works for the described setup and with variable box-shadows. It is not robust enough to solve some special cases like e.g. a rotation of the div.

The idea is to calculate the positions of the shadows once in the beginning (assuming that nothing moves or changes, otherwise we have to recalculate the positions multiple times). On mouse movement I check if the position of the mouse is in any of the intervals, which are defined by the position of a shadow plus its dimension.

It's been a while since I used Javascript, so there might be the chance that some parts can be simplified, but at least it works. Feel free to play around with the JSFiddle.

var boxWidth = parseInt($("#box-with-shadow").css('width'), 10);
var boxHeight = parseInt($("#box-with-shadow").css('height'), 10);
var boxOffsetX = parseInt($("#box-with-shadow").css('margin-left'), 10);
var boxOffsetY = parseInt($("#box-with-shadow").css('margin-top'), 10);
var boxShadowString = $('#box-with-shadow').css('box-shadow');
var boxShadows = boxShadowString.split(/,(?![^\(]*\))/);

// key: x-pos of the shadow, value: concatenated y-positions, separated by commas
var keyValuePairs = fillKeyValuePairs();

var cursorX;
var cursorY;

document.onmousemove = function(e) {
  cursorX = e.pageX;
  cursorY = e.pageY;
  checkCursor();
}

function checkCursor() {
  var shadowHovered = false;
  for (i = cursorX - boxWidth; i <= cursorX && !shadowHovered; i++) {
    if (keyValuePairs[i] != null) {
      // At this point we know that somewhere on this x-position there is a shadow.
      // Now check if there is an associated y-interval for the found x-position
      for (j = cursorY - boxHeight; j <= cursorY; j++) {
        if ((keyValuePairs[i] + "").split(",").indexOf(j + "") != -1) {
          shadowHovered = true;
          break;
        }
      }
    }
  }
  if (shadowHovered) {
    $("#status").css("background", "green");
    $("#status").text("Found shadow: xOffset = " + (i - 1) + "px, yOffset = " + j + "px");
  } else {
    $("#status").css("background", "red");
    $("#status").text("");
  }
}

function fillKeyValuePairs() {
  var keyValuePairs = [];

  for (index = 0; index < boxShadows.length; index++) {
    var xPos = parseInt(boxShadows[index].trim().match(/[0-9]+px/g)[0], 10) + boxOffsetX;
    var yPos = parseInt(boxShadows[index].trim().match(/[0-9]+px/g)[1], 10) + boxOffsetY;
    keyValuePairs[xPos] = keyValuePairs[xPos] != null ? keyValuePairs[xPos] + "," + yPos : yPos;
  }

  return keyValuePairs;
}
body {
  margin: 0;
  padding: 0;
}
#box-with-shadow {
  display: block;
  width: 10px;
  height: 10px;
  box-shadow: 100px 130px #000, 90px 140px #000, 100px 140px #000, 110px 140px #000, 80px 150px #000, 90px 150px #000, 110px 150px #000, 120px 150px #000, 90px 160px #000, 100px 160px #000, 110px 160px #000, 100px 170px #000;
}
#status {
  width: 100%;
  height: 2em;
  background: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>

<div id="box-with-shadow">
</div>

<div id="status">
</div>