Skip to content

Commit b37ae30

Browse files
authored
- Added Webkit and IOS specific optimizations to the CSS properties (#292)
- Added a performance improvement for the homepage to only load in tiles when the user scrolls to the bottom, instead of loading it in batches which looped forever each 150 ms - Changed the animation type and dynamic durations of the side bar to feel more snappy and consistent
1 parent 6485e8a commit b37ae30

3 files changed

Lines changed: 86 additions & 57 deletions

File tree

src/App.vue

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,20 @@ const cssVars = computed(() => ({
2424
'--loading-background-color': colorPalette.value.loading_background,
2525
}));
2626
27-
// Apply CSS variables to the document root
27+
// Apply CSS variables to the document root - batch updates to avoid thrashing
28+
let pendingCssUpdate = false;
2829
watchEffect(() => {
29-
const root = document.documentElement;
30-
Object.entries(cssVars.value).forEach(([key, value]) => {
31-
root.style.setProperty(key, value);
30+
if (pendingCssUpdate) {
31+
return;
32+
}
33+
34+
pendingCssUpdate = true;
35+
requestAnimationFrame(() => {
36+
const root = document.documentElement;
37+
Object.entries(cssVars.value).forEach(([key, value]) => {
38+
root.style.setProperty(key, value);
39+
});
40+
pendingCssUpdate = false;
3241
});
3342
});
3443
@@ -45,7 +54,7 @@ syncRef(
4554
<NGlobalStyle />
4655
<NMessageProvider placement="bottom">
4756
<NNotificationProvider placement="bottom-right">
48-
<div>
57+
<div class="app-root">
4958
<component :is="layout">
5059
<RouterView />
5160
</component>
@@ -56,22 +65,33 @@ syncRef(
5665
</template>
5766

5867
<style>
59-
body {
60-
min-height: 100%;
68+
html {
69+
height: 100%;
6170
margin: 0;
6271
padding: 0;
72+
backface-visibility: hidden;
73+
-webkit-font-smoothing: antialiased;
74+
-webkit-backface-visibility: hidden;
6375
}
6476
65-
html {
66-
height: 100%;
77+
body {
78+
min-height: 100%;
6779
margin: 0;
6880
padding: 0;
81+
-webkit-overflow-scrolling: touch;
6982
}
7083
7184
* {
7285
box-sizing: border-box;
7386
}
7487
88+
.app-root {
89+
contain: layout style paint;
90+
-webkit-transform: translateZ(0);
91+
transform: translateZ(0);
92+
-webkit-overflow-scrolling: touch;
93+
}
94+
7595
body .vld-container {
7696
position: absolute;
7797
left: 0;
@@ -81,6 +101,11 @@ body .vld-container {
81101
background-color: var(--loading-background-color);
82102
z-index: 9999;
83103
text-align: center;
104+
pointer-events: none;
105+
}
106+
107+
body .vld-container.vl-active {
108+
pointer-events: auto;
84109
}
85110
86111
body .vld-container .vl-overlay.vl-active {

src/components/CollapsibleToolMenu.vue

Lines changed: 21 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,9 @@ const props = withDefaults(defineProps<{ toolsByCategory?: ToolCategory[] }>(),
1010
const { toolsByCategory } = toRefs(props);
1111
const route = useRoute();
1212
13-
const makeLabel = (tool: Tool) => () => h(MenuItemWithTooltip, { tool });
14-
const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool });
13+
// Memoize component creation functions
14+
const makeLabel = (tool: Tool) => () => h(MenuItemWithTooltip, { tool, key: tool.path });
15+
const makeIcon = (tool: Tool) => () => h(MenuIconItem, { tool, key: tool.path });
1516
1617
const collapsedCategories = useStorage<Record<string, boolean>>(
1718
'menu-tool-option:collapsed-categories',
@@ -28,18 +29,25 @@ const collapsedCategories = useStorage<Record<string, boolean>>(
2829
2930
// Initialize default values for all categories
3031
watchEffect(() => {
32+
const updates: Record<string, boolean> = {};
33+
let hasChanges = false;
34+
3135
toolsByCategory.value.forEach(({ name }) => {
3236
if (!(name in collapsedCategories.value)) {
33-
collapsedCategories.value[name] = true;
37+
updates[name] = true;
38+
hasChanges = true;
3439
}
3540
});
41+
42+
if (hasChanges) {
43+
Object.assign(collapsedCategories.value, updates);
44+
}
3645
});
3746
3847
const isToggling = ref(false);
3948
4049
function toggleCategoryCollapse({ name }: { name: string }) {
41-
const currentState = collapsedCategories.value[name];
42-
collapsedCategories.value[name] = !currentState;
50+
collapsedCategories.value[name] = !collapsedCategories.value[name];
4351
}
4452
4553
const areAllCollapsed = computed(() => {
@@ -70,13 +78,10 @@ async function toggleAllCategories() {
7078
7179
// Function to calculate animation duration based on number of items
7280
function getAnimationDuration(itemCount: number): number {
73-
const baseDuration = 325;
74-
const baseItemCount = 10;
81+
const baseDuration = 250;
7582
const durationIncrement = 5;
7683
77-
const additionalDuration = (itemCount - baseItemCount) * durationIncrement;
78-
79-
return Math.max(baseDuration + additionalDuration, baseDuration);
84+
return baseDuration + (Math.min(itemCount, 30) * durationIncrement);
8085
}
8186
8287
const menuOptions = computed(() =>
@@ -113,7 +118,7 @@ const themeVars = useThemeVars();
113118
flex cursor-pointer items-center op-60
114119
@click="toggleCategoryCollapse({ name })"
115120
>
116-
<span :class="{ 'rotate-0': isCollapsed, 'rotate-90': !isCollapsed }" text-16px lh-1 op-50 transition-transform>
121+
<span :class="{ 'rotate-0': isCollapsed, 'rotate-90': !isCollapsed }" text-16px lh-1 op-50 transition-transform duration-200>
117122
<icon-mdi-chevron-right />
118123
</span>
119124

@@ -160,10 +165,11 @@ const themeVars = useThemeVars();
160165
}
161166
}
162167
.category-container {
163-
.menu-container {
164-
display: grid;
165-
grid-template-rows: 1fr;
166-
transition: grid-template-rows var(--animation-duration, 350ms) ease-in-out;
168+
.menu-container {
169+
display: grid;
170+
grid-template-rows: 1fr;
171+
transition: grid-template-rows var(--animation-duration, 250ms) ease-out;
172+
will-change: grid-template-rows;
167173
168174
&.collapsed {
169175
grid-template-rows: 0fr;

src/pages/Home.page.vue

Lines changed: 31 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -85,57 +85,55 @@ function stopOrderingFavorites() {
8585
8686
// Batch loading logic for tool cards
8787
const TOOLS_PER_ROW = 4; // Based on xl:grid-cols-4
88-
const ROWS_PER_BATCH = 8;
88+
const ROWS_PER_BATCH = 6;
8989
const TOOLS_PER_BATCH = TOOLS_PER_ROW * ROWS_PER_BATCH; // 32 tools per batch
9090
9191
const visibleToolsCount = ref(TOOLS_PER_BATCH); // Start with first batch
92-
let loadingInterval: NodeJS.Timeout | null = null;
92+
let loadingObserver: IntersectionObserver | null = null;
9393
9494
// Computed property for visible tools
9595
const visibleTools = computed(() => {
9696
return toolStore.tools.slice(0, visibleToolsCount.value);
9797
});
9898
99-
// Function to stop automated loading
100-
function stopAutomatedLoading() {
101-
if (loadingInterval) {
102-
clearInterval(loadingInterval);
103-
loadingInterval = null;
99+
// Function to load next batch
100+
function loadNextBatch() {
101+
if (visibleToolsCount.value < toolStore.tools.length) {
102+
visibleToolsCount.value = Math.min(
103+
visibleToolsCount.value + TOOLS_PER_BATCH,
104+
toolStore.tools.length,
105+
);
104106
}
105107
}
106108
107-
// Function to start automated loading
108-
function startAutomatedLoading() {
109-
// Clear any existing interval
110-
if (loadingInterval) {
111-
clearInterval(loadingInterval);
112-
}
113-
114-
// Start loading batches every 150ms
115-
loadingInterval = setInterval(() => {
116-
if (visibleToolsCount.value < toolStore.tools.length) {
117-
visibleToolsCount.value = Math.min(
118-
visibleToolsCount.value + TOOLS_PER_BATCH,
119-
toolStore.tools.length,
120-
);
121-
}
122-
else {
123-
// Stop loading when all tools are visible
124-
stopAutomatedLoading();
125-
}
126-
}, 150);
127-
}
128-
129-
// Start automated loading on component mount
109+
// Start intersection observer on component mount
130110
onMounted(() => {
131111
nextTick(() => {
132-
startAutomatedLoading();
112+
// Load first batch immediately
113+
loadNextBatch();
114+
115+
// Setup intersection observer for lazy loading
116+
const loadingIndicator = document.querySelector('[data-loading-indicator]');
117+
if (loadingIndicator) {
118+
loadingObserver = new IntersectionObserver(
119+
(entries) => {
120+
if (entries[0]?.isIntersecting && visibleToolsCount.value < toolStore.tools.length) {
121+
loadNextBatch();
122+
}
123+
},
124+
{ rootMargin: '200px' },
125+
);
126+
loadingObserver.observe(loadingIndicator);
127+
}
133128
});
134129
});
135130
136131
// Clean up on component unmount
137132
onUnmounted(() => {
138-
stopAutomatedLoading();
133+
if (loadingObserver) {
134+
loadingObserver.disconnect();
135+
loadingObserver = null;
136+
}
139137
});
140138
</script>
141139

@@ -201,7 +199,7 @@ onUnmounted(() => {
201199
</div>
202200

203201
<!-- Loading indicator when more tools are coming -->
204-
<div v-if="visibleToolsCount < toolStore.tools.length" mt-6 text-center>
202+
<div v-if="visibleToolsCount < toolStore.tools.length" data-loading-indicator mt-6 text-center>
205203
<div text-14px op-70>
206204
{{ t('home.loading-more-tools') }} <span>({{ visibleTools.length }}/{{ toolStore.tools.length }})</span>
207205
</div>

0 commit comments

Comments
 (0)