@@ -25,6 +25,7 @@ import * as mui from '@mui/icons-material';
25
25
import { Link } from '@mui/docs/Link' ;
26
26
import { useTranslate } from '@mui/docs/i18n' ;
27
27
import useQueryParameterState from 'docs/src/modules/utils/useQueryParameterState' ;
28
+
28
29
// For Debugging
29
30
// import Menu from '@mui/icons-material/Menu';
30
31
// import MenuOutlined from '@mui/icons-material/MenuOutlined';
@@ -95,6 +96,8 @@ function selectNode(node) {
95
96
96
97
const iconWidth = 35 ;
97
98
99
+ const SVG_ICON_CLASS = 'svg-icon' ;
100
+
98
101
const StyledIcon = styled ( 'span' ) ( ( { theme } ) => ( {
99
102
display : 'inline-flex' ,
100
103
flexDirection : 'column' ,
@@ -108,23 +111,24 @@ const StyledIcon = styled('span')(({ theme }) => ({
108
111
textAlign : 'center' ,
109
112
width : `calc(${ iconWidth } px + ${ theme . spacing ( 2 ) } * 2 + 2px)` ,
110
113
} ,
111
- } ) ) ;
112
-
113
- const StyledSvgIcon = styled ( SvgIcon ) ( ( { theme } ) => ( {
114
- boxSizing : 'content-box' ,
115
- cursor : 'pointer' ,
116
- color : theme . palette . text . primary ,
117
- border : '1px solid transparent' ,
118
- fontSize : iconWidth ,
119
- borderRadius : '12px' ,
120
- transition : theme . transitions . create ( [ 'background-color' , 'box-shadow' ] , {
121
- duration : theme . transitions . duration . shortest ,
122
- } ) ,
123
- padding : theme . spacing ( 2 ) ,
124
- margin : theme . spacing ( 0.5 , 0 ) ,
125
- '&:hover' : {
126
- backgroundColor : theme . palette . background . default ,
127
- borderColor : theme . palette . primary . light ,
114
+ [ `& .${ SVG_ICON_CLASS } ` ] : {
115
+ width : iconWidth ,
116
+ height : iconWidth ,
117
+ boxSizing : 'content-box' ,
118
+ cursor : 'pointer' ,
119
+ color : theme . palette . text . primary ,
120
+ border : '1px solid transparent' ,
121
+ fontSize : iconWidth ,
122
+ borderRadius : '12px' ,
123
+ transition : theme . transitions . create ( [ 'background-color' , 'box-shadow' ] , {
124
+ duration : theme . transitions . duration . shortest ,
125
+ } ) ,
126
+ padding : theme . spacing ( 2 ) ,
127
+ margin : theme . spacing ( 0.5 , 0 ) ,
128
+ '&:hover' : {
129
+ backgroundColor : theme . palette . background . default ,
130
+ borderColor : theme . palette . primary . light ,
131
+ } ,
128
132
} ,
129
133
} ) ) ;
130
134
@@ -143,20 +147,58 @@ function handleLabelClick(event) {
143
147
selectNode ( event . currentTarget ) ;
144
148
}
145
149
150
+ function isElmVisible ( elm , margin = 0 ) {
151
+ const rect = elm . getBoundingClientRect ( ) ;
152
+ return rect . bottom >= - margin && rect . top <= window . innerHeight + margin ;
153
+ }
154
+
146
155
function Icon ( props ) {
147
- const { icon, onOpenClick } = props ;
156
+ const { icon, onOpenClick, initiallyVisible = false } = props ;
157
+
158
+ const rootRef = React . useRef ( null ) ;
159
+ const [ isVisible , setIsVisible ] = React . useState ( initiallyVisible ) ;
160
+
161
+ // Virtualize the icons to reduce page size and React rendering time.
162
+ // Only render the icons after they become visible in the viewport.
163
+ React . useEffect ( ( ) => {
164
+ const margin = 200 ;
165
+ const root = /** @type {SVGElement } */ ( rootRef . current ) ;
166
+ if ( initiallyVisible || isElmVisible ( root , margin ) ) {
167
+ setIsVisible ( true ) ;
168
+ return ( ) => { } ;
169
+ }
170
+ const observer = new IntersectionObserver (
171
+ ( entries ) => {
172
+ if ( isElmVisible ( entries [ 0 ] . target , margin ) ) {
173
+ setIsVisible ( true ) ;
174
+ }
175
+ } ,
176
+ { rootMargin : `${ margin } px 0px` } ,
177
+ ) ;
178
+ observer . observe ( root ) ;
179
+ return ( ) => {
180
+ observer . disconnect ( ) ;
181
+ } ;
182
+ } , [ initiallyVisible ] ) ;
183
+
148
184
/* eslint-disable jsx-a11y/click-events-have-key-events */
149
185
return (
150
186
< StyledIcon
151
187
key = { icon . importName }
188
+ ref = { rootRef }
152
189
onClick = { Math . random ( ) < 0.1 ? handleIconClick ( icon ) : null }
153
190
>
154
- < StyledSvgIcon
155
- component = { icon . Component }
156
- tabIndex = { - 1 }
157
- onClick = { onOpenClick }
158
- title = { icon . importName }
159
- />
191
+ { isVisible ? (
192
+ < SvgIcon
193
+ component = { icon . Component }
194
+ className = { SVG_ICON_CLASS }
195
+ tabIndex = { - 1 }
196
+ onClick = { onOpenClick }
197
+ title = { icon . importName }
198
+ />
199
+ ) : (
200
+ < div className = { SVG_ICON_CLASS } />
201
+ ) }
160
202
{ /* eslint-disable-next-line jsx-a11y/no-static-element-interactions -- TODO: a11y */ }
161
203
< div onClick = { handleLabelClick } > { icon . importName } </ div >
162
204
{ /* eslint-enable jsx-a11y/click-events-have-key-events */ }
@@ -169,8 +211,14 @@ const Icons = React.memo(function Icons(props) {
169
211
170
212
return (
171
213
< div >
172
- { icons . map ( ( icon ) => (
173
- < Icon key = { icon . importName } icon = { icon } onOpenClick = { handleOpenClick } />
214
+ { icons . map ( ( icon , i ) => (
215
+ < Icon
216
+ key = { icon . importName }
217
+ icon = { icon }
218
+ onOpenClick = { handleOpenClick }
219
+ // Render the first 50 icons immediately as they would be visible on page load
220
+ initiallyVisible = { i < 50 }
221
+ />
174
222
) ) }
175
223
</ div >
176
224
) ;
0 commit comments