Display
ScrollHint
Indicador animado que invita a hacer scroll
Preview
size
Code
import css from "./ScrollHint.module.css";
import { CaretDownIcon } from "@phosphor-icons/react/dist/csr/CaretDown";
type Size = "md" | "lg";
interface ResponsiveSize {
base: Size;
mobile?: Size;
tablet?: Size;
desktop?: Size;
wide?: Size;
}
interface ScrollHintProps {
size: Size | ResponsiveSize;
}
function getSizeClasses(size: Size | ResponsiveSize): string {
if (typeof size === "string") {
return css[size];
}
const classes = [css[`${size.base}_base`]];
if (size.mobile) classes.push(css[`${size.mobile}_mobile`]);
if (size.tablet) classes.push(css[`${size.tablet}_tablet`]);
if (size.desktop) classes.push(css[`${size.desktop}_desktop`]);
if (size.wide) classes.push(css[`${size.wide}_wide`]);
return classes.join(" ");
}
export default function ScrollHint({ size }: ScrollHintProps) {
return (
<div className={`${css.hint} ${getSizeClasses(size)}`} aria-hidden="true">
<CaretDownIcon className={css.hint_chevron} weight="bold" />
<CaretDownIcon className={css.hint_chevron} weight="bold" />
<CaretDownIcon className={css.hint_chevron} weight="bold" />
</div>
);
}
.hint {
display: flex;
flex-direction: column;
align-items: center;
&.md {
font-size: var(--text-20);
}
&.lg {
font-size: var(--text-28);
}
&.md_base {
font-size: var(--text-20);
}
&.lg_base {
font-size: var(--text-28);
}
@media (min-width: 480px) {
&.md_mobile {
font-size: var(--text-20);
}
&.lg_mobile {
font-size: var(--text-28);
}
}
@media (min-width: 768px) {
&.md_tablet {
font-size: var(--text-20);
}
&.lg_tablet {
font-size: var(--text-28);
}
}
@media (min-width: 1024px) {
&.md_desktop {
font-size: var(--text-20);
}
&.lg_desktop {
font-size: var(--text-28);
}
}
@media (min-width: 1440px) {
&.md_wide {
font-size: var(--text-20);
}
&.lg_wide {
font-size: var(--text-28);
}
}
.hint_chevron {
width: 1em;
height: 1em;
line-height: 1;
color: var(--color-accent);
opacity: 0.25;
/* Each chevron lights up in turn → the pulse travels downward. */
animation: chevron-pulse 1.6s ease-in-out infinite;
/* Overlap so the three read as one cohesive arrow. */
&:not(:first-child) {
margin-top: -0.4em;
}
&:nth-child(2) {
animation-delay: 0.18s;
}
&:nth-child(3) {
animation-delay: 0.36s;
}
}
@media (prefers-reduced-motion: reduce) {
.hint_chevron {
animation: none;
opacity: 0.5;
}
}
}
@keyframes chevron-pulse {
0%,
60%,
100% {
opacity: 0.25;
}
30% {
opacity: 1;
}
}
<script setup lang="ts">
import { computed } from "vue";
import { PhCaretDown } from "@phosphor-icons/vue";
type Size = "md" | "lg";
interface ResponsiveSize {
base: Size;
mobile?: Size;
tablet?: Size;
desktop?: Size;
wide?: Size;
}
interface ScrollHintProps {
size: Size | ResponsiveSize;
}
const { size } = defineProps<ScrollHintProps>();
const sizeClasses = computed(() => {
if (typeof size === "string") {
return size;
}
const classes = [`${size.base}_base`];
if (size.mobile) classes.push(`${size.mobile}_mobile`);
if (size.tablet) classes.push(`${size.tablet}_tablet`);
if (size.desktop) classes.push(`${size.desktop}_desktop`);
if (size.wide) classes.push(`${size.wide}_wide`);
return classes.join(" ");
});
</script>
<template>
<div class="hint" :class="sizeClasses" aria-hidden="true">
<PhCaretDown class="hint_chevron" weight="bold" />
<PhCaretDown class="hint_chevron" weight="bold" />
<PhCaretDown class="hint_chevron" weight="bold" />
</div>
</template>
<style scoped>
.hint {
display: flex;
flex-direction: column;
align-items: center;
&.md {
font-size: var(--text-20);
}
&.lg {
font-size: var(--text-28);
}
&.md_base {
font-size: var(--text-20);
}
&.lg_base {
font-size: var(--text-28);
}
@media (min-width: 480px) {
&.md_mobile {
font-size: var(--text-20);
}
&.lg_mobile {
font-size: var(--text-28);
}
}
@media (min-width: 768px) {
&.md_tablet {
font-size: var(--text-20);
}
&.lg_tablet {
font-size: var(--text-28);
}
}
@media (min-width: 1024px) {
&.md_desktop {
font-size: var(--text-20);
}
&.lg_desktop {
font-size: var(--text-28);
}
}
@media (min-width: 1440px) {
&.md_wide {
font-size: var(--text-20);
}
&.lg_wide {
font-size: var(--text-28);
}
}
.hint_chevron {
width: 1em;
height: 1em;
line-height: 1;
color: var(--color-accent);
opacity: 0.25;
/* Each chevron lights up in turn → the pulse travels downward. */
animation: chevron-pulse 1.6s ease-in-out infinite;
/* Overlap so the three read as one cohesive arrow. */
&:not(:first-child) {
margin-top: -0.4em;
}
&:nth-child(2) {
animation-delay: 0.18s;
}
&:nth-child(3) {
animation-delay: 0.36s;
}
}
@media (prefers-reduced-motion: reduce) {
.hint_chevron {
animation: none;
opacity: 0.5;
}
}
}
@keyframes chevron-pulse {
0%,
60%,
100% {
opacity: 0.25;
}
30% {
opacity: 1;
}
}
</style>
Props
| Prop | Type | Required | Description |
|---|---|---|---|
| size | "md" | "lg" | ResponsiveSize | required | Tamaño de las flechas. Fijo o responsive. |