refactor(placeable): calc position logic

This commit is contained in:
07akioni 2019-12-28 22:44:05 +08:00
parent 48233c995e
commit 1a17ac4645
5 changed files with 296 additions and 112 deletions

View File

@ -1,7 +1,129 @@
<template>
<div style="height: 3000px;">
<div style="margin-left: 400px;">
<!-- <n-popover
<n-config-provider style="height: 6000px; width: 6000px;" theme="light">
<div class="popover-grid" style="margin-top: 1500px;">
<n-popover placement="top-start" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 1 / 2 / 2 / 3;">
Top Start
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="top" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 1 / 3 / 2 / 4;">
Top
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="top-end" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 1 / 4/ 2 / 5;">
Top End
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="left-start" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 2 / 1 / 3 / 2;">
Left Start
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="left" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 3 / 1 / 4 / 2;">
Left
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="left-end" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 4 / 1 / 5 / 2;">
Left End
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="right-start" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 2 / 5 / 3 / 6;">
Right Start
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="right" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 3 / 5 / 4 / 6;">
Right
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="right-end" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 4 / 5 / 5 / 6;">
Right End
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="bottom-start" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 5 / 2 / 6 / 3;">
Bottom Start
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="bottom" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 5 / 3 / 6 / 4;">
Bottom
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
<n-popover placement="bottom-end" trigger="click">
<template v-slot:activator>
<n-button size="small" style="grid-area: 5 / 4 / 6 / 5;">
Bottom End
</n-button>
</template>
<div class="large-text">
Oops!
</div>
</n-popover>
</div>
<!-- <div style="margin-left: 400px;">
<n-popover
v-model="v"
trigger="manual"
>
@ -18,7 +140,7 @@
>Activator span</span>
</template>
<span>Out Out Out</span>
</n-popover> -->
</n-popover>
<n-popover
arrow
trigger="click"
@ -29,10 +151,10 @@
</template>
<span>LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong</span>
</n-popover>
</div>
<div style="height: 100px;" />
</div> -->
<!-- <div style="height: 100px;" />
<div style="margin-left: 400px;">
<!-- <n-popover
<n-popover
v-model="v"
trigger="manual"
>
@ -49,7 +171,7 @@
>Activator span</span>
</template>
<span>Out Out Out</span>
</n-popover> -->
</n-popover>
<n-popover
arrow
trigger="click"
@ -60,8 +182,8 @@
</template>
<span>LongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLongLong</span>
</n-popover>
</div>
</div>
</div> -->
</n-config-provider>
</template>
<script>
@ -78,3 +200,17 @@ export default {
}
}
</script>
<style scoped>
.popover-grid {
display: grid;
grid-template-columns: auto auto auto auto auto;
grid-gap: 64px;
justify-content: center;
align-items: center;
}
.large-text {
font-size: 240px;
}
</style>

View File

@ -126,11 +126,11 @@
```css
.popover-grid {
display: grid;
grid-template-columns: auto auto auto auto auto;
grid-gap: 12px;
justify-content: center;
align-items: center;
display: grid;
grid-template-columns: auto auto auto auto auto;
grid-gap: 12px;
justify-content: center;
align-items: center;
}
.large-text {

View File

@ -22,7 +22,7 @@
</script>
<style scoped lang="scss">
.demo-card__view {
.demo-card__view, .naive-ui-doc {
/**STYLE_SLOT*/
}
</style>

View File

@ -77,7 +77,7 @@ function genVueComponent (parts, noRunning = false) {
const contentReg = /<!--CONTENT_SLOT-->/
const codeReg = /<!--CODE_SLOT-->/
const scriptReg = /\/\*\*\sSCRIPT_SLOT\s\*\//
const styleReg = /\/\*\*STYLE_SLOT\*\//
const styleReg = /\/\*\*STYLE_SLOT\*\//g
const demoReg = /<!--DEMO_SLOT-->/
let src = demoBlock
// console.log(src)

View File

@ -1,96 +1,144 @@
export default function calcPlacementTransform (placement, activatorRect, contentRect) {
let contentLeft = null
let contentTop = null
let contentRight = null
let contentBottom = null
let suggesetedTransfromOrigin
if (placement === 'top-start') {
contentTop = activatorRect.top - contentRect.height
contentLeft = activatorRect.left
suggesetedTransfromOrigin = 'bottom left'
} else if (placement === 'top') {
contentTop = activatorRect.top - contentRect.height
contentLeft = activatorRect.left + activatorRect.width / 2 - contentRect.width / 2
suggesetedTransfromOrigin = 'bottom'
} else if (placement === 'top-end') {
contentTop = activatorRect.top - contentRect.height
contentLeft = activatorRect.left + activatorRect.width - contentRect.width
suggesetedTransfromOrigin = 'bottom right'
} else if (placement === 'left-start') {
contentTop = activatorRect.top
contentLeft = activatorRect.left - contentRect.width
suggesetedTransfromOrigin = 'top right'
} else if (placement === 'left') {
contentTop = activatorRect.top + activatorRect.height / 2 - contentRect.height / 2
contentLeft = activatorRect.left - contentRect.width
suggesetedTransfromOrigin = 'center right'
} else if (placement === 'left-end') {
contentTop = activatorRect.top + activatorRect.height - contentRect.height
contentLeft = activatorRect.left - contentRect.width
suggesetedTransfromOrigin = 'bottom right'
} else if (placement === 'right-start') {
const toWindowBottom = window.innerHeight - activatorRect.top - contentRect.height
const toWindowRight = window.innerWidth - activatorRect.right - contentRect.width
if (toWindowBottom < 0) {
contentBottom = window.innerHeight - activatorRect.bottom
suggesetedTransfromOrigin = 'bottom'
} else {
contentTop = activatorRect.top
suggesetedTransfromOrigin = 'top'
}
if (toWindowRight < 0) {
contentRight = window.innerWidth - activatorRect.left
suggesetedTransfromOrigin += ' right'
} else {
contentLeft = activatorRect.left + activatorRect.width
suggesetedTransfromOrigin += ' left'
}
} else if (placement === 'right') {
contentTop = activatorRect.top + activatorRect.height / 2 - contentRect.height / 2
contentLeft = activatorRect.left + activatorRect.width
suggesetedTransfromOrigin = 'center left'
} else if (placement === 'right-end') {
contentTop = activatorRect.top + activatorRect.height - contentRect.height
contentLeft = activatorRect.left + activatorRect.width
suggesetedTransfromOrigin = 'bottom left'
} else if (placement === 'bottom-start') {
const toWindowBottom = window.innerHeight - activatorRect.bottom
const toWindowRight = window.innerWidth - activatorRect.left - contentRect.width
if (contentRect.height > toWindowBottom && activatorRect.top > toWindowBottom) {
contentBottom = toWindowBottom + activatorRect.height
// contentTop = null
suggesetedTransfromOrigin = 'bottom'
} else {
contentTop = activatorRect.top + activatorRect.height
// contentBottom = null
suggesetedTransfromOrigin = 'top'
}
if (toWindowRight < 0) {
contentLeft = activatorRect.right - contentRect.width
suggesetedTransfromOrigin += ' right'
} else {
contentLeft = activatorRect.left
suggesetedTransfromOrigin += ' left'
}
} else if (placement === 'bottom-end') {
contentTop = activatorRect.top + activatorRect.height
contentLeft = activatorRect.left + activatorRect.width - contentRect.width
suggesetedTransfromOrigin = 'top right'
} else {
contentTop = activatorRect.top + activatorRect.height
contentLeft = activatorRect.left + activatorRect.width / 2 - contentRect.width / 2
suggesetedTransfromOrigin = 'top center'
}
/**
* We could also change the position using transform.
* Such as return `transform: translateX(${contentLeft}px) translateY(${contentTop}px);`
* However, I found that the dom delay is very serious.
* So I decide to use left and top for now.
*/
return [{
left: contentLeft && `${contentLeft}px`,
top: contentTop && `${contentTop}px`,
right: contentRight && `${contentRight}px`,
bottom: contentBottom && `${contentBottom}px`
}, suggesetedTransfromOrigin]
const oppositeDirection = {
top: 'bottom',
bottom: 'top',
left: 'right',
right: 'left'
}
const adjacentDirections = {
top: ['left', 'right'],
right: ['top', 'bottom'],
bottom: ['left', 'right'],
left: ['top', 'bottom']
}
const lengthToCompare = {
top: 'height',
bottom: 'height',
left: 'width',
right: 'width'
}
function getAdjustedPlacementOfTrackingElement (placement = 'bottom-start', trackedRect, trackingRect) {
const [direction, position] = placement.split('-')
if (trackedRect[direction] >= trackingRect[lengthToCompare[direction]]) {
return placement
} else if (trackedRect[oppositeDirection[direction]] >= trackingRect[lengthToCompare[direction]]) {
if (position) return `${oppositeDirection[direction]}-${position}`
else return oppositeDirection[direction]
} else {
const [direction1, direction2] = adjacentDirections[direction]
let adjacentDirectionWithMoreSpace = direction1
if (trackedRect[direction1] < trackedRect[direction2]) {
adjacentDirectionWithMoreSpace = direction2
}
if (trackedRect[adjacentDirectionWithMoreSpace] < trackingRect[lengthToCompare[adjacentDirections]]) {
/**
* If no direction has required space, simply not flip tracking element to any side.
*/
return placement
}
return adjacentDirectionWithMoreSpace
}
}
const placementToTransformOrigin = {
'bottom-start': 'top left',
'bottom': 'top center',
'bottom-end': 'top right',
'top-start': 'bottom left',
'top': 'bottom',
'top-end': 'bottom right',
'right-start': 'top left',
'right': 'center left',
'right-end': 'bottom left',
'left-start': 'top right',
'left': 'center right',
'left-end': 'bottom right'
}
function getTransformOriginByPlacement (placement) {
return placementToTransformOrigin[placement] || null
}
function getPosition (placement, trackedRect, trackingRect) {
const position = {
left: null,
right: null,
top: null,
bottom: null
}
switch (placement) {
case 'bottom-start':
position.top = trackedRect.top + trackedRect.height
position.left = trackedRect.left
break
case 'bottom':
position.top = trackedRect.top + trackedRect.height
position.left = trackedRect.left + trackedRect.width / 2 - trackingRect.width / 2
break
case 'bottom-end':
position.top = trackedRect.top + trackedRect.height
position.left = trackedRect.left + trackedRect.width - trackingRect.width
break
case 'top-start':
position.top = trackedRect.top - trackingRect.height
position.left = trackedRect.left
break
case 'top':
position.top = trackedRect.top - trackingRect.height
position.left = trackedRect.left + trackedRect.width / 2 - trackingRect.width / 2
break
case 'top-end':
position.top = trackedRect.top - trackingRect.height
position.left = trackedRect.left + trackedRect.width - trackingRect.width
break
case 'left-start':
position.top = trackedRect.top
position.left = trackedRect.left - trackingRect.width
break
case 'left':
position.top = trackedRect.top + trackedRect.height / 2 - trackingRect.height / 2
position.left = trackedRect.left - trackingRect.width
break
case 'left-end':
position.top = trackedRect.top + trackedRect.height - trackingRect.height
position.left = trackedRect.left - trackingRect.width
break
case 'right-start':
position.top = trackedRect.top
position.left = trackedRect.left + trackedRect.width
break
case 'right':
position.top = trackedRect.top + trackedRect.height / 2 - trackingRect.height / 2
position.left = trackedRect.left + trackedRect.width
break
case 'right-end':
position.top = trackedRect.top + trackedRect.height - trackingRect.height
position.left = trackedRect.left + trackedRect.width
break
}
if (position.left !== null) position.left = position.left + 'px'
if (position.right !== null) position.right = position.right + 'px'
if (position.top !== null) position.top = position.top + 'px'
if (position.bottom !== null) position.bottom = position.bottom + 'px'
return position
}
function calcPlacementTransform (placement, activatorRect, contentRect) {
const trackedRect = {
left: parseInt(activatorRect.left),
top: parseInt(activatorRect.top),
bottom: parseInt(window.innerHeight - activatorRect.bottom),
right: parseInt(window.innerWidth - activatorRect.right),
width: parseInt(activatorRect.width),
height: parseInt(activatorRect.height)
}
const trackingRect = contentRect
const adjustedPlacement = getAdjustedPlacementOfTrackingElement(placement, trackedRect, trackingRect)
const suggesetedTransfromOrigin = getTransformOriginByPlacement(adjustedPlacement)
const position = getPosition(adjustedPlacement, trackedRect, trackingRect)
return [position, suggesetedTransfromOrigin]
}
export default calcPlacementTransform