mirror of
https://gitlab.com/openstapps/openstapps.git
synced 2026-03-12 17:52:17 +00:00
fix: parallax broken since safari 16.4
Moves the parallax effect into a directive which injects required elements into the shadow DOM and adds `part` attributes, so it can be styled from a global stylesheet.
This commit is contained in:
@@ -22,10 +22,8 @@
|
||||
<!-- TODO: translation -->
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-content-parallax">
|
||||
<div>
|
||||
<ion-content parallax>
|
||||
<div class="about-changelog">
|
||||
<markdown src="assets/about/CHANGELOG.md"></markdown>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
<!-- TODO: translation -->
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-content-parallax">
|
||||
<div>
|
||||
<ion-content parallax>
|
||||
<div class="licenses-content">
|
||||
<ion-card
|
||||
*ngFor="let license of licenses"
|
||||
@@ -49,5 +48,4 @@
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -24,11 +24,9 @@
|
||||
</ng-template>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content *ngIf="content" class="ion-content-parallax">
|
||||
<div>
|
||||
<ion-content parallax *ngIf="content">
|
||||
<ion-text>{{ appName }} v{{ version }}</ion-text>
|
||||
<div class="page-content">
|
||||
<about-page-content *ngFor="let element of content.content" [content]="element"></about-page-content>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 StApps
|
||||
~ Copyright (C) 2023 StApps
|
||||
~ This program is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU General Public License as published by the Free
|
||||
~ Software Foundation, version 3.
|
||||
@@ -26,14 +26,12 @@
|
||||
<ion-label>{{ label | translate }}</ion-label>
|
||||
<stapps-edit-modal #editModal (save)="selection.save()">
|
||||
<ng-template>
|
||||
<ion-content class="ion-padding modal-content">
|
||||
<div>
|
||||
<ion-content parallax [parallaxSize]="160" class="ion-padding modal-content">
|
||||
<stapps-edit-event-selection
|
||||
#selection
|
||||
[items]="associatedDateSeries"
|
||||
(modified)="editModal.pendingChanges = true"
|
||||
></stapps-edit-event-selection>
|
||||
</div>
|
||||
</ion-content>
|
||||
<ion-footer mode="ios">
|
||||
<ion-toolbar color="light">
|
||||
|
||||
@@ -12,8 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@import 'src/theme/common/ion-content-parallax';
|
||||
|
||||
:host {
|
||||
display: block;
|
||||
padding: var(--spacing-sm);
|
||||
@@ -35,10 +33,7 @@
|
||||
}
|
||||
|
||||
.modal-content {
|
||||
--background: var(--ion-color-primary);
|
||||
--color: var(--ion-color-primary-contrast);
|
||||
|
||||
@include ion-content-parallax($content-size: 160px);
|
||||
}
|
||||
|
||||
ion-footer > ion-toolbar {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 StApps
|
||||
~ Copyright (C) 2023 StApps
|
||||
~ This program is free software: you can redistribute it and/or modify it
|
||||
~ under the terms of the GNU General Public License as published by the Free
|
||||
~ Software Foundation, version 3.
|
||||
@@ -30,8 +30,8 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ng-content select="[header]"></ng-content>
|
||||
<ion-content class="ion-no-padding ion-content-parallax">
|
||||
<div [ngSwitch]="true">
|
||||
<ion-content parallax class="ion-no-padding">
|
||||
<ng-container [ngSwitch]="true">
|
||||
<ng-container *ngSwitchCase="!item && (isDisconnected | async)">
|
||||
<div class="centeredMessageContainer">
|
||||
<ion-icon name="signal_disconnected"></ion-icon>
|
||||
@@ -59,5 +59,5 @@
|
||||
[contentTemplateRef]="contentTemplateRef"
|
||||
></stapps-data-detail-content>
|
||||
</ng-container>
|
||||
</div>
|
||||
</ng-container>
|
||||
</ion-content>
|
||||
|
||||
@@ -12,11 +12,6 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
:host {
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.crumb-label {
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
|
||||
@@ -21,8 +21,7 @@
|
||||
<ion-title>{{ 'feedback.page.TITLE' | translate }}</ion-title>
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
<ion-content class="ion-content-parallax">
|
||||
<div>
|
||||
<ion-content parallax>
|
||||
<div class="feedback-content">
|
||||
<ion-card>
|
||||
<form #feedbackForm="ngForm" (ngSubmit)="onSubmit()">
|
||||
@@ -123,5 +122,4 @@
|
||||
</form>
|
||||
</ion-card>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-content-parallax" (elementSizeChange)="calcPageSize($event)">
|
||||
<div>
|
||||
<ion-content parallax (elementSizeChange)="calcPageSize($event)">
|
||||
<ion-refresher slot="fixed" (ionRefresh)="refresh($event.target)">
|
||||
<ion-refresher-content
|
||||
pullingIcon="chevron-down-outline"
|
||||
@@ -58,5 +57,4 @@
|
||||
<ion-infinite-scroll id="infinite-scroll" threshold="20%" (ionInfinite)="loadMore($event.target)">
|
||||
<ion-infinite-scroll-content loading-spinner="crescent"> </ion-infinite-scroll-content>
|
||||
</ion-infinite-scroll>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -22,16 +22,13 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content>
|
||||
<div>
|
||||
<ion-content color="light" parallax [parallaxSize]="130">
|
||||
<section class="user-card-wrapper">
|
||||
<ion-card class="user-card">
|
||||
<ion-card-header>
|
||||
<ion-img src="assets/imgs/header.svg"></ion-img>
|
||||
<span *ngIf="user$ | async as userInfo">
|
||||
{{
|
||||
userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase)
|
||||
}}
|
||||
{{ userInfo.role ? (userInfo?.role | titlecase) : ('profile.role_guest' | translate | titlecase) }}
|
||||
</span>
|
||||
</ion-card-header>
|
||||
<ion-card-content>
|
||||
@@ -118,5 +115,4 @@
|
||||
</ion-card-content>
|
||||
</ion-card>
|
||||
</section>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
@@ -12,15 +12,7 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
@import 'src/theme/common/ion-content-parallax';
|
||||
|
||||
:host {
|
||||
ion-content {
|
||||
--background: var(--ion-color-light);
|
||||
|
||||
@include ion-content-parallax($content-size: 130px);
|
||||
}
|
||||
|
||||
section {
|
||||
margin-bottom: calc(2 * var(--spacing-lg) - var(--spacing-md));
|
||||
padding: var(--spacing-md);
|
||||
|
||||
@@ -22,8 +22,7 @@
|
||||
</ion-toolbar>
|
||||
</ion-header>
|
||||
|
||||
<ion-content class="ion-no-padding ion-content-parallax">
|
||||
<div>
|
||||
<ion-content parallax class="ion-no-padding">
|
||||
<div class="settings-content">
|
||||
<ng-container *ngFor="let categoryKey of categoriesOrder">
|
||||
<ion-list *ngIf="objectKeys(settingsCache).includes(categoryKey)">
|
||||
@@ -55,5 +54,4 @@
|
||||
<ion-icon slot="start" name="device_reset"></ion-icon>
|
||||
</ion-button>
|
||||
</div>
|
||||
</div>
|
||||
</ion-content>
|
||||
|
||||
116
src/app/util/ion-content-parallax.directive.ts
Normal file
116
src/app/util/ion-content-parallax.directive.ts
Normal file
@@ -0,0 +1,116 @@
|
||||
/*
|
||||
* Copyright (C) 2023 StApps
|
||||
* This program is free software: you can redistribute it and/or modify it
|
||||
* under the terms of the GNU General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
||||
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
||||
* more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
import {Directive, ElementRef, HostBinding, Input, OnDestroy, OnInit} from '@angular/core';
|
||||
|
||||
type IonicColor =
|
||||
| 'danger'
|
||||
| 'dark'
|
||||
| 'light'
|
||||
| 'medium'
|
||||
| 'primary'
|
||||
| 'secondary'
|
||||
| 'success'
|
||||
| 'tertiary'
|
||||
| 'warning'
|
||||
| string;
|
||||
|
||||
/**
|
||||
* Adds a parallax effect to `<ion-content/>` elements
|
||||
*
|
||||
* Why is this necessary and why do we need to inject
|
||||
* elements into a foreign element's shadow DOM just for this?
|
||||
*
|
||||
* We previously had used a global style in conjunction with
|
||||
* adding a child `<div/>`, however, unfortunately Safari 16.4
|
||||
* broke 3D transforms where the parent is inside the shadow
|
||||
* DOM and child elements are outside.
|
||||
*
|
||||
* We also have to use `<ion-content/>` as ionic relies on the
|
||||
* `<ion-content/>` selector to make iOS animations work.
|
||||
*
|
||||
* Which means this is pretty much the only way to solve this
|
||||
* problem.
|
||||
*
|
||||
* This directive, when applied using `<ion-content parallax/>`,
|
||||
* will transform its shadow DOM from
|
||||
*
|
||||
* ```html
|
||||
* <div id="background-content" part="background"></div>
|
||||
* <main class="inner-scroll scroll-y" part="scroll">
|
||||
* <slot></slot>
|
||||
* </main>
|
||||
* <slot name="fixed"><slot />
|
||||
* ```
|
||||
*
|
||||
* To
|
||||
*
|
||||
* ```html
|
||||
* <div id="background-content" part="background"></div>
|
||||
* <main class="inner-scroll scroll-y" part="scroll parallax-scroll">
|
||||
* <div part="parallax-parent">
|
||||
* <div part="parallax"></div>
|
||||
* <slot></slot>
|
||||
* </div>
|
||||
* </main>
|
||||
* <slot name="fixed"><slot />
|
||||
* ```
|
||||
*
|
||||
* Which we can then be used through `::part()` to style it from
|
||||
* a global style sheet.
|
||||
*/
|
||||
@Directive({
|
||||
selector: 'ion-content[parallax]',
|
||||
})
|
||||
export class IonContentParallaxDirective implements OnInit, OnDestroy {
|
||||
@HostBinding('style.--parallax-content-size.px') @Input() parallaxSize = 230;
|
||||
|
||||
@Input() set parallaxColor(value: IonicColor) {
|
||||
this.parallaxBackground = `var(--ion-color-${value})`;
|
||||
}
|
||||
|
||||
@HostBinding('style.--parallax-background') parallaxBackground?: string;
|
||||
|
||||
private mutationObserver: MutationObserver;
|
||||
|
||||
constructor(private element: ElementRef) {}
|
||||
|
||||
ngOnInit() {
|
||||
this.mutationObserver = new MutationObserver(() => {
|
||||
const inner = this.element.nativeElement.shadowRoot.querySelector('.inner-scroll') as
|
||||
| HTMLDivElement
|
||||
| undefined;
|
||||
if (!inner) return;
|
||||
|
||||
// eslint-disable-next-line unicorn/no-array-for-each
|
||||
inner.childNodes.forEach(node => node.remove());
|
||||
|
||||
inner.part.add('parallax-scroll');
|
||||
const parallaxParentElement = document.createElement('div');
|
||||
parallaxParentElement.part.add('parallax-parent');
|
||||
const parallaxElement = document.createElement('div');
|
||||
parallaxElement.part.add('parallax');
|
||||
|
||||
inner.append(parallaxParentElement);
|
||||
parallaxParentElement.append(parallaxElement, document.createElement('slot'));
|
||||
});
|
||||
this.mutationObserver.observe(this.element.nativeElement.shadowRoot, {
|
||||
childList: true,
|
||||
});
|
||||
}
|
||||
|
||||
ngOnDestroy() {
|
||||
this.mutationObserver.disconnect();
|
||||
}
|
||||
}
|
||||
@@ -31,10 +31,12 @@ import {SimpleSwiperComponent} from './simple-swiper.component';
|
||||
import {SearchbarAutofocusDirective} from './searchbar-autofocus.directive';
|
||||
import {SectionComponent} from './section.component';
|
||||
import {RouterModule} from '@angular/router';
|
||||
import {IonContentParallaxDirective} from './ion-content-parallax.directive';
|
||||
|
||||
@NgModule({
|
||||
imports: [BrowserModule, IonicModule, TranslateModule, ThingTranslateModule.forChild(), RouterModule],
|
||||
declarations: [
|
||||
IonContentParallaxDirective,
|
||||
ElementSizeChangeDirective,
|
||||
ArrayLastPipe,
|
||||
DateIsThisPipe,
|
||||
@@ -50,6 +52,7 @@ import {RouterModule} from '@angular/router';
|
||||
SearchbarAutofocusDirective,
|
||||
],
|
||||
exports: [
|
||||
IonContentParallaxDirective,
|
||||
ElementSizeChangeDirective,
|
||||
ArrayLastPipe,
|
||||
DateIsThisPipe,
|
||||
|
||||
@@ -124,10 +124,6 @@ ion-header {
|
||||
}
|
||||
}
|
||||
|
||||
.ion-content-parallax {
|
||||
@include ion-content-parallax();
|
||||
}
|
||||
|
||||
ion-alert {
|
||||
button.alert-button.preferred {
|
||||
background-color: var(--ion-color-primary);
|
||||
|
||||
@@ -12,43 +12,34 @@
|
||||
* You should have received a copy of the GNU General Public License along with
|
||||
* this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
$overscroll-padding: 720px;
|
||||
$parallax-strength: 2;
|
||||
$default-parallax-content-size: 230px;
|
||||
|
||||
@mixin ion-content-parallax(
|
||||
$parallax-background: var(--ion-color-primary),
|
||||
$background: var(--ion-background-color),
|
||||
$parallax-strength: 2,
|
||||
$overscroll-padding: 720px,
|
||||
$content-size: 230px
|
||||
) {
|
||||
&::part(background) {
|
||||
background: $background;
|
||||
}
|
||||
&::part(scroll) {
|
||||
ion-content::part(parallax-scroll) {
|
||||
perspective: 2px;
|
||||
perspective-origin: center top;
|
||||
}
|
||||
> div {
|
||||
|
||||
ion-content::part(parallax-parent) {
|
||||
transform-style: preserve-3d;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
&::before {
|
||||
content: ' ';
|
||||
ion-content::part(parallax) {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
|
||||
$height: calc($content-size + $overscroll-padding);
|
||||
$translateY: calc($overscroll-padding * $parallax-strength);
|
||||
$translateZ: calc(-1px * $parallax-strength);
|
||||
$transform-origin: calc($parallax-strength * $parallax-strength * $overscroll-padding);
|
||||
|
||||
height: $height;
|
||||
height: calc(var(--parallax-content-size, $default-parallax-content-size) + $overscroll-padding);
|
||||
width: 150%;
|
||||
transform-origin: 50% $transform-origin;
|
||||
transform: translate3d(0px, $translateY, $translateZ) scale($parallax-strength);
|
||||
|
||||
background: $parallax-background;
|
||||
}
|
||||
}
|
||||
background: var(--parallax-background, var(--ion-color-primary));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user