Абсолютное позиционирование div в Angular - PullRequest
0 голосов
/ 04 сентября 2018

Я разрабатываю компонент календаря для Angular, и мне нужно расположить некоторый элемент div (представляющий события) в сетке.

this.days.forEach((day: SchedulerViewDay, dayIndex: number) => {
        day.events = this.getEventsInPeriod({...}).map((event: CalendarSchedulerEvent) => {
            const segmentDuration: number = 60.0 / this.hourSegments;
            const dayStartDate: Date =
                setSeconds(setMinutes(setHours(
                setDate(setMonth(setYear(new Date(), day.date.getFullYear()), day.date.getMonth()), day.date.getDate()), this.dayStartHour), this.dayStartMinute), 0);
            const segmentsNumber: number = (differenceInMinutes(event.start, dayStartDate) / segmentDuration);

            return <CalendarSchedulerEvent>{
                ...
                height: this.hourSegmentHeight * (differenceInMinutes(event.end, event.start) / segmentDuration),
                top: (this.hourSegmentHeight * segmentsNumber)
            };
        });

С этим кодом, это результат: enter image description here

Как видите, div не правильно расположен. Я думаю, что это связано с границами ячейки сетки, поэтому я применил это исправление:

top: (this.hourSegmentHeight * segmentsNumber) + (segmentsNumber / 2) // 1px for each separator line

Каждая граница имеет ширину 1px, и я рассматриваю только сплошные, а не пунктирные (это мотивация / 2.

Это почти решило проблему. Там все еще смещение. Более того, это не элегантное решение, но я не могу придумать, как сделать его более чистым.

Как правильно расположить эти элементы?

Большое спасибо!

РЕДАКТИРОВАТЬ: реализация решения inorganik (первый ответ)

Я создал следующее mixins в моем scss файле:

@mixin day-x($attr, $attr-count: 7, $attr-steps: 1, $unit: '%') {
    $attr-list: null;

    @for $i from 1 through $attr-count {
        $attr-value: $attr-steps * $i;

        &.day#{$i} {
            #{$attr}: #{$attr-value}#{$unit};
        }

        $attr-list: append($attr-list, unquote(".#{$attr}-#{$attr-value}"), comma);
    }

    #{$attr-list} {
        //append style to all classes
    }
}

@mixin time-x($attr, $start-hour: 0, $end-hour: 23, $attr-steps: 2, $minutes-steps: 15, $unit: '%') {
    $attr-list: null;
    $hours: $start-hour;
    $minutes: 0;
    $attr-count: ((($end-hour + 1) - $start-hour) * 60) / $minutes-steps;

    @for $i from 0 through $attr-count {
        $attr-value: $attr-steps * $i;

        @if($i > 0) {
            $minutes: $minutes + $minutes-steps;
            @if($minutes == 60) {
                $minutes: 0;
                $hours: $hours + 1;
            }
        }

        $hoursString: '#{$hours}';
        @if($hours < 10) {
            $hoursString: '0#{$hours}';
        }

        $minutesString: '#{$minutes}';
        @if($minutes < 10) {
            $minutesString: '0#{$minutes}';
        }

        &.time#{$hoursString}#{$minutesString} {
            #{$attr}: #{$attr-value}#{$unit};
        }

        $attr-list: append($attr-list, unquote(".#{$attr}-#{$attr-value}"), comma);
    }

    #{$attr-list} {
        //append style to all classes
    }
}

@mixin length-x($attr, $attr-steps: 2, $minutes-steps: 15, $unit: '%') {
    $attr-list: null;
    $attr-count: 24 * 60 / $minutes-steps;

    @for $i from 0 through $attr-count {
        $attr-name: $minutes-steps * $i;
        $attr-value: $attr-steps * $i;

        &.length#{$attr-name} {
            #{$attr}: #{$attr-value}#{$unit};
        }

        $attr-list: append($attr-list, unquote(".#{$attr}-#{$attr-value}"), comma);
    }

    #{$attr-list} {
        //append style to all classes
    }
}

@include day-x('left', 7, 5);
@include time-x('top', 6, 22, 1.47);
@include length-x('height', 1.47);

Затем я применяю эти стили с атрибутом [ngClass], вызывая следующий метод (пока мне не нужен getDay):

getPositioningClasses(event: CalendarSchedulerEvent): string {
    const classes: string[] = [
        this.getDayClass(event.start),
        this.getTimeClass(event.start),
        this.getLengthClass(differenceInMinutes(event.end, event.start))
    ];
    return classes.join(' ');
}

private getDayClass(date: Date): string {
    return '';
}
private getTimeClass(date: Date): string {
    let hours: string = date.getHours() < 10 ? `0${date.getHours()}` : `${date.getHours()}`;
    let minutes: string = date.getMinutes() < 10 ? `0${date.getMinutes()}` : `${date.getMinutes()}`;
    return `time${hours}${minutes}`;
}
private getLengthClass(durationInMinutes: number): string {
    return `length${durationInMinutes}`;
}

Это результат:

enter image description here

Графически это работает как шарм, но мне нужно, чтобы высота сегментов (58px) была выровнена с процентным приращением, используемым для генерации классов позиционирования (теперь это 1.47%).

Есть ли способ сделать эти переменные (высоту сегментов и процентную высоту и приращения верхней позиции) из углового кода, делая высоту сегмента настраиваемой пользователем и позволяя этим приращениям адаптироваться?

Большое спасибо!

РЕДАКТИРОВАТЬ 2: еще одна проблема

Привет еще раз! Я столкнулся с другой проблемой. Диапазон часов компонента не фиксирован, но настраивается снаружи. В примере в вопросе 6 утра - 22 вечера. Для этого диапазона подойдет процент 1,47, но если я изменю диапазон (например, 0:00 - 22:00), высота календаря будет выше, и процент больше не будет в порядке.

Я думаю, мне нужно вычислить эти значения позиционирования из Typescript. Но я не могу понять, как это сделать.

Чтобы попробовать что-то, я пытаюсь это:

ngAfterViewInit(): void {
    this.calendarContainerHeight = this.calendarContainer.nativeElement.clientHeight;
    const segmentPercentage: number = 100.0 * this.hourSegmentHeight / this.calendarContainerHeight;
    console.log("calendarContainerHeight: ", this.calendarContainerHeight);
    console.log("segmentPercentage: ", segmentPercentage);

    setTimeout(() => {
        this.view.days.forEach(d => {
            d.events.forEach((event: CalendarSchedulerEvent) => {
                let hours: number = event.start.getHours();
                if (this.dayStartHour > 0) { hours = hours - this.dayStartHour; }

                const numberOfSegments: number = hours * this.hourSegments;
                event.top = numberOfSegments * segmentPercentage;
            });
        });
    });
}

Затем я добавил [style.top.%]="event.top" к событию div. Вот результат (пока игнорируйте высоту, я еще не управлял ими):

enter image description here

Как вы можете видеть, процент не является точным, а те события, которые происходят в середине дня (или ближе к концу дня), расположены неправильно.

Как я мог решить эту проблему? Большое спасибо, еще раз!

1 Ответ

0 голосов
/ 04 сентября 2018

Это кажется хрупким решением для вычисления расстояния в пикселях в коде. Я бы использовал CSS классы. Вы можете сделать так, чтобы смесь SCSS генерировала для вас все возможные классы по дням, временам и длинам сегментов. Создайте 3 метода, которые возвращают строки класса css, один для дня, один для времени и один для длины сегмента, который вы можете применить с [ngClass]. Затем метод, который вызывает каждый, что-то вроде ниже. Предполагается, что у вас есть массив сегментов, в которых хранится информация о дате / времени / длине:

getPositioningClasses(segment: any): string {
  const classes = [this.getDayClass(segment.dayStartDate), this.getTimeClass(segment.dayStartDate), this.getLengthClass(segment.timeLength)];
  return classes.join(' ');
}

Таким образом, ваша разметка может выглядеть так:

<segment [ngClass]="getPositioningClasses(segment)"></segment>

Составитель:

<segment class="day0 time1430 length130"></segment>
  • day0 - у вас может быть 0-X, сколько бы дней вы ни показывали. Этот класс просто отвечает за расстояние слева. Используйте проценты, если можете, чтобы макет был гибким.
  • time1430 в этом случае 14:30 - этот класс просто отвечает за расстояние от вершины
  • length130 в этом случае полтора часа. В идеале установите наименьшее приращение времени (15 м?) В виде процентной высоты, а затем дайте миксину умножить приращение на каждую возможную длину.

Также, чтобы убедиться, что вам не нужно учитывать границы, поместите правило box-sizing: border-box css практически на все.

...