mirror of
https://github.com/Hopiu/vue-material.git
synced 2026-05-20 13:01:52 +00:00
Fix tooltips jumping when screen resize #85
This commit is contained in:
parent
ca6c701318
commit
732ca6f925
4 changed files with 134 additions and 107 deletions
|
|
@ -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;
|
||||
},
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
41
src/core/utils/getInViewPosition.js
Normal file
41
src/core/utils/getInViewPosition.js
Normal 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;
|
||||
Loading…
Reference in a new issue