[SalesForce] Move CSS Popover with cursor in Lightning

I'm trying to move a popover created with CSS, relative to the cursor position. I believe I could do this if I had access to the full DOM, but as far as I understand the LockerService prevents this. The code below shows my current approach, which isn't working.

Component markup:

<div aura:id="popover" class="popover">
    <p class="popover-text">
        Some text
    </p>
</div>

Javascript:

var popovers = cmp.find("popover");

window.onmousemove = function (e) {

    //this isn't the DOM element, and .getElement() no longer works.
    //var popover = popovers[0];

    var x = e.clientX;
    var y = e.clientY;
    for (var i = 0; i < popovers.length; i++) {
        var popover = popovers[i];
        popover.style.top = (y + 20) + 'px';
        popover.style.left = (x + 20) + 'px';
    }
};

The component is has position: absolute and other properties set by CSS not shown. It displays correctly, except I would like it to move with the cursor and not be stationary. Is this currently possible in Lightning?

EDIT: In this case I've realised the question can be summed up as "is it possible to set an element's style via javascript without going via a CSS class?." Unless there's still a better way in Lightning.

Best Answer

You can do this native in Lightning with locker service, but the way you do it is kind of tricky.

Here's the solution I came up with that works. Explanation at the end.

Application.app

<aura:application >
    <aura:attribute name="popovers" type="List" default="['1','2','3','4','5']" />
    <div class="root">
        <aura:iteration items="{!v.popovers}" var="popover" indexVar="index">
            <span class="ta" onmousemove="{!c.updatelocation}" data-index="{!index}">
                This is the text of {!popover}.
            </span>
            <span aura:id="text" class="ta-text">
                This is the tooltip for the {!popover} area.
            </span>
        </aura:iteration>
    </div>
</aura:application>

ApplicationController.js

({
    updatelocation: function(component, event, helper) {
        var index = event.currentTarget.dataset.index;
        var text = index > -1? component.find("text")[index].getElement(): null;
        if(text) {
            text.style.left = (event.clientX+20)+'px';
            text.style.top = (event.clientY-20)+'px';
        }
    }
})

Application.css

.THIS.root {
    padding: 2em;
}
.THIS .ta {
    position: relative;
}
.THIS .ta + .ta-text {
    display: none;
}
.THIS .ta:hover + .ta-text {
    display: inherit;
    position: fixed;
    background: #ff8;
    border: 1px solid red;
    width: 25rel;
    height: 10rel;
    z-index: 9999;
}

In order to avoid traversing the DOM, we use component.find to find our inner HTML elements, then read the index value off of the mouseover area, and if we find popover text, we set its position.

The CSS itself sets up the elements to disappear when their parent is no longer being hovered, so the only JavaScript we need to do is setting the x/y position.

Note that because of z-index stacking rules, the elements are positioned in a way (siblings instead of nested). This prevents the tooltip from appearing under popup areas rendered nearby.

Related Topic