Reputation: 14153
I have used Visual Studio Online for a while for a project, and the way they apply rounded borders to selections in their online code viewer is very interesting:
I've tried inspecting the element and looking for some kind of custom CSS, but had no luck.
I have a feeling this requires some complex "hacks" to make it work, but it seems very interesting as I've never seen it done before.
How are they able to apply rounded borders to a selection?
Note: The normal selection is completely hidden WHILE selecting, and the rounded selection follows your cursor just like a regular selection. Not AFTER you have selected something.
Edit: I have created a fork of @Coma's answer that should work in Firefox and select while the mouse if moving using:
$(document).on('mousemove', function () {
(The borders in certain cases could still use work.)
Upvotes: 34
Views: 19438
Reputation: 174
Full explanation: https://css-tricks.com/gooey-effect/
Live demo: https://codepen.io/ines/pen/NXbmRO
<h1>
<div class="highlight" contenteditable="true">This is an example of a simple headline or text with rounded corners using<br>a gooey SVG filter.</div>
</h1>
<!-- Create the SVG Filter: https://css-tricks.com/gooey-effect/ -->
<svg style="visibility: hidden; position: absolute;" width="0" height="0" xmlns="http://www.w3.org/2000/svg" version="1.1">
<defs>
<filter id="goo"><feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur" />
<feColorMatrix in="blur" mode="matrix" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 19 -9" result="goo" />
<feComposite in="SourceGraphic" in2="goo" operator="atop"/>
</filter>
</defs>
</svg>
:root {
--color-bg: #34304c;
--color-bg2: #534d7a;
--color-highlight: #fff;
--font: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
}
.highlight {
filter: url('#goo'); /* Apply the SVG filter*/
font-size: 3rem;
line-height: 1.48;
display: inline;
box-decoration-break: clone;
background: var(--color-highlight);
padding: 0.5rem 1rem;
}
.highlight:focus {
outline: 0;
}
body {
padding: 7.5vh 100px 0 100px;
font-family: var(--font);
background: var(--color-bg);
}
Upvotes: 4
Reputation: 125
I fond also a solution which you might like. It basically works with spans around each word which you then can apply a border-radius. But I don't know how to archive the corner above - so it's only horizontal connected.
p.introduction {
width: 150px;
}
p.introduction span {
background-color: #f48024;
color: #1d1d1e;
border-radius: 25px;
float: left;
padding: 0 15px 0 10px;
margin: 4px -15px 4px 0px;
}
<p class="introduction"><span>Be</span><span>part</span><span>of</span><span>our</span><span>awesome</span><span>community</span><span>and</span><span>have</span><span>fun</span><span>with</span><span>us.</span></p>
Upvotes: 2
Reputation: 1739
I can assure you this has nothing to do with html, css border radius or highlighting. The proof?
Summary, they must be using the Canvas property and whole lot of codes to 'underlay' an interactive selection procedure. There are numerous different types of highlighting appearing in the editor, like 'same word highlighting', 'selected highlighting', 'out of focus highlighting', etc... For all these to happen efficiently, I can't find a better alternative than canvas.
Don't be mad at me for posting this. But I didn't want to see my 4 hours of research as a waste. At least I got an observation and that's that.
UPDATE :
Though I thought covering the selection using a white rectangle with a border-radius at the end, is a rather inefficient and unnecessary way. Microsoft doesn't think so.
They are using the curved edged rectangles to cover up the end of highlights to give that effect. They are using absolutely positioned, round-edged <div>
s to give the effect of highlighting. And at the end of that <div>
, they overlay an image of a rounded rectangle.
And kudos to them, they have done a great job with it.
Upvotes: 8
Reputation: 1739
They are actually using round edged rectangles to cover the end of highlights in sentences which are smaller than the preceding or succeeding lines (just as I said in point 2). Check this out yourself:
<div>
contains all the "selection" highlights. They just put round edged, background-colored rectangles below the text using
absolute, top and left!!!**<div>
holds similar background-colored <div>
s, only they are for highlighting focused word, similar words, etc...This is actually the content of the iframe. See the #document
at the top?
See the expanded view. The small space above having the code is actually the highlighted section.
This is not a very good idea for a simple website though. They really needed to parse and stuff with the words and letters, since it is supposed to be a high-end code editor, so cant blame them for spending a comparatively little time to 'round'ening the edges a little.
Upvotes: 4
Reputation: 16639
Not perfect but it's working:
http://jsfiddle.net/coma/9p2CT/
Remove the real selection
::selection {
background-color: transparent;
}
Add some styles
span.highlight {
background: #ADD6FF;
}
span.begin {
border-top-left-radius: 5px;
border-bottom-left-radius: 5px;
}
span.end {
border-top-right-radius: 5px;
border-bottom-right-radius: 5px;
}
pre.merge-end > span:last-child {
border-bottom-right-radius: 0;
}
pre.merge-end + pre > span:last-child {
border-top-right-radius: 0;
}
pre.merge-begin > span:first-child {
border-bottom-left-radius: 0;
}
pre.merge-begin + pre > span:first-child {
border-top-left-radius: 0;
}
Wrap every character in a node element
var format = function () {
var before = -1;
var html = $.trim(editor.text())
.split("\n")
.reverse()
.map(function (line) {
var a = line.length === before ? 'merge-end' : '';
before = line.length;
return '<pre class="' + a + '"><span>' + line.split('').join('</span><span>') + '</span></pre>';
})
.reverse()
.join('');
editor.html(html);
};
Get the selected nodes and highlight them, take care of their parents
var getSelectedNodes = function () {
var i;
var nodes = [];
var selection = rangy.getSelection();
for (i = 0; i < selection.rangeCount; ++i) {
selection
.getRangeAt(i)
.getNodes()
.forEach(function (node) {
if ($(node).is('span')) {
nodes.push(node);
}
});
}
return nodes;
};
var highlight = function (nodes, beforeNode) {
var currentNode = $(nodes.shift()).addClass('highlight');
var currentParent = currentNode.parent();
if (beforeNode) {
var beforeParent = beforeNode.parent();
if (currentParent.get(0) !== beforeParent.get(0)) {
currentNode.addClass('begin');
beforeNode.addClass('end');
beforeParent.addClass('merge-begin');
}
} else {
currentNode.addClass('begin');
}
if (nodes.length) {
highlight(nodes, currentNode);
} else {
currentNode.addClass('end');
}
};
format();
$(document).on('mouseup', function () {
$('.highlight').removeClass('highlight begin end');
highlight(getSelectedNodes());
});
Upvotes: 19
Reputation: 6517
CSS' ::selection
only supports declaring color, background, cursor and outline (See W3C). So there's no possibility to define border-radius
for the selection with pure CSS.
So I believe they did it like Niklas mentioned in comments:
I begun to try to create a solution myself, but I lost my motivation since it tooks too much time. Maybe someone could need my suggestions (I used jQuery):
For point 2:
var selection = (window.getSelection() // > IE 9
|| document.selection.createRange() //< IE 9
).toString();
For point 4 use replace()
For point 6:
$(".selection").replaceWith($(".selection")[0].childNodes);
Upvotes: 2