Fix tooltips jumping when screen resize #85

This commit is contained in:
Marcos Moura 2016-11-21 00:49:44 -02:00
parent ca6c701318
commit 732ca6f925
4 changed files with 134 additions and 107 deletions

View file

@ -8,6 +8,7 @@
<script>
import transitionEndEventName from '../../core/utils/transitionEndEventName';
import getInViewPosition from '../../core/utils/getInViewPosition';
export default {
props: {
@ -73,39 +74,6 @@
this.close();
}
},
isAboveOfViewport(pos) {
return pos.top <= this.browserMargin - parseInt(getComputedStyle(this.menuContent).marginTop, 10);
},
isBelowOfViewport(pos) {
return pos.top + this.menuContent.offsetHeight + this.browserMargin >= window.innerHeight - parseInt(getComputedStyle(this.menuContent).marginTop, 10);
},
isOnTheLeftOfViewport(pos) {
return pos.left <= this.browserMargin - parseInt(getComputedStyle(this.menuContent).marginLeft, 10);
},
isOnTheRightOfViewport(pos) {
return pos.left + this.menuContent.offsetWidth + this.browserMargin >= window.innerWidth - parseInt(getComputedStyle(this.menuContent).marginLeft, 10);
},
getInViewPosition(position) {
let computedStyle = getComputedStyle(this.menuContent);
if (this.isAboveOfViewport(position)) {
position.top = this.browserMargin - parseInt(computedStyle.marginTop, 10);
}
if (this.isOnTheLeftOfViewport(position)) {
position.left = this.browserMargin - parseInt(computedStyle.marginLeft, 10);
}
if (this.isOnTheRightOfViewport(position)) {
position.left = window.innerWidth - this.browserMargin - this.menuContent.offsetWidth - parseInt(computedStyle.marginLeft, 10);
}
if (this.isBelowOfViewport(position)) {
position.top = window.innerHeight - this.browserMargin - this.menuContent.offsetHeight - parseInt(computedStyle.marginTop, 10);
}
return position;
},
getBottomRightPos() {
let menuTriggerRect = this.menuTrigger.getBoundingClientRect();
let position = {
@ -113,7 +81,7 @@
left: menuTriggerRect.left
};
this.getInViewPosition(position);
position = getInViewPosition(this.menuContent, position);
return position;
},
@ -124,7 +92,7 @@
left: menuTriggerRect.left - this.menuContent.offsetWidth + menuTriggerRect.width
};
this.getInViewPosition(position);
position = getInViewPosition(this.menuContent, position);
return position;
},
@ -135,7 +103,7 @@
left: menuTriggerRect.left
};
this.getInViewPosition(position);
position = getInViewPosition(this.menuContent, position);
return position;
},
@ -146,7 +114,7 @@
left: menuTriggerRect.left - this.menuContent.offsetWidth + menuTriggerRect.width
};
this.getInViewPosition(position);
position = getInViewPosition(this.menuContent, position);
return position;
},

View file

@ -32,6 +32,10 @@ $tooltip-height: 20px;
transition-delay: 0s !important;
}
&.md-transition-off {
transition: none !important;
}
&.md-tooltip-top {
margin-top: -14px;
transform: translate(-50%, 8px);

View file

@ -7,42 +7,8 @@
<style lang="scss" src="./mdTooltip.scss"></style>
<script>
let onMouseEnter;
let onMouseLeave;
let targetElement;
let calculateTooltipPosition = (scope) => {
let position = scope.targetElement.getBoundingClientRect();
switch (scope.mdDirection) {
case 'top':
scope.$el.style.top = position.top - scope.$el.offsetHeight + 'px';
scope.$el.style.left = position.left + position.width / 2 + 'px';
break;
case 'right':
scope.$el.style.top = position.top + 'px';
scope.$el.style.left = position.left + position.width + 'px';
break;
case 'bottom':
scope.$el.style.top = position.bottom + 'px';
scope.$el.style.left = position.left + position.width / 2 + 'px';
break;
case 'left':
scope.$el.style.top = position.top + 'px';
scope.$el.style.left = position.left - scope.$el.offsetWidth + 'px';
break;
default:
console.warn(`Invalid ${scope.mdDirection} option to md-direction option`);
}
};
import transitionEndEventName from '../../core/utils/transitionEndEventName';
import getInViewPosition from '../../core/utils/getInViewPosition';
export default {
props: {
@ -55,15 +21,17 @@
default: '0'
}
},
data() {
return {
active: false
};
},
data: () => ({
active: false,
transitionOff: false,
topPosition: false,
leftPosition: false
}),
computed: {
classes() {
return {
'md-active': this.active,
'md-transition-off': this.transitionOff,
'md-tooltip-top': this.mdDirection === 'top',
'md-tooltip-right': this.mdDirection === 'right',
'md-tooltip-bottom': this.mdDirection === 'bottom',
@ -72,47 +40,93 @@
},
style() {
return {
'transition-delay': this.mdDelay + 'ms'
'transition-delay': this.mdDelay + 'ms',
top: this.topPosition + 'px',
left: this.leftPosition + 'px'
};
}
},
watch: {
mdDirection() {
calculateTooltipPosition(this);
this.calculateTooltipPosition();
}
},
mounted() {
let tooltipElement = this.$el;
let targetElement = tooltipElement.parentNode;
methods: {
calculateTooltipPosition() {
let position = this.parentElement.getBoundingClientRect();
let cssPosition = {};
this.targetElement = targetElement;
switch (this.mdDirection) {
case 'top':
cssPosition.top = position.top - this.$el.offsetHeight;
cssPosition.left = position.left + position.width / 2;
onMouseEnter = () => {
document.body.appendChild(tooltipElement);
calculateTooltipPosition(this);
this.active = true;
};
break;
onMouseLeave = () => {
let onTransitionEnd = () => {
tooltipElement.removeEventListener('transitionend', onTransitionEnd);
case 'right':
cssPosition.top = position.top;
cssPosition.left = position.left + position.width;
if (tooltipElement.parentNode && !tooltipElement.classList.contains('md-active')) {
document.body.removeChild(tooltipElement);
break;
case 'bottom':
cssPosition.top = position.bottom;
cssPosition.left = position.left + position.width / 2;
break;
case 'left':
cssPosition.top = position.top;
cssPosition.left = position.left - this.$el.offsetWidth;
break;
default:
console.warn(`Invalid ${this.mdDirection} option to md-direction option`);
}
cssPosition = getInViewPosition(this.tooltipElement, cssPosition);
this.topPosition = cssPosition.top;
this.leftPosition = cssPosition.left;
},
open() {
document.body.appendChild(this.tooltipElement);
getComputedStyle(this.tooltipElement).top;
this.transitionOff = true;
this.calculateTooltipPosition();
window.setTimeout(() => {
this.transitionOff = false;
this.active = true;
}, 10);
},
close() {
let cleanupElements = () => {
this.tooltipElement.removeEventListener(transitionEndEventName, cleanupElements);
if (this.tooltipElement.parentNode && !this.tooltipElement.classList.contains('md-active')) {
document.body.removeChild(this.tooltipElement);
}
};
this.active = false;
tooltipElement.removeEventListener('transitionend', onTransitionEnd);
tooltipElement.addEventListener('transitionend', onTransitionEnd);
};
this.tooltipElement.removeEventListener(transitionEndEventName, cleanupElements);
this.tooltipElement.addEventListener(transitionEndEventName, cleanupElements);
}
},
mounted() {
this.$nextTick(() => {
this.tooltipElement = this.$el;
this.parentElement = this.tooltipElement.parentNode;
this.$el.parentNode.removeChild(this.$el);
this.$el.parentNode.removeChild(this.$el);
targetElement.addEventListener('mouseenter', onMouseEnter);
targetElement.addEventListener('focus', onMouseEnter);
targetElement.addEventListener('mouseleave', onMouseLeave);
targetElement.addEventListener('blur', onMouseLeave);
this.parentElement.addEventListener('mouseenter', this.open);
this.parentElement.addEventListener('focus', this.open);
this.parentElement.addEventListener('mouseleave', this.close);
this.parentElement.addEventListener('blur', this.close);
});
},
beforeDestroy() {
this.active = false;
@ -121,11 +135,11 @@
document.body.removeChild(this.$el);
}
if (targetElement) {
targetElement.removeEventListener('mouseenter', onMouseEnter);
targetElement.removeEventListener('focus', onMouseEnter);
targetElement.removeEventListener('mouseleave', onMouseLeave);
targetElement.removeEventListener('blur', onMouseLeave);
if (this.parentElement) {
this.parentElement.removeEventListener('mouseenter', this.open);
this.parentElement.removeEventListener('focus', this.open);
this.parentElement.removeEventListener('mouseleave', this.close);
this.parentElement.removeEventListener('blur', this.close);
}
}
};

View file

@ -0,0 +1,41 @@
const margin = 8;
const isAboveOfViewport = (element, position) => {
return position.top <= margin - parseInt(getComputedStyle(element).marginTop, 10);
};
const isBelowOfViewport = (element, position) => {
return position.top + element.offsetHeight + margin >= window.innerHeight - parseInt(getComputedStyle(element).marginTop, 10);
};
const isOnTheLeftOfViewport = (element, position) => {
return position.left <= margin - parseInt(getComputedStyle(element).marginLeft, 10);
};
const isOnTheRightOfViewport = (element, position) => {
return position.left + element.offsetWidth + margin >= window.innerWidth - parseInt(getComputedStyle(element).marginLeft, 10);
};
const getInViewPosition = (element, position) => {
let computedStyle = getComputedStyle(element);
if (isAboveOfViewport(element, position)) {
position.top = margin - parseInt(computedStyle.marginTop, 10);
}
if (isOnTheLeftOfViewport(element, position)) {
position.left = margin - parseInt(computedStyle.marginLeft, 10);
}
if (isOnTheRightOfViewport(element, position)) {
position.left = window.innerWidth - margin - element.offsetWidth - parseInt(computedStyle.marginLeft, 10);
}
if (isBelowOfViewport(element, position)) {
position.top = window.innerHeight - margin - element.offsetHeight - parseInt(computedStyle.marginTop, 10);
}
return position;
};
export default getInViewPosition;