Skip to content

Commit 3f44d7a

Browse files
committed
Add scrolling text component to main page and projects
1 parent 2df771b commit 3f44d7a

File tree

12 files changed

+7568
-40
lines changed

12 files changed

+7568
-40
lines changed

src/app/(frontend)/[locale]/layout.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ export default async function RootLayout(props: Props) {
3131
<html lang={locale}>
3232
<body
3333
style={{ scrollbarGutter: 'stable', overflowY: 'scroll' }}
34-
className={`${nexaFont.className} text-vector-cream bg-vector-black min-h-svh flex flex-col`}
34+
className={`${nexaFont.className} text-vector-cream bg-vector-black min-h-svh flex flex-col overflow-x-clip`}
3535
>
3636
<NextIntlClientProvider>
3737
<Header />

src/app/(frontend)/[locale]/page.tsx

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { Metadata } from 'next'
1010
import ScrollToTopBtn from '@/components/mainPage/ScrollToTopBtn'
1111
import Footer from '@/components/mainPage/Footer'
1212
import NavLine from '@/components/mainPage/NavLine'
13+
import ScrollingText from '@/components/ScrollingText'
1314

1415
import React from 'react'
1516
import { Link } from '@/i18n/navigation'
@@ -236,7 +237,10 @@ export default async function MainPage({ params }: Props) {
236237
id="welcome"
237238
className="header-screen flex flex-col lg:flex-row [&_figcaption]:flex [&_figcaption]:items-center [&_figcaption]:justify-center [&_figure]:pb-0! [&>div]:px-0! justify-stretch items-center grow lg:px-10 lg:mb-0"
238239
key={image.id}
239-
style={{ backgroundColor: image.bgColor }}
240+
style={{
241+
backgroundColor:
242+
image.blockType !== 'animatedText' ? image.bgColor : 'transparent',
243+
}}
240244
>
241245
{image.blockType === 'image' && imageBlock(image)}
242246
<ul
@@ -268,16 +272,24 @@ export default async function MainPage({ params }: Props) {
268272
)
269273
})
270274
)}
271-
{mainPageImages.images?.slice(1).map((image) => {
275+
{mainPageImages.images?.slice(1).map((image, i) => {
276+
const scrollableText = image.blockType === 'animatedText'
277+
const previousElement = mainPageImages.images?.slice(1).at(i - 1)
278+
const previousColor =
279+
previousElement?.blockType !== 'animatedText' ? previousElement?.bgColor : 'transparent'
280+
272281
return (
273282
<div
274-
style={{ backgroundColor: image.bgColor }}
283+
style={{ backgroundColor: scrollableText ? previousColor : image.bgColor }}
275284
key={image.id}
276-
className={`${image.blockType !== 'navigation' ? 'lg:min-h-svh' : ''} flex items-center`}
285+
className={`${image.blockType !== 'navigation' && !scrollableText ? 'lg:min-h-svh' : ''} flex items-center`}
277286
>
278287
{image.blockType === 'image' && imageBlock(image)}
279288
{image.blockType === 'aboutUs' && aboutUsBlock(image)}
280289
{image.blockType === 'navigation' && navBlock(image, t('projects'), t('sculptures'))}
290+
{image.blockType === 'animatedText' && (
291+
<ScrollingText text={image.text} color={image.textColor} />
292+
)}
281293
</div>
282294
)
283295
})}

src/app/(frontend)/[locale]/projects/[id]/page.tsx

Lines changed: 68 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { getTranslations, setRequestLocale } from 'next-intl/server'
66
import { RichText } from '@payloadcms/richtext-lexical/react'
77
import headersConverter from '@/lib/utils/converter'
88
import NextProject from '@/components/nextProjects'
9+
import ScrollingText from '@/components/ScrollingText'
910
import '@styles/project.scss'
1011

1112
import { ReactNode } from 'react'
@@ -238,42 +239,75 @@ const Page = async ({ params }: Props) => {
238239
})}
239240
<>
240241
<div className="hidden lg:block">
241-
{project.images?.slice(1).map((img) => (
242-
<div
243-
key={img.id}
244-
className="min-h-svh flex items-center justify-center img-container"
245-
style={{
246-
backgroundColor:
247-
img.blockType === 'image'
248-
? img.bgColor
249-
: img.blockType === 'imageGroup'
250-
? img.images?.at(-1)?.bgColor
251-
: 'transparent',
252-
}}
253-
>
254-
{img.blockType === 'image' ? DesktopImage(img) : DesktopImageGroup(img)}
255-
</div>
256-
))}
242+
{project.images?.slice(1).map((img, i, arr) => {
243+
const prevElement = arr.at(Math.max(i - 1, 0)) // To not cause it to wrap to -1
244+
const previousElementColor =
245+
prevElement?.blockType === 'image'
246+
? prevElement.bgColor
247+
: prevElement?.blockType === 'imageGroup'
248+
? prevElement.images?.at(-1)?.bgColor
249+
: 'transparent'
250+
251+
return (
252+
<div
253+
key={img.id}
254+
className={`${img.blockType !== 'animatedText' ? 'min-h-svh' : ''} flex items-center justify-center img-container`}
255+
style={{
256+
backgroundColor:
257+
img.blockType === 'image'
258+
? img.bgColor
259+
: img.blockType === 'imageGroup'
260+
? img.images?.at(-1)?.bgColor
261+
: 'transparent',
262+
}}
263+
>
264+
{img.blockType === 'image' ? (
265+
DesktopImage(img)
266+
) : img.blockType === 'imageGroup' ? (
267+
DesktopImageGroup(img)
268+
) : (
269+
<div style={{ backgroundColor: previousElementColor }}>
270+
<ScrollingText text={img.text} color={img.textColor} />
271+
</div>
272+
)}
273+
</div>
274+
)
275+
})}
257276
</div>
258277
<div className="block lg:hidden">
259-
{project.images?.slice(1).map((img) => (
260-
<div
261-
key={img.id}
262-
className="img-container"
263-
style={{
264-
backgroundColor:
265-
img.blockType === 'image'
266-
? img.bgColor
267-
: img.blockType === 'imageGroup'
268-
? img.images?.at(-1)?.bgColor
269-
: 'transparent',
270-
}}
271-
>
272-
{img.blockType === 'image'
273-
? PhoneImage(img)
274-
: img.images?.map((groupedImg) => PhoneImage(groupedImg as ProjectImageSingle))}
275-
</div>
276-
))}
278+
{project.images?.slice(1).map((img, i, arr) => {
279+
const prevElement = arr.at(Math.max(i - 1, 0)) // To not cause it to wrap to -1
280+
const previousElementColor =
281+
prevElement?.blockType === 'image'
282+
? prevElement.bgColor
283+
: prevElement?.blockType === 'imageGroup'
284+
? prevElement.images?.at(-1)?.bgColor
285+
: 'transparent'
286+
return (
287+
<div
288+
key={img.id}
289+
className="img-container"
290+
style={{
291+
backgroundColor:
292+
img.blockType === 'image'
293+
? img.bgColor
294+
: img.blockType === 'imageGroup'
295+
? img.images?.at(-1)?.bgColor
296+
: 'transparent',
297+
}}
298+
>
299+
{img.blockType === 'image' ? (
300+
PhoneImage(img)
301+
) : img.blockType === 'imageGroup' ? (
302+
img.images?.map((groupedImg) => PhoneImage(groupedImg as ProjectImageSingle))
303+
) : (
304+
<div style={{ backgroundColor: previousElementColor }}>
305+
<ScrollingText text={img.text} color={img.textColor} />
306+
</div>
307+
)}
308+
</div>
309+
)
310+
})}
277311
</div>
278312
</>
279313
</div>

src/collections/Projects.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { routing } from '@/i18n/routing'
77
import draftAccess from '@/lib/utils/access'
88
import purgeURL from '@/lib/utils/purge'
99
import { PAYLOAD_SECRET } from '@/lib/secrets'
10+
import { ScrollingText } from '@/lib/payloadFields'
1011

1112
// Extracting it from imageConfig since there's an extra field for groups so I add it manually when setting the schema
1213
const desktopConfig: Field = {
@@ -240,6 +241,7 @@ export const Projects: CollectionConfig = {
240241
},
241242
],
242243
},
244+
ScrollingText,
243245
],
244246
validate: (imgs) => {
245247
const images = imgs as Project['images'] // Doing this because typing it in the function definition causes an error

src/components/ScrollingText.tsx

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
'use client'
2+
3+
import '@styles/scrollingText.scss'
4+
5+
import { useEffect, useRef, useState } from 'react'
6+
7+
export default function ScrollingText({
8+
text,
9+
color,
10+
}: {
11+
text: string | null | undefined
12+
color: string
13+
}) {
14+
const textElement = useRef<HTMLSpanElement>(null)
15+
const textContainer = useRef<HTMLDivElement>(null)
16+
17+
const [amountToFill, setAmountToFill] = useState(1)
18+
const [textWidth, setTextWidth] = useState(0)
19+
20+
useEffect(() => {
21+
function setupScroll() {
22+
if (!textElement.current || !textContainer.current) return
23+
24+
const textStyles = getComputedStyle(textElement.current)
25+
const textWidth =
26+
textElement.current.getBoundingClientRect().width +
27+
parseFloat(textStyles.marginInline.replace('px', '')) * 2
28+
const screenWidth = window.screen.width
29+
30+
// Adding plus one to have a hidden one on the left edge
31+
// of the screen so it scrolls into view
32+
setAmountToFill(Math.ceil(screenWidth / textWidth) + 1)
33+
setTextWidth(textWidth)
34+
35+
textContainer.current.style.setProperty('--scroll-amount', `${textWidth}px`)
36+
}
37+
38+
setupScroll()
39+
40+
document.addEventListener('resize', setupScroll)
41+
42+
return () => document.removeEventListener('resize', setupScroll)
43+
}, [textElement, textContainer, text])
44+
45+
return (
46+
<div
47+
className="text-2xl w-svw text-nowrap overflow-x-clip whitespace-nowrap"
48+
style={{ color }}
49+
ref={textContainer}
50+
>
51+
{[...new Array(amountToFill)].map((_, i) => (
52+
<span
53+
ref={textElement}
54+
key={i}
55+
className={`relative mx-25 w-fit ${amountToFill > 1 ? 'scroll-text' : ''} whitespace-nowrap`}
56+
style={{ right: `${textWidth}px` }}
57+
>
58+
{text}
59+
</span>
60+
))}
61+
</div>
62+
)
63+
}

src/globals/MainPage.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import { routing } from '@/i18n/routing'
77
import { revalidatePath } from 'next/cache'
88
import purgeRoute from '@/lib/utils/purge'
99
import { PAYLOAD_SECRET } from '@/lib/secrets'
10+
import { ScrollingText } from '@/lib/payloadFields'
1011

1112
const MainPage: GlobalConfig = {
1213
slug: 'mainPageImages',
@@ -213,6 +214,7 @@ const MainPage: GlobalConfig = {
213214
colorField,
214215
],
215216
},
217+
ScrollingText,
216218
],
217219
},
218220
],

src/lib/payloadFields.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import type { Block } from 'payload'
2+
import { colorField } from './utils/colors'
3+
import { Field } from 'payload'
4+
5+
export const ScrollingText: Block = {
6+
slug: 'animatedText',
7+
labels: {
8+
plural: 'texto animado',
9+
singular: 'texto animado',
10+
},
11+
fields: [
12+
{
13+
type: 'text',
14+
name: 'text',
15+
label: 'texto',
16+
localized: true,
17+
admin: {
18+
description: 'El texto a mostar moviendose a través de la pantalla',
19+
},
20+
},
21+
{
22+
...colorField,
23+
name: 'textColor',
24+
label: 'color de Texto',
25+
admin: {
26+
description: 'El color del texto',
27+
components: {
28+
Field: '@/components/admin/ColorSelect.tsx',
29+
},
30+
},
31+
} as Field,
32+
],
33+
}

src/lib/styles/scrollingText.scss

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
.scroll-text {
2+
animation: scroll 5s linear infinite forwards;
3+
display: inline-block;
4+
will-change: transform;
5+
}
6+
7+
// This works just fine in everything except firefox for some reason it
8+
// flickers when restarting.
9+
@keyframes scroll {
10+
from {
11+
transform: translateX(0);
12+
}
13+
to {
14+
transform: translateX(var(--scroll-amount));
15+
}
16+
}

0 commit comments

Comments
 (0)