html - Angular viewChild εγγενές κύλιση CSS παράθυρο διάταξη ιδιομορφίες
Έχω ένα συστατικό σε μια εφαρμογή Angular 19 με ένα κάθετα κυλιόμενο παράθυρο όπου το περιεχόμενο έξω από το παράθυρο είναι κρυφό, δηλαδή, το περιεχόμενο του παραθύρου εκτείνεται πέρα από το πάνω και το κάτω μέρος του σταθερού παραθύρου και το περιεχόμενο μπορεί να κεντραριστεί με το πάτημα ενός κουμπιού. Δεν υπάρχει οριζόντια κύλιση.
Η αρχική κεντράρισμα του περιεχομένου γίνεται με ένα εφέ στον κατασκευαστή:
element = viewChild.required<ElementRef>('myWindow');
#scrollTo = computed(() => {
const nativeElement = this.element().nativeElement;
const scrollHeight: number = nativeElement.scrollHeight;
const offsetHeight: number = nativeElement.offsetHeight;
return 4 + (scrollHeight - offsetHeight) / 2;
});
constructor() {
effect(() =>
this.element().nativeElement.scrollTo(0, this.#scrollTo())
);
}
Τις περισσότερες φορές το scrollHeight είναι 700px και το offsetHeight είναι 300px και τα πράγματα λειτουργούν σωστά· ωστόσο, περίπου μία στις δέκα ή δεκαπέντε ανανεώσεις (στο Chrome) το scrollHeight και το offsetHeight είναι τα ίδια, σε αυτή την περίπτωση 38px, γεγονός που προκαλεί την αποτυχία της κεντράρισμα.
Όπως μπορεί να φανταστείτε, η σκληρή κωδικοποίηση της τιμής κύλισης δεν διορθώνει το πρόβλημα (και δεν θα ήταν επίσης βιώσιμη λύση).
Υποθέτω ότι αυτό είναι μια συνθήκη αγώνα μεταξύ του υπολογισμού διάταξης του προγράμματος περιήγησης (Chrome στη δική μου περίπτωση) και του υπολογισμού του σήματος scrollTo; Κάποιες ιδέες για το πώς να διορθώσουμε αυτόν τον υπολογισμό και/ή τη συμπεριφορά, χωρίς να εισάγουμε καθυστέρηση στον constructor του component;
Ακολουθεί ένα StackBlitz που δείχνει το πρόβλημα· μπορείτε να αλλάξετε τον κατασκευαστή του στοιχείου παραθύρου για να δείτε διάφορους συνδυασμούς που λειτουργούν/δεν λειτουργούν.
ΕΝΗΜΕΡΩΣΕΙΣ
Κατέθεσα αναφορά σφάλματος στο Angular καθώς φαίνεται να είναι πρόβλημα του πλαισίου· είναι απαραίτητο ένα προσωρινό μέτρο μέχρι να είναι διαθέσιμη μια διόρθωση από το Angular.
Αυτό το ζήτημα εμφανίζεται μόνο κατά την ανάπτυξη όταν είναι ενεργοποιημένο το HMR· οπότε, η σωστή λύση για παραγωγή είναι το renderAfterEffect χωρίς καθυστέρηση.
constructor() {
renderAfterEffect(() =>
this.element().nativeElement.scrollTo(0, this.#scrollTo())
);
}
Όπως παρατήρησε ο Matthieu Riegler, το σωστό εφέ που πρέπει να χρησιμοποιηθεί για αλληλεπιδράσεις DOM είναι το renderAfterEffect· δυστυχώς, αυτό επίσης αποτυγχάνει αυτή τη στιγμή χωρίς καθυστέρηση ή με την απενεργοποίηση του Hot Module Replacement (HMR):
constructor() {
renderAfterEffect(() =>
setTimeout(() =>
this.element().nativeElement.scrollTo(0, this.#scrollTo()),
200
)
);
}
Όπως σημείωσε ο Naren Murali στο δεύτερο μέρος της απάντησής του, μια καθυστέρηση χρονικού ορίου θα παρακάμψει το πρόβλημα:
constructor() {
effect(() =>
setTimeout(() =>
this.element().nativeElement.scrollTo(0, this.#scrollTo()),
200
)
);
}
Ωστόσο, όπως δήλωσε ο Matthieu Riegler, η σωστή λύση θα πρέπει να χρησιμοποιεί το renderAfterEffect.
Απάντηση #1
Αυτό το effect θα έπρεπε στην πραγματικότητα να είναι ένα afterRenderEffect() :
constructor() {
afterRenderEffect(() =>
this.element().nativeElement.scrollTo(0, this.#scrollTo())
);
}
effect είναι γνωστό ότι εκτελείται πριν από τη διαδικασία συγχρονισμού, ενώ afterRenderEffect() εκτελείται μετά την απόδοση της εφαρμογής. Αυτό το API συνιστάται ειδικά για τέτοιες περιπτώσεις όπου θέλετε να διαβάσετε και να τροποποιήσετε το DOM.
Επεξεργασία: Όπως απαντήθηκε στο ζήτημα του Angular, αυτό είναι ένα πρόβλημα HMR. Τα στυλ φορτώνονται μετά την εκτέλεση της κλήσης επιστροφής, διαβάζοντας έτσι ένα offsetHeight που σύντομα θα ήταν ξεπερασμένο.
Η λύση είναι να απενεργοποιήσετε το HMR με τη σημαία --no-hmr.
Απάντηση #2
Υποψιάζομαι ότι φορτώνετε το περιεχόμενο μέσα μέσω μιας κλήσης API, γι' αυτό αντιμετωπίζετε αυτό το πρόβλημα. Αντίθετα, δοκιμάστε αυτήν την οδηγία, η οποία είναι λειτουργικά ισοδύναμη με το ng-init από το angularjs.
import { Directive, EventEmitter, Output } from '@angular/core';
@Directive({
selector: '[ngInit]',
exportAs: 'ngInit'
})
export class NgInitDirective {
@Output()
ngInit: EventEmitter<any> = new EventEmitter();
ngOnInit() {
this.ngInit.emit();
}
}
Χρησιμοποιώντας αυτό, μπορούμε να επισυνάψουμε αυτό το γεγονός για να ενεργοποιηθεί στο στοιχείο HTML και να εκτελέσουμε τον υπολογισμό.
<div (ngInit)="scrollFunction()">... content inside </div>
Αλλά αισθάνομαι ότι το κύριο πρόβλημα είναι η χρήση του computed για τον υπολογισμό της διάστασης για κύλιση, μπορεί να μην υπολογίζει την σωστή τιμή, αντίθετα υπολόγισέ την χρησιμοποιώντας μια συνάρτηση αντί για μια έκφραση computed.
Θα πρέπει να χρησιμοποιήσετε το hook ngAfterViewInit, το οποίο ενεργοποιείται μετά την αρχικοποίηση της προβολής (HTML).
Δεν χρειαζόμαστε ένα effect εδώ, αρκεί μια άμεση κλήση.
Αν δεν δουλέψει, δοκίμασε να το τυλίξεις σε ένα setTimeout και να το εκτελέσεις.
ngAfterViewInit() {
this.element().nativeElement.scrollTo(0, this.#scrollTo());
}
Τυλίξτε τον κώδικά σας σε ένα setTimeout ώστε η απόδοση να μπορέσει να ολοκληρωθεί.
constructor() {
effect(() => {
setTimeout(() => {
this.element().nativeElement.scrollTo(0, this.#scrollTo());
});
});
}
