Radiant: 一个美丽的新市场网站模板

Dan Hollick

我们刚刚完成了一个美丽的新SaaS市场网站模板 Radiant 的开发,现在作为 Tailwind UI 的一部分发布。

了解Radiant模板

它是使用Next.js、Framer Motion和Tailwind CSS构建的,博客由Sanity提供支持。

我们已经有一段时间没有构建这样的SaaS市场模板了,在此期间,我们学到了很多关于什么使得这样的模板有用且易于使用的知识。我们努力将所有这些经验融入到Radiant中。

像往常一样,查看实时预览以获取完整体验——这个模板中有很多酷炫的细节,您必须在浏览器中查看才能真正欣赏。


精致的互动

在这样的网站上,过度使用动画是非常容易的。我们都见过那些在滚动时,许多不同元素都在动画中的网站。更糟糕的是,当您必须等待内容出现才能阅读时,感觉是多么的慢。

Radiant 充满了让人愉悦的动画,但它们都是叠加在现有内容上并由用户交互触发,这样网站仍然感觉很快。在大多数情况下,我们选择了循环动画,以使元素在你与它们互动时感觉“活着”。

我们几乎使用Framer Motion进行了所有的动画。它是声明式的,这使得创建我们自己的API以供他人定制复杂动画变得容易。

不过,它确实有一些需要避免的缺点。例如,当您有多个独立动画的元素时,将悬停状态传递到每个子元素会很麻烦。我们最终利用了Framer的变体传播来使其工作——悬停事件触发父元素的变体变化,这种变化会向下传播到子元素,因为它们共享相同的变体键。

bento-card.tsx
export function BentoCard() {
return (
<motion.div
initial="idle"
whileHover="active"
variants={{ idle: {}, active: {} }}
data-dark={dark ? "true" : undefined}
>
/* ... */
</motion.div>
);
}

父元素中的变体没有区别,因此实际上不会更改,但子元素在悬停时仍然获得变更变体的信号,即使它们嵌套得很深。

map.tsx
function Marker({
src,
top,
offset,
delay,
}: {
src: string
top: number
offset: number
delay: number
}) {
return (
<motion.div
variants={{
idle: { scale: 0, opacity: 0, rotateX: 0, rotate: 0, y: 0 },
active: { y: [-20, 0, 4, 0], scale: [0.75, 1], opacity: [0, 1] },
}}
transition={{ duration: 0.25, delay, ease: 'easeOut' }}
style={{ '--offset': `${offset}px`, top } as React.CSSProperties}
className="absolute left-[calc(50%+var(--offset))] size-[38px] drop-shadow-[0_3px_1px_rgba(0,0,0,.15)]"
>
/* ... */
</motion.div>
)
}
/* ... */

logo时间线动画稍微不同,因为我们希望在您停止悬停时,logo在当前位置暂停,而不是返回到原始位置。这与Framer的起始和结束状态的定义方法不太兼容,因此实际上在CSS中构建这个要容易得多。

它利用了您可以设置负的animation-delay值来偏移元素的起始位置这一点。这样所有logo共享相同的动画关键帧,但它们可以在不同的位置开始并具有不同的持续时间。

logo-timeline.tsx
function Logo({
label,
src,
className,
}: {
label: string
src: string
className: string
}) {
return (
<div
className={clsx(
className,
'absolute top-2 grid grid-cols-[1rem,1fr] items-center gap-2 whitespace-nowrap px-3 py-1',
'rounded-full bg-gradient-to-t from-gray-800 from-50% to-gray-700 ring-1 ring-inset ring-white/10',
'[--move-x-from:-100%] [--move-x-to:calc(100%+100cqw)] [animation-iteration-count:infinite] [animation-name:move-x] [animation-play-state:paused] [animation-timing-function:linear] group-hover:[animation-play-state:running]',
)}
>
<img alt="" src={src} className="size-4" />
<span className="text-sm/6 font-medium text-white">{label}</span>
</div>
)
}
export function LogoTimeline() {
return (
/* ... */
<Row>
<Logo
label="Loom"
src="./logo-timeline/loom.svg"
className="[animation-delay:-26s] [animation-duration:30s]"
/>
<Logo
label="Gmail"
src="./logo-timeline/gmail.svg"
className="[animation-delay:-8s] [animation-duration:30s]"
/>
</Row>
/* ... */

这种方法使得我们无需在JavaScript中跟踪播放状态,我们只需使用group-hover:[animation-play-state:running]类来在父元素悬停时启动动画。

正如你可能注意到的,我们在这个组件中使用了一堆任意属性来设置各个animation属性,因为这些实用工具在今天的Tailwind中并不存在。这正是创建这些模板的好处——它帮助我们发现了Tailwind CSS中的盲点。谁知道呢,也许我们会在v4.0中看到这些实用工具的增加!


深思熟虑的可重用性

设计这样的SaaS模板最棘手的部分是想出用户可以在其产品中轻松应用的交互元素。买到一个模板却发现它对实例内容过于具体,无法用于自己的项目,那是最糟糕的事情了。

我们想出了一些大多数SaaS产品可能具有的核心图形元素。比如带有标记的地图、logo集群、键盘——这些东西可以应用于许多不同的功能。因为我们希望它们易于重用到您的产品中,所以我们在代码中构建了许多,并为它们设计了不错的API。

例如,logo集群具有一个简单的API,允许您传入自己的logo,调整它们的位置和悬停动画以匹配。

<Logo src="./logo-cluster/dribbble.svg" left={285} top={20} hover={{ x: 4, y: -5, rotate: 6, delay: 0.3 }} />

键盘快捷键部分是另一个很好的例子。只需将一个键名数组传递给Keyboard组件,即可添加您自己的快捷键,并且因为每个键都是一个组件,您可以轻松添加自定义键或更改布局。

<Keyboard highlighted={["F", "M", "L"]} />

其实在代码中构建一个键盘其实需要不少工作,但至少您现在再也不用自己去发现这个了。

当然,我们还留出了您插入自己产品截图的地方。这一部分的样子是为了我们的朋友们 SavvyCal 定制的,使用了相同的交互组件。

Radiant作为SavvyCal

由CMS驱动

通常我们在向模板添加博客时只是使用MDX,但这次我们想尝试使用无头CMS,尝试一下不同的东西。我们决定尝试使用Sanity ,因为在对我们的观众进行投票后,听到了很多好评。

与创建文件、进行提交和手动管理图片和其他内容不同,CMS让您可以通过其UI处理所有内容,因此即使是非开发人员也可以轻松参与。

Sanity Studio

像Sanity这样的无头CMS的一个很酷的功能是您获得的内容是结构化格式的,因此与MDX类似,您可以将元素映射到自己的自定义组件,以处理所有的排版样式。

<PortableText
value={post.body}
components={{
block: {
normal: ({ children }) => <p className="my-10 text-base/8 first:mt-0 last:mb-0">{children}</p>,
h2: ({ children }) => (
<h2 className="mt-12 mb-10 text-2xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">
{children}
</h2>
),
h3: ({ children }) => (
<h3 className="mt-12 mb-10 text-xl/8 font-medium tracking-tight text-gray-950 first:mt-0 last:mb-0">
{children}
</h3>
),
blockquote: ({ children }) => (
<blockquote className="my-10 border-l-2 border-l-gray-300 pl-6 text-base/8 text-gray-950 first:mt-0 last:mb-0">
{children}
</blockquote>
),
},
types: {
image: ({ value }) => (
<img className="w-full rounded-2xl" src={image(value).width(2000).url()} alt={value.alt || ""} />
),
},
/* ... */
}}
/>

使用CMS还意味着您的所有资产,如图片,都由您托管,并且您可以动态控制图像的大小、质量和格式。

<div className="text-sm/5 max-sm:text-gray-700 sm:font-medium">
{dayjs(post.publishedAt).format('dddd, MMMM D, YYYY')}
</div>
{post.author && (
<div className="mt-2.5 flex items-center gap-3">
{post.author.image && (
<img
className="aspect-square size-6 rounded-full object-cover"
src={image(post.author.image).width(64).height(64).url()}
alt=""
/>
)}
<div className="text-sm/5 text-gray-700">
{post.author.name}
</div>
</div>
)}

正如在Markdown中使用前置内容一样,您还可以使用自定义字段丰富内容。例如,我们在博客文章架构中添加了一个featured布尔字段,以便您可以在博客的特殊部分突出显示某些帖子。

Radiant博客

Sanity尤其是一个付费产品,但它有一个相当慷慨的免费套餐,这足以供您玩耍。如果您想尝试不同的无头CMS,我认为我们在这里设置的Sanity集成仍然是一个很好的示例,展示了如何与其他工具连接。


这就是 Radiant! 了解它的内部运作,尝试一下,告诉我们您的看法。

像我们所有的模板一样,它包含在一次性购买的Tailwind UI全访问许可证中,这是支持我们在Tailwind CSS上工作的最佳方式,让我们能够继续为您构建出色的产品,未来几年内不断创新。

Get all of our updates directly to your inbox.
Sign up for our newsletter.