dumogu-dropdown-menu
Version:
A simple custom scrollbar plugin
344 lines (343 loc) • 13.1 kB
JavaScript
import { BaseTools, removeElement, addElementClass, removeElementClass } from './common';
export class DumoguDropdownMenu extends BaseTools {
#dropdownMenuEl__;
#dropdownMenuContainerEl__;
#dropdownMenuContainerTargetEl__;
#show__ = false;
#content__ = '';
#transitionStart__;
#transitionEnd__;
#isTransitioning__ = false;
#area__ = {};
#areaStartPoint = [0, 0];
#areaEndPoint = [0, 0];
#areaWidth = 0;
#areaHeight = 0;
constructor(options = {}) {
super();
const dropdownMenuEl = document.createElement('div');
const dropdownMenuContainerEl = document.createElement('div');
const dropdownMenuContainerTargetEl = document.createElement('div');
addElementClass(dropdownMenuEl, 'dumogu-dropdown-menu-body');
addElementClass(dropdownMenuContainerEl, 'dumogu-dropdown-menu-body-container');
addElementClass(
dropdownMenuContainerTargetEl,
'dumogu-dropdown-menu-body-container-target',
);
dropdownMenuEl.appendChild(dropdownMenuContainerEl);
dropdownMenuContainerEl.appendChild(dropdownMenuContainerTargetEl);
this.#dropdownMenuEl__ = dropdownMenuEl;
this.#dropdownMenuContainerEl__ = dropdownMenuContainerEl;
this.#dropdownMenuContainerTargetEl__ = dropdownMenuContainerTargetEl;
this.#addEventListener();
this.content = options.content;
this.area = options.area;
this.show = options.show;
}
set dropdownMenuEl(value) {
this.#dropdownMenuEl__ = value;
}
get dropdownMenuEl() {
return this.#dropdownMenuEl__;
}
set dropdownMenuContainerEl(value) {
this.#dropdownMenuContainerEl__ = value;
}
get dropdownMenuContainerEl() {
return this.#dropdownMenuContainerEl__;
}
set dropdownMenuContainerTargetEl(value) {
this.#dropdownMenuContainerTargetEl__ = value;
}
get dropdownMenuContainerTargetEl() {
return this.#dropdownMenuContainerTargetEl__;
}
set show(value) {
if (this.isDestroyed) return;
this.#show__ = value;
if (value && !this.isMounted) {
this.mount();
this.#computedPosition();
// 刚挂载的元素不能直接设置类名来完成过渡动画
setTimeout(() => {
this.#setupActionClass();
}, 16);
} else {
this.#computedPosition();
this.#setupActionClass();
}
}
get show() {
return this.#show__;
}
set content(value) {
this.#content__ = value;
this.#setupContent();
}
get content() {
return this.#content__;
}
set isTransitioning(value) {
this.#isTransitioning__ = value;
this.#autoUnmount();
}
get isTransitioning() {
return this.#isTransitioning__;
}
/**
* 设置用于计算位置的区域
* @param {Object} [value={}] - 区域的属性对象。
* @param {number[]} [value.start=[]] - 区域的起点坐标,格式为 `[x, y]`。
* @param {number[]} [value.end=[]] - 区域的终点坐标,格式为 `[x, y]`。
* @param {number} [value.width] - 区域的宽度。
* @param {number} [value.height] - 区域的高度。
*/
set area(value = {}) {
this.#area__ = value;
this.#areaStartPoint = value.start || [];
this.#areaEndPoint = value.end || [];
this.#areaWidth = value.width || 0;
this.#areaHeight = value.height || 0;
}
get area() {
return this.#area__;
}
mount() {
if (this.isDestroyed) return;
super.mount();
this.#computedPosition();
document.body.appendChild(this.#dropdownMenuEl__);
}
unmount() {
if (this.isDestroyed) return;
super.unmount();
removeElement(this.#dropdownMenuEl__);
}
destroy() {
if (this.isDestroyed) return;
this.#removeEventListener();
this.unmount();
this.content = undefined;
super.destroy();
this.#dropdownMenuEl__ = undefined;
this.#dropdownMenuContainerEl__ = undefined;
this.#dropdownMenuContainerTargetEl__ = undefined;
}
update() {
this.#computedPosition();
}
/** 添加类名 */
#setupActionClass() {
if (this.isDestroyed) return;
const dropdownMenuEl = this.#dropdownMenuEl__;
if (this.#show__) {
addElementClass(dropdownMenuEl, 'show');
} else {
removeElementClass(dropdownMenuEl, 'show');
}
}
/** 添加内容 */
#setupContent() {
if (this.isDestroyed) return;
const dropdownMenuContainerTargetEl = this.#dropdownMenuContainerTargetEl__;
let content = this.#content__ || '';
if (typeof content === 'string') {
dropdownMenuContainerTargetEl.innerHTML = content;
return;
} else if (!content.forEach) {
content = [content];
}
dropdownMenuContainerTargetEl.innerHTML = '';
content.forEach((el) => {
dropdownMenuContainerTargetEl.appendChild(el);
});
}
/** 自动卸载 */
#autoUnmount() {
if (!this.#isTransitioning__ && !this.#show__) {
this.unmount();
}
}
#transitionstart(e) {
if (!e || e.propertyName !== 'transform' || e.target !== this.#dropdownMenuContainerEl__)
return;
this.isTransitioning = true;
}
#transitionEnd(e) {
if (!e || e.propertyName !== 'transform' || e.target !== this.#dropdownMenuContainerEl__)
return;
this.isTransitioning = false;
}
#addEventListener() {
const dropdownMenuContainerEl = this.#dropdownMenuContainerEl__;
const that = this;
that.#transitionStart__ = function (e) {
that.#transitionstart(e);
};
that.#transitionEnd__ = function (e) {
that.#transitionEnd(e);
};
dropdownMenuContainerEl.addEventListener('transitionstart', that.#transitionStart__);
dropdownMenuContainerEl.addEventListener('transitionend', that.#transitionEnd__);
}
#removeEventListener() {
const dropdownMenuContainerEl = this.#dropdownMenuContainerEl__;
dropdownMenuContainerEl.removeEventListener('transitionstart', this.#transitionStart__);
dropdownMenuContainerEl.removeEventListener('transitionend', this.#transitionEnd__);
}
/** 计算位置 */
#computedPosition() {
if (this.isDestroyed) return;
if (!this.#show__) return;
const dropdownMenuContainerEl = this.#dropdownMenuContainerEl__;
const dropdownMenuContainerTargetEl = this.#dropdownMenuContainerTargetEl__;
const position = this.#computContainerStyle();
dropdownMenuContainerEl.style.width = position.containerWidth + 'px';
dropdownMenuContainerEl.style.height = position.containerHeight + 'px';
dropdownMenuContainerEl.style.left = position.transformX + 'px';
dropdownMenuContainerEl.style.top = position.transformY + 'px';
dropdownMenuContainerTargetEl.style.position = 'absolute';
dropdownMenuContainerTargetEl.style.top = 'initial';
dropdownMenuContainerTargetEl.style.right = 'initial';
dropdownMenuContainerTargetEl.style.bottom = 'initial';
dropdownMenuContainerTargetEl.style.left = 'initial';
switch (true) {
case position.x == 'left' && position.y == 'top':
dropdownMenuContainerTargetEl.style.right = '0px';
dropdownMenuContainerTargetEl.style.bottom = '0px';
dropdownMenuContainerEl.style.transformOrigin = 'right bottom';
break;
case position.x == 'right' && position.y == 'top':
dropdownMenuContainerTargetEl.style.bottom = '0px';
dropdownMenuContainerTargetEl.style.left = '0px';
dropdownMenuContainerEl.style.transformOrigin = 'left bottom';
break;
case position.x == 'right' && position.y == 'bottom':
dropdownMenuContainerTargetEl.style.top = '0px';
dropdownMenuContainerTargetEl.style.left = '0px';
dropdownMenuContainerEl.style.transformOrigin = 'left top';
break;
case position.x == 'left' && position.y == 'bottom':
dropdownMenuContainerTargetEl.style.top = '0px';
dropdownMenuContainerTargetEl.style.right = '0px';
dropdownMenuContainerEl.style.transformOrigin = 'right top';
break;
default:
break;
}
}
/** 计算空余,判断元素的那个方向有空余 */
#computSpare() {
const areaStartPoint = this.#areaStartPoint;
const areaEndPoint = this.#areaEndPoint;
const innerHeight = window.innerHeight;
const innerWidth = window.innerWidth;
let x = 'left';
if (innerWidth - areaEndPoint[0] > areaStartPoint[0]) {
x = 'right';
}
let y = 'top';
if (innerHeight - areaEndPoint[1] > areaStartPoint[1]) {
y = 'bottom';
}
return {
x,
y,
};
}
/** 计算精度,更靠近那个方向 */
#computSpareTo() {
const areaStartPoint = this.#areaStartPoint;
const areaEndPoint = this.#areaEndPoint;
const areaWidth = this.#areaWidth;
const areaHeight = this.#areaHeight;
const innerHeight = window.innerHeight;
const innerWidth = window.innerWidth;
const position = this.#computSpare();
let ratio = '';
let targetSlope = Math.abs(areaWidth / areaHeight);
let containerSlope = 0;
if (position.x === 'left' && position.y === 'top') {
containerSlope = Math.abs(areaEndPoint[0] / areaStartPoint[1]);
}
if (position.x === 'right' && position.y === 'top') {
containerSlope = Math.abs((innerWidth - areaStartPoint[0]) / areaStartPoint[1]);
}
if (position.x === 'left' && position.y === 'bottom') {
containerSlope = Math.abs(areaEndPoint[0] / (innerHeight - areaStartPoint[1]));
}
if (position.x === 'right' && position.y === 'bottom') {
containerSlope = Math.abs(
(innerWidth - areaStartPoint[0]) / (innerHeight - areaStartPoint[1]),
);
}
if (targetSlope > containerSlope) {
ratio = 'y';
} else {
ratio = 'x';
}
return {
...position,
ratio,
};
}
/** 计算容器位置,宽高 */
#computContainerStyle() {
const areaStartPoint = this.#areaStartPoint;
const areaEndPoint = this.#areaEndPoint;
const innerHeight = window.innerHeight;
const innerWidth = window.innerWidth;
const position = this.#computSpareTo();
let containerWidth = 0;
let containerHeight = 0;
let transformY = 0;
let transformX = 0;
if (position.x === 'left' && position.y === 'top') {
if (position.ratio == 'x') {
containerWidth = areaStartPoint[0];
containerHeight = areaEndPoint[1];
} else {
containerWidth = areaEndPoint[0];
containerHeight = areaStartPoint[1];
}
}
if (position.x === 'right' && position.y === 'top') {
if (position.ratio == 'x') {
containerWidth = innerWidth - areaEndPoint[0];
containerHeight = areaEndPoint[1];
} else {
containerWidth = innerWidth - areaStartPoint[0];
containerHeight = areaStartPoint[1];
}
transformX = innerWidth - containerWidth;
}
if (position.x === 'left' && position.y === 'bottom') {
if (position.ratio == 'x') {
containerWidth = areaStartPoint[0];
containerHeight = innerHeight - areaStartPoint[1];
} else {
containerWidth = areaEndPoint[0];
containerHeight = innerHeight - areaEndPoint[1];
}
transformY = innerHeight - containerHeight;
}
if (position.x === 'right' && position.y === 'bottom') {
if (position.ratio == 'x') {
containerWidth = innerWidth - areaEndPoint[0];
containerHeight = innerHeight - areaStartPoint[1];
} else {
containerWidth = innerWidth - areaStartPoint[0];
containerHeight = innerHeight - areaEndPoint[1];
}
transformX = innerWidth - containerWidth;
transformY = innerHeight - containerHeight;
}
return {
...position,
containerWidth,
containerHeight,
transformX,
transformY,
};
}
}