Reputation: 1522
We are trying to scroll to a specific <mat-expansion-panel>
item within a <mat-accordion>
. The problem is that ngAfterViewInit()
is triggered before the accordion and its panels are fully loaded. This means the scrollIntoView()
function is called while the accordions are being loaded and the page size afterwards change making our scroll operation take us to the wrong position of the page.
We also tried the other lifecycle hooks, which did not help, since they're all called to early. Does somebody have any good practice for this issue?
Our source is simple since we are trying to implement something very basic:
landingpage.component.html
<mat-accordion>
<mat-expansion-panel id="pangel-1">
<mat-expansion-panel-header>
<mat-panel-title>Lorem ipsum</mat-panel-title>
</mat-expansion-panel-header>
<p>
Lorem ipsum dolor sit amet,
consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt...
</p>
</mat-expansion-panel>
<mat-expansion-panel id="panel-2">
<mat-expansion-panel-header>
<mat-panel-title>Lorem ipsum</mat-panel-title>
</mat-expansion-panel-header>
<p>
Lorem ipsum dolor sit amet,
consetetur sadipscing elitr,
sed diam nonumy eirmod tempor invidunt...
</p>
</mat-expansion-panel>
[ ... ] // more panels
</mat-accordion>
landingpage.component.ts
ngAfterViewInit() {
this.scroll("panel-1");
}
scroll(id) {
console.log(`scrolling to ${id}`);
let el = document.getElementById(id);
el.scrollIntoView();
}
Upvotes: 8
Views: 14698
Reputation: 1
I ran into the same problem and managed to solve it in a fairly simple and clean way. Thought it was worth sharing in case it help someone else and it expands on the previous answers. I essentially scrolled into view after the state changed and the component mounted and controlled the timeouts for the animations as well as the scroll function. I handled triggering the scroll if the currently active panel (just made expanded state true) was equal to the currently rendered panel. I wanted to have all previous accordions expanded, i.e. not closed others.
import React, { useRef, useEffect } from 'react';
import { withStyles, makeStyles } from '@material-ui/core/styles';
import MuiAccordion from '@material-ui/core/Accordion';
import MuiAccordionDetails from '@material-ui/core/AccordionDetails';
import MuiAccordionSummary from '@material-ui/core/AccordionSummary';
const ACCORDIAN_MIN_HEIGHT = 50;
const ACCORDIAN_V_MARGIN = 20;
const Accordion = withStyles((theme) => ({
root: {
// border: '1px solid rgba(0, 0, 0, .125)',
// boxShadow: 'none',
margin: `${ACCORDIAN_V_MARGIN}px 0`,
'&:not(:last-child)': {
borderBottom: 0,
},
'&:before': {
display: 'none',
},
'&$expanded': {
margin: `${ACCORDIAN_V_MARGIN}px 0`,
},
},
}))(MuiAccordion);
const AccordionSummary = withStyles((theme) => ({
root: {
minHeight: ACCORDIAN_MIN_HEIGHT,
pointerEvents: 'none', // make accordian unlclickable
'&$expanded': {
minHeight: ACCORDIAN_MIN_HEIGHT,
},
},
}))(MuiAccordionSummary);
const AccordionDetails = withStyles((theme) => ({
root: {
padding: theme.spacing(2),
},
}))(MuiAccordionDetails);
// Additional styles
const useStyles = makeStyles((theme) => ({
accordianContent: {
'& .MuiAccordionSummary-content' :{
fontFamily: 'Gilroy',
fontSize: '24px',
transition: 'font-size 1s ease',
margin: `${ACCORDIAN_V_MARGIN}px 0`,
},
'& .MuiAccordionSummary-content.Mui-expanded': {
fontSize: '35px',
},
},
}));
export default function AccordianSection(props) {
const classes = useStyles();
const thisAccordianRef = useRef(props.panel)
const scrollQuestionIntoView = () => {
setTimeout(() => thisAccordianRef.current.scrollIntoView({ behavior: 'smooth',block: "start", inline: "start" }),100)
}
useEffect(() => {
if (props.activeFormStep===props.panel) {
scrollQuestionIntoView()
}
}, [props.activeFormStep])
return (
<>
<Accordion ref={thisAccordianRef} expanded={props.expanded}
TransitionProps={{
timeout: 50
}}
>
<AccordionSummary
expandIcon={null}
className={classes.accordianContent}
>
{props.sectionHeading}
</AccordionSummary>
<AccordionDetails>
{props.children}
</AccordionDetails>
</Accordion>
</>
)
}
Upvotes: 0
Reputation: 17958
Here is an example on StackBlitz with Angular Material 7. Your technique works fine, but you need to be careful about a couple of things - make sure that the page is long enough so that the panel can be positioned at the top of the page, and make sure you don't misspell the panel's id.
Upvotes: 3
Reputation: 391
In my example i click something and it opens a side nav that takes up half the screen, so on the click function i have this logic:
if ($event) {
setTimeout(() => $event.target.scrollIntoView({behavior: 'smooth', block: 'end'}), 1);
}
So in your example you could tie to the afterExpand
event (on mat-expansion-panel) and run the logic.
Upvotes: 3