Вопрос или проблема
Когда я использовал кастомный пакет мейсонри на Angular с помощью кастомной директивы, в первый раз контент наложился, так как я не правильно рассчитал позицию. Почему это произошло?
Я проверил отладку, и индекс целевого элемента был неправильным, поэтому они накладывались на предыдущий контент. Что мне сделать?
Я думаю, что проблема возникла только в директиве.
вставьте описание изображения здесь
import { Directive, ElementRef, EventEmitter, Input, OnChanges, OnDestroy, Output, Renderer2 } from '@angular/core';
import $ from 'jquery';
@Directive({
selector: '[appPinGrid]',
// standalone: true
})
export class PinGridDirective implements OnChanges, OnDestroy {
@Input()
gutter: number = 0;
@Input()
columns: number = 0;
@Input()
minColWidth: number = 2;
@Input()
maxColWidth: number = 10;
@Input()
doubleColumnWidth: number = 10;
@Input()
itemClass: string = '';
@Input() public data: any;
@Output() onLayoutComplete: EventEmitter<any> = new EventEmitter();
container: any;
containerWidth: number = 0;
containeHeight: number = 0;
columnWidth: any;
items: any;
// columns of grid
columnHeightArray: any;
// timer for detect changes
controlSkipper: any;
resizeControlSkipper: any;
// host listener for resize window
// resizeListener: any;
isSearchFirstTime: any = true;
constructor(public el: ElementRef, public renderer: Renderer2) { }
ngOnDestroy() {
// unsubscribe event listener
this.resizeListener();
}
resizeListener(){
this.renderer.listen('window', 'resize', () => {
if (this.resizeControlSkipper)
clearTimeout(this.controlSkipper)
this.resizeControlSkipper = setTimeout(() => {
// console.log('relayout call');
this.reLayout();
}, 400);
});
}
ngOnChanges(changes: { data: any; }) {
if (this.isSearchFirstTime) {
this.isSearchFirstTime = false;
// this is for skip first changes detect for initialieze all directive (scroll unnecessary api of second page from here so this solution is apply)
} else {
if (changes.data) {
if (this.controlSkipper)
clearTimeout(this.controlSkipper)
this.controlSkipper = setTimeout(async () => {
this.reLayout();
}, 500);
}
}
}
reLayout() {
// console.log('relayout');
this.container = this.el.nativeElement;
this.items = this.el.nativeElement.childNodes;
this.items.forEach((element: any) => {
$(element).css('opacity', '0');
});
this.layoutItem();
}
async layoutItem() {
// console.log('layout');
this.getColumnWidth();
this.initColumnHeightarray();
for (var i = 0; i < this.items.length; i++) {
this.placeItem(this.items[i]);
}
this.items.forEach((element: any) => {
console.log('element: ', element);
$(element).css('opacity', '1');
});
console.log('layout complete in function');
await $(this.container).height(Math.max.apply({}, this.columnHeightArray));
// console.log('set wrapper height', Math.max.apply({}, this.columnHeightArray));
await this.onLayoutComplete.emit('LayoutComplete');
}
initColumnHeightarray() {
this.resetColumnHeight();
for (var i = 0; i < this.columns; i++) {
this.columnHeightArray.push(0);
}
}
resetColumnHeight() {
this.columnHeightArray = [];
}
getColumnWidth() {
this.containerWidth = this.el.nativeElement.offsetWidth;
this.columnWidth = Math.floor((this.containerWidth / this.columns) - this.gutter);
if (this.columnWidth <= Number(this.minColWidth) && this.columns >= 2) {
this.columns--;
this.getColumnWidth();
}
// in below condition I check double column width
// because column minus if card width < 200 and suppose column = 1 then it's width is increase
// now maximum card width is 300 and container is about 465(200 column width + 2 number of column + 2 * gutter size) something, so that, below condition is become true and add one column i.e. 2.
// now two column card width is again < 200 thus it will gose to infonite.
// so I check that if container have enough space to display two column then I add column othersize it will display only one column
if (this.columnWidth >= Number(this.maxColWidth) && this.containerWidth > this.doubleColumnWidth) {
this.columns++;
this.getColumnWidth();
}
}
calculateImageSize() {
}
async placeItem(item: { nodeType: number; getElementsByClassName: (arg0: string) => { (): any; new(): any; length: number; }; }) {
if (item.nodeType != Node.ELEMENT_NODE) {
return; // Skip non-element nodes
}
// Find the column with the minimum height
const minimumColHeight = Math.min.apply({}, this.columnHeightArray);
const minColIndex = $.inArray(minimumColHeight, this.columnHeightArray);
// Calculate the position
const targetIndex = minColIndex;
const fixMarginLeft = (this.containerWidth - this.columnWidth * this.columns - this.gutter * (this.columns - 1)) / 2;
// If the item has the specified class, adjust its size
if (item.getElementsByClassName(this.itemClass).length > 0) {
const img = item.getElementsByClassName(this.itemClass)[0];
$(img).css('position', 'initial');
// Ensure width and height are correctly set for proper placement
const aspectRatio = $(img).width() / ($(img).height() || 1);
$(img).css('width', this.columnWidth);
$(img).css('height', Math.floor(this.columnWidth / aspectRatio));
}
// Calculate the position for the item
console.log('this.columnHeightArray[targetIndex]: ', this.columnHeightArray[targetIndex]);
const position = {
left: (this.columnWidth + this.gutter) * targetIndex + fixMarginLeft,
top: this.columnHeightArray[targetIndex] + this.gutter // Adding gutter to the top position
};
// Position the item
$(item).css('position', 'absolute');
$(item).css('top', position.top);
$(item).css('left', position.left);
// Update the column height to account for the new item
this.columnHeightArray[targetIndex] += $(item).outerHeight() + this.gutter; // Update column height including gutter
console.log('this.columnHeightArray[targetIndex]: ', this.columnHeightArray[targetIndex]);
}
}
Я хочу правильно настроить позицию. Как я могу это сделать?
Ответ или решение
Проблема, с которой вы столкнулись, связана с неправильным вычислением позиций элементов при первом рендере в вашем приказе "PinGridDirective". Судя по вашему описанию и предоставленному коду, ошибка возникает из-за того, что вы не учитываете, что размеры или высоты элементов могут быть не установлены до выполнения раскладки. Это может привести к наложению элементов.
Решение проблемы наложения элементов:
-
Проверка размера изображений: Убедитесь, что размеры изображений загружены перед выполнением раскладки. Вам может понадобиться использовать событие загрузки изображений (например,
load
), чтобы дождаться, пока изображения будут полностью загружены, прежде чем производить вычисления. -
Отложенный рендеринг: Вместо того чтобы запускать
reLayout
сразу после изменений, дождитесь полной загрузки всех данных и изображений, прежде чем вызывать его. Это можно сделать с помощьюPromise
илиasync/await
. -
Использование метода расчета размеров: Хотя у вас есть функция для расчета ширины колонки, убедитесь, что вы также проверяете фактические размеры элементов внутри вашего контейнера после их загрузки. Для этого может потребоваться дополнительная логика.
-
Рендеринг после определенного таймаута: Попробуйте увеличить задержку перед вызовом
reLayout
, чтобы были учтены задержки загрузки элементов. Однако будьте осторожны, чтобы не слишком увеличивать задержку, так как это может привести к плохому пользовательскому опыту. -
Обновление резизера и очистка: Код для обновления при изменении размера окна также должен учитывать, что элементы могут быть снова наложены. Убедитесь, что ваш
resizeListener
корректно срабатывает и вызываетreLayout
с учетом текущих размеров элементов контейнера.
Пример кода
Измените ваш метод layoutItem
, добавив условие для ожидания завершения загрузки изображений:
async layoutItem() {
this.getColumnWidth();
this.initColumnHeightarray();
const promises = [];
// Собираем промисы для всех изображений
for (let i = 0; i < this.items.length; i++) {
const item = this.items[i];
const itemImages = item.getElementsByClassName(this.itemClass);
for (let img of itemImages) {
if (img.complete) {
// Если изображение уже загружено
continue;
} else {
// Добавляем промис для загрузки изображения
promises.push(new Promise((resolve) => {
img.onload = () => { resolve(); };
img.onerror = () => { resolve(); }; // Обрабатываем ошибки
}));
}
}
}
await Promise.all(promises); // Ждем загрузки всех изображений
// Теперь размещаем элементы
for (var i = 0; i < this.items.length; i++) {
this.placeItem(this.items[i]);
}
this.items.forEach((element: any) => {
$(element).css('opacity', '1');
});
await $(this.container).height(Math.max.apply({}, this.columnHeightArray));
this.onLayoutComplete.emit('LayoutComplete');
}
Заключение
С помощью этих изменений вы сможете избежать наложения элементов при первом рендере. Очень важно, чтобы все размеры и положения элементов были правильно рассчитаны, прежде чем вы их раскладываете. Надеюсь, это решение поможет вам улучшить производительность вашего компонента и устранить проблемы с наложением контента.