As you may know that box-shadow is not a part of box-model. so what could be a good way to compute the width of box-shadow that adds to an element?
Update: I need to know the total width of an element, including the shadow width.
well you could simply add a margin equal to the box-shadow. For example:
box-shadow: 0 0 10px #008800;
margin: 10px;
in the case you use the X and Y offsets on the box-shadow use add that value to the length of the shadow. Example:
box-shadow: 5px 5px 10px #080;
margin: 5px 15px 15px 5px;
here the offset is 5px, plus the 10px length. In the case of the spread we can continue to add to the margin values to take this into consideration.
box-shadow: 5px 5px 10px 7px #080;
margin: 12px 21px 21px 12px;
using the margin will keep the shadow from overlapping other objects on the page.
Exact width will differ from browser to browser. Each renders the shadows different. If i have to give a hard calculation for the object I guess it would be the something like this (the css property for reference)
box-shadow: h-shadow v-shadow blur spread color;
The box model offsets would be
top = (spread - v_shadow + 0.5*blur)
right = (spread + h_shadow + 0.5*blur)
bottom = (spread + v_shadow + 0.5*blur)
left = (spread - h_shadow + 0.5*blur)
The coefficient of the blur is a estimate, it may need to be adjusted slightly. Personally I prefer to not use the offset, but is here to show where it would be used
here is a jsfiddle to see it in action http://jsfiddle.net/YvqZV/4/
Just extending #samuel.molinski's answer by creating a complete function that takes a box shadow and returns the widths.
function getBoxShadowWidths(boxShadow) {
// not supporting multiple box shadow declarations for now
if ((boxShadow.match(/(rgb|#)/g) || []).length > 1) {
return false;
}
const regEx = /(\d(?=(px|\s)))/g;
const matches = [];
// box-shadow can have anywhere from 2-4 values, including horizontal offset, vertical offset,
// blur, and spread. Below finds each one and pushes it into an array (regEx.exec when used in succession
// with a global regex will find each match.
let match = regEx.exec(boxShadow);
while (match != null) {
matches.push(match[0]);
match = regEx.exec(boxShadow);
}
// default blur & spread to zero px if not found by the regex
const [hOffset = 0, vOffset = 0, blur = 0, spread = 0] = matches.map(parseFloat);
// calculate approximate widths by the distance taken up by each side of the box shadow after normalizing
// the offsets with the spread and accounting for the added distance resulting from the blur
// See https://msdn.microsoft.com/en-us/hh867550.aspx - "the blurring effect should approximate the
// Gaussian blur with a standard deviation equal to HALF of the blur radius"
const top = spread - vOffset + 0.5 * blur;
const right = spread + hOffset + 0.5 * blur;
const bottom = spread + vOffset + 0.5 * blur;
const left = spread - hOffset + 0.5 * blur;
return { top, right, bottom, left };
}
Thanks #Joey for the function. I added support for multiple values:
function getBoxShadowWidths(boxShadowValues) {
const regEx = /(\d(?=(px|\s)))/g
const widths = { top: 0, right: 0, bottom: 0, left: 0 }
boxShadowValues.split(/\s*,\s*/).forEach(boxShadowValue => {
const matches = []
// box-shadow can have anywhere from 2-4 values, including horizontal offset, vertical offset, blur, and spread.
// Below finds each one and pushes it into an array (regEx.exec when used in succession with a global regex will find each match.
let match = regEx.exec(boxShadowValue)
while (match != null) {
matches.push(match[0])
match = regEx.exec(boxShadowValue)
}
// default blur & spread to zero px if not found by the regex
const [hOffset = 0, vOffset = 0, blur = 0, spread = 0] = matches.map(parseFloat)
// calculate approximate widths by the distance taken up by each side of the box shadow after normalizing
// the offsets with the spread and accounting for the added distance resulting from the blur
// See https://msdn.microsoft.com/en-us/hh867550.aspx - "the blurring effect should approximate the
// Gaussian blur with a standard deviation equal to HALF of the blur radius"
const actualWidths = {
top: spread - vOffset + 0.5 * blur,
right: spread + hOffset + 0.5 * blur,
bottom: spread + vOffset + 0.5 * blur,
left: spread - hOffset + 0.5 * blur,
}
Object.keys(actualWidths).forEach(side => {
widths[side] = Math.max(widths[side], actualWidths[side])
})
})
return widths
}
Related
I am trying to render beautiful pages transitions in my react application. I chose simple fade-in/fade-out transitions, and they work perfectly for random pages. But it happens that some pages are quite identical (just a text change), and in that case the transition is not so good (see code-pen bellow for a concrete example).
The way I understand mathematics, if I have two layers, let's say A and B, a linear fade-out of A starting above a similar linear fade-in of B should give constant pixels colors for pixels of the exact same color in both layers, isn't it? But as you can see in that codepen, it's not the case (i was hoping to have no visual effect as the two layers have the exact same color), and I don't understand why. Is there a way to obtain the desired result (for a given pixel, color B replace color A if they are different, but stay still if they are the same)?
https://codepen.io/st-phane-smirnow/pen/dyJaBPG
And here is the corresponding code - HTML:
<div class="a1">a1 div</div>
<div class="a2 hidden">a2 div</div>
<button>Switch divs<button>
CSS:
.a1, .a2 {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 0;
left: 0;
width: 100vw;
height: 100vh;
background-color: red;
transition: all .5s linear;
}
.hidden { opacity: 0; }
button {
position: absolute;
top: 10px;
left: 10px;
}
JS:
window.addEventListener("load", () => {
document.querySelector("button").addEventListener("click", () => {
document
.querySelectorAll("div")
.forEach(d => d.classList.toggle("hidden"));
});
});
EDIT: I partially solved my problem by disabling the transparency of the underlying layer. That way, the application background, whatever it is, is hidden by the second layer (opacity always to 1), and the foremost layer just fade-in the underlaying layer. I don't know if, in a perfect world, that result is really different of what I would have got of a truly functional cross-fade, but at least it give a good result at screen.
It's because during the transition, they are both transparent so you're seeing the white background through them.
Here's an interesting post that shows some formulas that might help to why the resulting color isn't rgb(255,0,0) all the way through. Basically, some of the green and blue channel are being introduced.
It's reasonable to think that a 90% red + a 10% red would be a 100% red, but actually each layer is letting a little bit of 'white light' through. Here's how:
According to the post, the general formula for a 2 layer image is:
Y = p*T+(1-p)*B where...
p is the opacity 0...1 of the top layer,
T = rgb number of top layer color,
B is the rgb number of fully opaque bottom layer.
Y is the rgb number of the equivalent fully opaque color.
So, say you have rgba(255, 0, 0, 0.9) on a white background. The resulting color is:
r = 0.9 * 255 + 0.1 * 255 = 255
g = 0.9 * 0 + 0.1 * 255 = 25.5
b = 0.9 * 0 + 0.1 * 255 = 25.5
Now let's add a rgba(255, 0, 0, 0.1) on top of that result:
r = 0.1 * 255 + 0.9 * 255 = 255
g = 0.1 * 0 + 0.9 * 25.5 = 22.9
b = 0.1 * 0 + 0.9 * 25.5 = 22.9
The extra green and blue channels are moving the color towards white.
How to get CSS text margin/padding from the Photoshop?
or
How to convert the distance from/to the text in Photoshop into CSS margin/padding?
Distances from text elements (paragraphs) in Photoshop do not correspond to margins/paddings in the CSS. Distances are measured, for example, using smart guides:
All because the line height is not used in the distances calculation. Therefore, the first recommendation I found is to use the formula:
margin_in_CSS = distance_in_PS - (line-height - font-size) / 2
or shorter:
CSS = PS - (line-height - font-size) / 2
This is the distance from some obvious border (line) to the text element. For the distance between two paragraphs we use, respectively:
CSS = PS - (line-height_1 - font-size_1) / 2 - (line-height_2 - font-size_2) / 2
As the font size increases, it becomes clear that this formula is not enough. The actual height of the line (obtained with the selection tool) in Photoshop is even less than the font size!
Although the photoshop still considers the height of the element to be approximately equal to the font size, which does not affect the distance to it :(. For example, on the Properties tab:
I calculated that the difference between the real height of the line and the font size is about 30% or 15% at the top and bottom of the text (I'm not saying this is 100% true!). And now I use the formula:
CSS = PS - (0.15 * font-size + (line-height - font-size) / 2)
Or between two paragraphs:
CSS = PS - (0.15 * font-size_1 + (line-height_1 - font-size_1) / 2)
- (0.15 * font-size_2 + (line-height_2 - font-size_2) / 2)
Similarly, we can not rely on the correct definition of the height of a paragraph in several lines by Photoshop. But here the situation is simpler, the real height of the paragraph in the CSS will be:
height = line-height * num_of_lines
The question is, is there a simpler way? О_о
Sorry for my English ^_^
UPDATE, shorter formulas:
text <> border
CSS = PS - (line-height - 0.7 * font-size) / 2
text <> text
CSS = PS - (line-height_1 - 0.7 * font-size_1) / 2
- (line-height_2 - 0.7 * font-size_2) / 2
UPDATE:
Now a script is being developed for the correct calculation of distances on the Adobe forum (link). At the moment, the script can calculate the distance from the bounding box of the text line with a standard (auto) line-height of 120%.
UPDATE:
It does not matter if you use a pointed text or a paragraph text, the result bounding box height is not equal to the text line-height (leading)
How to convert the distance from/to the text in Photoshop into CSS margin/padding?
The actual resulting glyph(s) (pink border in your image) of your text will have different height with the following contents:
"
[empty space] = no glyph at all
...
a
A
Qq
q
Margins and paddings should not be measured from the text itself, but from the boundaries of text line (or line-height in CSS).
In the above example:
65px is the actual height of text line (or line-height in CSS), (the distance from two text baselines when the text wraps) and what is used when calculating margin/padding. The end result being that no matter the contents of your text element, the distance from its baseline to the element following it should remain the same, based on line-height, (bottom) margin and (bottom) padding (and, of course, on the top margin and padding of next element).
To answer your question in a nutshell, PS does not apply a reduction to margins. It's just they are not calculated from the bounding box of the text glyphs (which might vary depending on contents), but from the bounding box of text line.
Another thing to consider when converting from .psd to HTML is that in HTML you have collapsing margins. In short, from two vertical adjacent margins only the largest one will be kept. If the other one is negative, it will be deducted from the positive one and if both are negative, the one with the largest value will be applied.
Finally, the script for measuring vertical distance is finished!
It can correctly calculate the vertical distance for CSS between layers, one of which or both are text layers.
Here's the link on Adobe Forums - A script for measuring the distance between two elements?
// version no CS6 or no effects
var old_units = app.preferences.rulerUnits;
app.preferences.rulerUnits = Units.PIXELS;
try { app.activeDocument.suspendHistory("Get Text Bounds", "var bounds = get_selected_layers_bounds()") } catch(e) { alert(e); }
try { executeAction( charIDToTypeID( "undo" ), undefined, DialogModes.NO ); } catch(e) { alert(e); }
app.preferences.rulerUnits = old_units;
if (bounds)
{
if (bounds.length == 2)
{
var distance = 0;
if (bounds[0].bottom <= bounds[1].top) distance = bounds[1].top - bounds[0].bottom;
else if (bounds[1].bottom <= bounds[0].top) distance = bounds[0].top - bounds[1].bottom;
else alert("Intersecting layers")
var distance_in_css = distance - (bounds[0].leading - 1.2*bounds[0].size)/2 - (bounds[1].leading - 1.2*bounds[1].size)/2;
alert("distance = " + distance + "\ndistance_in_css = " + distance_in_css);
}
else
alert("More then 2 selected layers")
}
else
alert("There is no selected layers")
/////////////////////////////////////////////////////////////////////////////////////////////////
function get_selected_layers_bounds()
{
try {
var ref = new ActionReference();
ref.putProperty( charIDToTypeID( "Prpr" ), stringIDToTypeID( "targetLayers" ) );
ref.putEnumerated( charIDToTypeID("Dcmn"), charIDToTypeID("Ordn"), charIDToTypeID("Trgt") );
var desc = executeActionGet(ref);
if (!desc.hasKey( stringIDToTypeID("targetLayers") ) ) return null;
var n = 0;
try { activeDocument.backgroundLayer } catch (e) { n = 1; }
desc = desc.getList( stringIDToTypeID("targetLayers"));
var len = desc.count;
var selected_bounds = new Array();
for (var i = 0; i < len; i++)
{
try
{
var r = new ActionReference();
r.putIndex( charIDToTypeID( "Lyr " ), desc.getReference(i).getIndex() + n);
var ret = executeActionGet(r);
var size = 0;
var leading = 0;
if (ret.hasKey(stringIDToTypeID("textKey")))
{
var textStyleRangeList = ret.getObjectValue(stringIDToTypeID("textKey")).getList(charIDToTypeID("Txtt" ));
if (textStyleRangeList.count > 1) { alert("More than one textStyleRange in layer", "Oops!!"); }
var textStyle = textStyleRangeList.getObjectValue(0).getObjectValue(charIDToTypeID("TxtS" ));
var auto_leading = textStyle.getBoolean(stringIDToTypeID("autoLeading"));
size = textStyle.getUnitDoubleValue(stringIDToTypeID("size"));
leading = auto_leading?size*1.2:textStyle.getUnitDoubleValue(stringIDToTypeID("leading"));
var s = ret.getObjectValue(stringIDToTypeID("textKey")).getString(charIDToTypeID("Txt " ));
s = s.replace(/^./gm, String.fromCharCode(0x2588));
var d1 = new ActionDescriptor();
d1.putReference( charIDToTypeID( "null" ), r );
var d2 = new ActionDescriptor();
d2.putString( charIDToTypeID( "Txt " ), s);
d1.putObject( charIDToTypeID( "T " ), charIDToTypeID( "TxLr" ), d2 );
executeAction( charIDToTypeID( "setd" ), d1, DialogModes.NO );
ret = executeActionGet(r);
}
// var bounds = ret.getObjectValue(stringIDToTypeID("bounds")); // use this in CS6 or when you want to take into account the effects
var bounds = ret.getObjectValue(stringIDToTypeID("boundsNoEffects")); // in CS6 does not work
var obj = {
left : bounds.getUnitDoubleValue(stringIDToTypeID("left")),
top : bounds.getUnitDoubleValue(stringIDToTypeID("top")),
right : bounds.getUnitDoubleValue(stringIDToTypeID("right")),
bottom : bounds.getUnitDoubleValue(stringIDToTypeID("bottom")),
size : size,
leading: leading,
};
selected_bounds.push(obj);
}
catch (e) { alert(e); return null; }
}
return selected_bounds;
}
catch (e) { alert(e); return null; }
}
The script should be saved as a *.js or *.jsx file (for example, distance.js) in the Photoshop folder - C:\Program Files\Adobe\Adobe Photoshop CC 2017\Presets\Scripts
It will be available in the Photoshop menu - File > Scripts > Distance
it does not matter , psd is for showing how the website looks on completion you have to take in consideration the font size , for e.g. for paragraph text if the font size is 14 pt in psd and the grid is 1200px (bootstrap) than you have to convert the font in aspect ratio of browser ( default 16px now in bootstrap ) and calculate accordingly like 14pt in psd is equals to 14px + (14 * 0.16%)px in browser and everything else accordingly , similar for line height.
also if you want to set font size same as psd its up to you select 14px for html if our psd font size is 14pt for paragraph.
I have to draw a bow object in a canvas (2d). The box has an external shadow specified as css definition
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.15)
I don't know how to convert this to the canvas way of defining shadows, using shadowOffsetX/Y, shadowColor and shadowBlur.
If I look at the shadowBlur spec, it's explicitely not related to pixels, but it only says "it's a parameter for a gaussian blur effect" (paraphrasing). Actually, I find this to be under-specified.
Would a better approximation using a gradient to transparent instead ? But then won't it miss the blurring effect ?
Now there are filters which are similar to the CSS filters properties and accepts the same functions. Although it is still a experimental technology.
// create canvas
const canvas = document.createElement("canvas");
canvas.width = 500;
canvas.height = 500;
// set context
const context = canvas.getContext("2d");
context.fillStyle = '#ff0';
context.rect(50, 50, 100, 100);
// set shadow filter
let offsetX = 20;
let offsetY = 0;
let blurRadius = 5;
let color = 'rgba(255, 0, 0, 0.8)';
context.filter = 'drop-shadow('+ offsetX + 'px ' + offsetY + 'px ' + blurRadius + 'px ' + color + ')';
context.fill();
document.body.appendChild(canvas);
The shadow blur is in pixels, and only supports pixel dimensions. A blur of 0 has a sharp edge, 1 is a blur of one pixel, 2 two pixels and so on. It is not affected by the 2d API transformation. Shadow spread is not supported by the canvas 2D API.
BTW values should be qualified. I have added px where you forgot them in the CSS.
So CSS
box-shadow:0px 1px 2px 0px rgba(0, 0, 0, 0.15);
becomes
ctx.shadowBlur = 2;
ctx.shadowColor = "rgba(0, 0, 0, 0.15)";
ctx.shadowOffsetX = 0;
ctx.shadowOffsetY = 1;
I have a canvas which is currently drawing a grey scale diagram (JSBin example).
It's effectively a radial progress meter, that will be used a lot in the application. However, rather than colouring it with Javascript, I'd prefer to be able to give it a colour based on a class.
I thought it would be an ideal use case for CSS filters. I'd draw the default progress meter in gray, then use CSS filters to add saturation and do a hue rotation, in order to achieve blue, orange and green too.
canvas {
-webkit-filter: saturate(8);
}
The rule was supported and valid in Chrome, but the problem was, it doesn't seem to change the saturation at all.
I'm imagining that #aaa is transformed into it's HSL counterpart hsl(0, 0%, 67%). Then when I increase the saturation with a filter, it should become more saturated, but for the same hue.
I was hoping to end up with something like hsl(0, 50%, 67%) but instead, the filter doesn't seem to change the colour at all, no matter what value I use.
Any ideas?
It turns out if you draw the meter with some saturation initially, you can use the hue-rotate filter and then you can desaturate them to achieve grey scale again.
http://jsbin.com/qohokivobo/2/edit?html,css,output
Conceptually, this isn't an answer. But in the meantime, it's a solution.
What about picking the color from the CSS style ?
canvas {
color: red;
}
function init() {
let canvas = document.getElementById('test'),
context = canvas.getContext('2d');
let style = window.getComputedStyle (canvas);
let color = style.color;
canvas.width = 300;
canvas.height = 300;
let x = canvas.width / 2,
y = canvas.height / 2;
context.beginPath( );
context.arc(x, y, 100, 0, 2 * Math.PI, false);
context.strokeStyle = color;
context.lineWidth = 20;
context.stroke();
context.globalAlpha = 0.85;
context.beginPath();
context.arc(x, y, 100, 0, Math.PI + 0.3, false);
context.strokeStyle = '#eee';
context.stroke();
}
demo
I'm trying to emulate the CTRL+F functionality from Chrome that highlights matches on the page in the scrollbar, but for certain fields in a form. Using page offsets and percentages, I have blocks of color which correspond to the relative locations of those fields on the page.
In my prototype, the blocks of color sit to the left of the scrollbar. Ideally, they'd sit UNDERNEATH the scrollbar, and the scrollbar's track would be transparent so that it looks like they're part of the scrollbar track.
Can the default scrollbar be set to allow for overflow content to show underneath it (or allow page content to go over it)? I know this could be accomplished if I just rolled my own scroll, but I'd like to use the default ones provided by the browser if at all possible.
It's clearest if you just look at this Prototype.
CSS:
::-webkit-scrollbar {
width: 14px;
height: 18px;
background-color:transparent;
}
::-webkit-scrollbar-track,
::-webkit-scrollbar-track-piece {
background:none;
}
::-webkit-scrollbar-thumb {
height: 6px;
border: 4px solid rgba(0, 0, 0, 0);
background-clip: padding-box;
-webkit-border-radius: 7px;
background-color: #333
}
::-webkit-scrollbar-button {
width: 0;
height: 0;
display: none;
}
::-webkit-scrollbar-corner {
background-color: transparent;
}
I thought of rendering the matches on the trackbar similarly to what browsers do today before. The idea is simple by using linear-gradient background for the ::-webkit-scrollbar-track. However I did not try implementing this. Right after reading your question, I've tried it and looks like it's not such easy.
You can use the linear-gradient background OK, but if you try rendering more than 1 match (a line), it sometimes can't be rendered (especially when the window's size is changed) and the line is not rendered smoothly. Such as this seems to be OK:
//render 2 lines, one is at 50px and the other is at 100px
background: linear-gradient(transparent 50px, red 50px, red 51px, transparent 51px,
transparent 100px, red 100px, red 101px, transparent 101px);
but it's not stable, as I said when you try resizing the window, at some size, some line won't be rendered (at least I tried on Opera). When the window's height is large, the line even becomes blurred (not sharp) and thicker. I don't really understand this, because the color stops are set fixedly (by px, not by %). This issue is even worse when the number of lines is larger. You have a linear-gradient with many corresponding color stops. That seems to be a neat way to solve the problem. Just because of the undesired issue, we can't use that approach.
The new approach: So I tried using multi-backgrounds feature instead. Each background just renders 1 line, the background-size is the same for all the background is just about 2px height and the background-position should be different. Here is the equivalent code (to the above clean code) using this approach:
background: linear-gradient(red, red), linear-gradient(red, red);
background-repeat: no-repeat;
background-size: 100% 2px;
background-position: 0 50px, 0 100px;
The new approach of course requires that the browser has to support multi-backgrounds features (looks like just IE8- do not support this cool feature).
So that's almost what you need to solve this problem. Now we need to find how to apply that style using script. We can't select a pseudo-element (or something like that) via script. We can just use the window.getComputedStyle() method to get the read-only style of a pseudo-element. However we always have a way to modify the CSS directly. That's is by using pure JS with the help of document.styleSheets and cssRules. They allow us to insert/remove/modify a rule.
That looks great. But there is still another issue. When changing the style using that method, the style is not applied right (at least it happens to the ::-webkit-scrollbar-track, it may not happen to other elements). Only when you move the mouse over the scrollbar, the new style is applied. I've just found a simple way to invalidate that scrollbar by setting the overflow of document.documentElement (the html) to hidden and set it back to auto. That works almost well.
Here is the code:
var requiredTb = $(".required input");
var invalids = requiredTb;
var offsets = [];
//init offsets to highlight on the trackbar later
requiredTb.each(function() {
offsets.push(($(this).offset().top)/document.body.scrollHeight * 100);
});
//find the rule styling the -webkit-scrollbar-track
//we added in the CSS stylesheet, this is done just 1 time
var sheets = document.styleSheets;
var trackRule;
for(var i = 0; i < sheets.length; i++){
var rules = sheets[i].cssRules || sheets[i].rules;
for(var j = 0; j < rules.length; j++){
var rule = rules[j];
if(rule.selectorText == "::-webkit-scrollbar-track:vertical"){
trackRule = rule; break;
}
}
}
//define an invalidate() method, we need to use this method
//to refresh the scrollbars, otherwise the newly applied style is not affected
window.invalidate = function(){
$(document.documentElement).css('overflow','hidden');
setTimeout(function(e){
$(document.documentElement).css('overflow','auto');
},1);
};
//this is the main function to set style for the scrollbar track.
function setTrackHighlights(positions, color){
positions.sort();//ensure that the input array should be ascendingly sorted.
trackRule.style.cssText = "";
var gradient = "background: ", backPos = "background-position: ";
var winHeight = $(window).height();
$.each(positions, function(i,pos){
gradient += "linear-gradient(" + color + ", " + color + "),";
backPos += "0 " + pos + "%,"
});
gradient = gradient.substr(0,gradient.length-1) + ";";
backPos = backPos.substr(0,backPos.length -1) + ";";
trackRule.style.cssText += gradient + backPos + "background-repeat:no-repeat; background-size:100% 2px";
invalidate();
}
//initially set the highlights on the trackbar
setTrackHighlights(offsets,'red');
//handle the oninput event to update the highlights accordingly
requiredTb.on('input', function(e){
var required = $(this).closest('.required');
var refreshHighlights = false;
if(this.value && !required.is('.ok')) {
required.addClass('ok');
refreshHighlights = true;
invalids = invalids.not(this);
}
if(!this.value && required.is('.ok')) {
required.removeClass('ok');
refreshHighlights = true;
invalids = invalids.add(this);
}
if(refreshHighlights){
offsets.splice(0);
invalids.each(function() {
offsets.push(($(this).offset().top)/document.body.scrollHeight * 100);
});
setTrackHighlights(offsets,'red');
}
});
You have to add an empty ::-webkit-scrollbar-track:vertical rule (we need to deal only with the vertical scrollbar) in the CSS code, it should be appended at the last to override any similar rule before. We can in fact use the insertRule() method (of a CSSRuleList which can be accessed via cssRules property) to add a new rule instead of looping through the styleSheets, and through the cssRules (of each sheet) to find the empty rule ::-webkit-scrollbar-track:vertical.
The code I posted here can be improved, such as you can add another method setTrackHighlights to allow to add more lines (instead of rendering all the lines each time we need to add/remove just 1 line)...
Note that by using the term line, I mean the rendering representation of a match on the trackbar.
Demo