Tomáš Zato
Tomáš Zato

Reputation: 53236

Make foreground transparent, but not the background

I'm experimenting a little bit, and I've set the following challenge, which I now cannot solve. I'm making user script for Flyspray bug tracker. In a list of issues, there's a column indicating percent complete:

enter image description here

The system is rather old, which gives it a certain beauty of simplicity. This is the HTML for the progress:

<td class="task_progress">
    <img src="/themes/Bluey/percent-70.png" alt="70%">
</td>

Clean and simple. Now what I'd like to do is to move the progress on mouse move above the <img>, without changes to HTML. This could be done if I could make the ` foreground semi-transparent and set a CSS gradient background.

I could solve this by wrapping the <img> in <span>, but it's very clumsy.

const wrapped = document.querySelector("#wrapped");
const img = wrapped.querySelector("img");
const box = img.getBoundingClientRect();
//wrapped.style.height = (box.bottom-box.top)+"px";

const bg = "linear-gradient(to right, rgba(0,38,114,1) 0%,rgba(0,38,114,1) 50%,rgba(0,38,114,0) 51%,rgba(0,38,114,0) 100%)";
console.log(wrapped);
wrapped.addEventListener("mousemove", (e)=>{
    const box = wrapped.getBoundingClientRect();
    const mousePos = e.clientX - box.left;
    const max = box.right-box.left;
    const perc = 100*(mousePos/max);
    
    const gradient = bg
               .replace("50%", (perc-0.5)+"%")
               .replace("51%", (perc+0.5)+"%");
    //console.log(gradient);
    ///console.log(perc);
    wrapped.style.backgroundImage = gradient;
    wrapped.style.color = "red";
}, {capture: false});
#wrapped {
   display: inline-block;
   margin: 0px;
   padding: -1px;
   border-width: 1px;
   border-color:transparent;
   border-style: solid;
   
   position: relative;
   
   background-position: center center;
   
   background-repeat: no-repeat;
   
   background-size: 0px 0px;
}
#wrapped img {
   margin: 0px;
   padding: 0px;
   
   position: relative;
   
   top:0px;
   left: 0px;
}
#wrapped:hover img {
    opacity: 0.3;
}
#wrapped:hover {
    /*border-color:#002672;*/
    background-size: 100% 8.82px;
}
<span id="wrapped"><img src="https://i.sstatic.net/lymku.png" /></span>

The main problem with the above solution: The wrapper's height does not match the <img> height, which means that background dimensions had to be set exactly to a pixel.

enter image description here

Is there a way to do this without any wrapper element?

Please note that this is an exercise/learning kind of question, solutions that solve the entire thing through other means are of no use to me.

Upvotes: 3

Views: 245

Answers (1)

Temani Afif
Temani Afif

Reputation: 273530

Don't use gradient with transparent color. Use only the solid color and control the background-size then simply make the img block element to avoid the whitespace issue:

Not sure if it's possible without a wrapper because you will need an element where you have to apply the gradient:

const wrapped = document.querySelector("#wrapped");
const img = wrapped.querySelector("img");
const box = img.getBoundingClientRect();
//wrapped.style.height = (box.bottom-box.top)+"px";

const bg = "linear-gradient(rgba(0,38,114,1),rgba(0,38,114,1))";
console.log(wrapped);
wrapped.addEventListener("mousemove", (e)=>{
    const box = wrapped.getBoundingClientRect();
    const mousePos = e.clientX - box.left;
    const max = box.right-box.left;
    const perc = 100*(mousePos/max);

    //console.log(gradient);
    ///console.log(perc);
    wrapped.style.backgroundSize =perc+"% 100%";
    wrapped.style.color = "red";
}, {capture: false});
#wrapped {
   display: inline-block;
   margin: 0px;
   /*padding: -1px; there is no negative padding */
   border-width: 1px;
   border-color:transparent;
   border-style: solid;
   
   background-position:left;
   background-repeat: no-repeat;
}
#wrapped img {
   display:block;
}
#wrapped:hover img {
    opacity: 0.3;
}
#wrapped:hover {
    /*border-color:#002672;*/
    background-image:linear-gradient(rgba(0,38,114,1),rgba(0,38,114,1));
    background-size: 50% 100%;
}
<span id="wrapped"><img src="https://i.sstatic.net/lymku.png" /></span>

But you can probably consider an idea with multiple background to avoid the image and have one element at the end that will replace the image:

const wrapped = document.querySelector("#wrapped");


wrapped.addEventListener("mousemove", (e)=>{
    const box = wrapped.getBoundingClientRect();
    const mousePos = e.clientX - box.left;
    const max = box.right-box.left;
    const perc = 100*(mousePos/max);

    wrapped.style.backgroundSize =perc+"% 100%, 60% 100%";
}, {capture: false});
#wrapped {
   display: inline-block;
   border-width: 1px;
   border-style: solid;
   border-color:rgba(0,38,114,1);
   height:10px;
   width:100px;
    background-image:
      linear-gradient(transparent,transparent),
      linear-gradient(rgba(0,38,114,1),rgba(0,38,114,1));
    background-size: 60% 100%;
   background-position:left;
   background-repeat: no-repeat;
}
#wrapped:hover {
   border-color:rgba(0,38,114,0.5);
    background-image:
      linear-gradient(rgba(0,38,114,1),rgba(0,38,114,1)),
      linear-gradient(rgba(0,38,114,0.5),rgba(0,38,114,0.5));
}
<span id="wrapped"></span>

Upvotes: 3

Related Questions