Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to apply rounded borders to highlight/selection

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:

http://i.imgur.com/V9dlwSr.png

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.)

like image 610
Cyral Avatar asked Jul 27 '14 20:07

Cyral


People also ask

How do I make a round picture border?

The border-radius CSS property is what adds the rounded corners. You can experiment with different values to get it the way you like. border-radius: 75px; If you want it to be a circle, add border-radius: 50%; .


3 Answers

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());
});

Thanks to Tim Down for Rangy!

like image 172
coma Avatar answered Nov 11 '22 05:11

coma


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:

  1. You cannot inspect element directly from the iframe. So click somewhere else and navigate to the iframe. I did it using chromes' built in source code inspector.
  2. Then use this image to find out the position of line highlighted in the image.
  3. That <div> contains all the "selection" highlights. They just put round edged, background-colored rectangles below the text using absolute, top and left!!!**
  4. The next <div> holds similar background-colored <div>s, only they are for highlighting focused word, similar words, etc...

enter image description here

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.

enter image description here

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.

like image 39
BlackPanther Avatar answered Nov 11 '22 05:11

BlackPanther


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:

  1. Wait until the user selects something (selectstart, combination of mousedown and mouseup)
  2. Get the selected text
  3. Get the position of the selected text (number of characters from the beginning) since if you just double-click a single word, you can't create a rule from it
  4. Wrap the selection with a div or span
  5. Apply styles to the wrapper
  6. Listen for the user to click something else etc. (unselects text) -> remove wrapper

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);

Fiddle

like image 2
SVSchmidt Avatar answered Nov 11 '22 04:11

SVSchmidt