I have an editor in which the user can create a banner the user can drag the element in any position he/she wants inside a banner, the element has a tooltip, on hover, it should show the tooltip positioned on the side where the space is larger than the rest (top, left, bottom, right) and the tooltip should never go outside the container no matter what.
HTML
<div id="banner-container" class="banner-container">
<span
(cdkDragReleased)="onCircleButtonDragEnd($event)"
id="point-button"
class="point-button"
cdkDragBoundary=".banner-container"
cdkDrag
[style.left]="banner.bannerElements.x"
[style.top]="banner.bannerElements.y"
[attr.data-id]="banner.bannerElements.buttonId"
[id]="'button-' + banner.bannerElements.buttonId"
></span>
<span
id="tooltip"
[style.left]="banner.bannerElements.x"
[style.top]="banner.bannerElements.y"
[attr.data-id]="banner.bannerElements.tooltipId"
[id]="'button-' + banner.bannerElements.tooltipId"
>
Szanujemy Twoją prywatność
</span>
</div>
TS
banner = {
buttonId: 11,
tooltipId: 2,
x: 0,
y: 0
};
onCircleButtonDragEnd(event) {
const container = event.currentTarget as HTMLElement;
const containerWidth = container.clientWidth;
const containerHeight = container.clientHeight;
this.banner.y =
((event.clientX - container.getBoundingClientRect().left) /
containerWidth) *
100;
this.banner.y =
((event.clientY - container.getBoundingClientRect().top) /
containerHeight) *
100;
}``
CSS
.point-button {
cursor: pointer;
display: block;
width: 24px;
height: 24px;
border: 2px solid rgb(179, 115, 188);
background-color: rgb(255, 255, 255);
background-image: none;
border-radius: 100%;
position: relative;
z-index: 1;
box-sizing: border-box;
}
.point-button:active {
box-shadow: 0 5px 5px -3px rgba(0, 0, 0, 0.2),
0 8px 10px 1px rgba(0, 0, 0, 0.14), 0 3px 14px 2px rgba(0, 0, 0, 0.12);
}
.banner-container {
width: 350px;
height: 200px;
max-width: 100%;
border: dotted #ccc 2px;
}
.tooltip {
width: fit-content;
height: 50px;
border: 2px #ccc solid;
display: none;
}
.point-button:hover + .tooltip {
display: block;
}`
**
LIVE DEMO
** : DEMO
2
Answers
Looking at the given demo there is a lot of things to cover:
the
$event
passed to theonCircleButtonDragEnd
is not theMouseEvent
object; the mouse event can be accessed via$event.event
so when you try to assign theevent.currentTarget
to thecontainer
constant, it returns an error,you don’t really need to set
[style.left]
and[style.top]
of thepoint-button
because theCdkDrag
directive already sets the proper position using thetransform: translate3d
,you forgot to add the units to the
[style.left]
and[style.top]
of thetooltip
component so even if the values are calculated properly, the styles will not be bound to the element,if you want the
tooltip
to float next to the draggablepoint-button
it’d be best to setposition: relative
for thebanner-container
andposition: absolute
for thetooltip
, otherwise they may both be misaligned (as one element influences the position of another),in order to achieve what you want we need 3 things:
I. the size of the
banner-container
in order to compute the position of the tooltip,II. the size of the
tooltip
as it influences the desired position,III. the size of the
point-button
.We can start solving this by:
getting the mentioned sizes. The most straightforward way to achieve this in Angular is to use the
@ViewChild
decorator to get theElementRef
references and then get the desired dimensions.adding the
AfterViewInit
hook in the component in which:I. you save the
tooltip
dimensions to use in the future,II. you set the initial
tooltip
position – thepoint-button
position is fixed so this should be quite easy (I did it with thesetTimeout
delayed by0
so Angular didn’t complain about the value change during the lifecycles check),III. you hide the tooltip and set it’s
position
toabsolute
. Setting theposition: absolute
ordisplay: none
in CSS may result in the wrong dimensions calculation, that’s why I’d recommend to first display it the normal way and then hide. For the purpose of hiding the element in Angular you can just set itshidden
attribute.The next steps are just pure maths. As you presented in the pictures the position of the
tooltip
should be dependent on thepoint-button
position. If you analyze it carefully you will notice that one of the possible ways to do this is to divide the rectangle container into 4 triangles using its diagonals and then set thetooltip
position accordingly:That’s why we can tackle this problem by writing the function
computeTheSegment
that takes the position(x, y)
of the point within the container of size(width, height)
and then determines to which segment (T
,R
,B
orL
) the point belongs. It can be done by writing the diagonals equations explicitly (these are just the linear equations of the formy = ax + b
) or by computing the projection coordinates for both the diagonalsd1
andd2
and then comparing them to the originalx
andy
.Once we are done we just need to update the
tooltip
position in theonCircleButtonDragEnd
method. We can do this the following way:extract the
circle-button
position using the mentionedElementRef
,window.getComputedStyle
and theWebKitCSSMatrix
(to get thex
andy
translation from thetransform: translate3d
property)add half of the
circle-button
width to the extracted position to determine the central point of the button,compute the segment by providing the computed position and the
banner-container
sizes to thecomputeTheSegment
method,finally, setting the
x
andy
of the tooltip depending on the segment thecircle-button
belongs to:for the top and bottom position set the
x
of the tooltip to be thecircle-button
position minus half of the tooltip’s width (and accordingly as it comes to the left and right positions and they
coordinate),for the top and bottom position set the
y
of the tooltip to be next to thecircle-button
(and accordingly as it comes to the vertical segments),remember that we don’t want the
tooltip
to overflow from the container, so it’s good to use theMath.max(0, x)
function during the computation – if the value is negative it will be replaced by0
.I share the demo with you:
DEMO
Of course, it can be done in a more elegant way (e.g. with paying attention to the screen
resize
event which can influence thebanner-container
size). Nonetheless, I think it’s the fine approach to solve this problem.You can create a function that does the math and logic of determining the
top
andleft
properties of the tooltip position. Then you can easily apply them using[ngStyle]
:Here’s a working StackBlitz demo.