如何使用 Cloudinary 和 Next.js 构建个性化的图片社交分享应用
由 Mux 主办的 DEV 全球展示挑战赛:展示你的项目!
你见过Contentful 的活动网站吗?该网站可以生成可定制和可分享的门票图片,我们已将其发布在年度大会Fast Forward上。
随着数字化领域的不断发展,你可能已经在社交媒体上看到过一些有趣的个性化活动门票,例如2021年GraphQL Conf和Next.js Conf的门票。我非常喜欢这个创意——不仅仅是因为它有趣,更重要的是,它展现了Web开发领域中存在着许多优质的低成本服务和功能。
在这篇文章中,我们将使用Next.js和Cloudinary构建一个前端应用程序,该应用程序可以根据 URL 参数创建个性化的票据图像,以便在 Twitter 和 LinkedIn 上分享。
我们还会配置应用程序,使其对持票人和查看票券的人有不同的行为。这种方法的妙处在于,动态图像分享的可能性是无限的。之前,我写过一篇关于使用 Puppeteer 和 Node.js 截取网页屏幕截图并生成动态 Open Graph 图像以进行社交分享的三种方法的文章。然而,使用 Cloudinary 构建此功能要简单得多,所以我正在考虑将博客上的 Open Graph 图像也改用这种方法!🙈
三种使用 Puppeteer 和 Node.js 截取网页屏幕截图并生成动态 Open Graph 图片以用于社交分享的方法
Salma Alam-Naylor 为 Contentful 撰稿 ・ 2021年3月17日
这里先给大家预览一下我们将要构建的内容。URL 中的 name 参数会提供一个名称,该名称将通过 Cloudinary API 嵌入到图像本身,而不是通过 HTML 和 CSS 进行覆盖。我们还会生成一个随机票号,并配置应用程序,使其对非持票观众有不同的行为。
本教程只需要一张你想进行个性化处理的图片。让我们开始吧!
注册 Cloudinary
Cloudinary是一款图像和视频资产管理服务,它提供 API 来即时自定义媒体。您可以为图像添加文本、使用颜色和自定义字体设置样式、裁剪、旋转、调整大小、重新着色、检测人脸……功能非常强大!
前往 Cloudinary 网站,点击免费注册。
确认您的电子邮件地址后,登录 Cloudinary,您将看到欢迎屏幕。
将您的资产上传到 Cloudinary
点击“媒体库”导航项,然后点击右上角的“上传”。选择您的模板图片,稍等片刻,您将在控制面板中看到新的图片素材。
您的资源文件将自动添加后缀后上传。点击资源文件即可在预览窗格中打开,您可以更改文件名,以便稍后在代码中更容易识别图像名称。
我还上传了一些自定义字体到 Cloudinary,以确保图片个性化设置符合 Contentful 的品牌形象。鉴于 Cloudinary API 支持多种 Google 字体,本文不再赘述字体相关内容,但您可以参考Jason Lengstorf 的这篇文章,了解如何通过 Cloudinary 媒体库上传自定义字体。
现在我们的图像资源已安全存储在 Cloudinary 中。让我们开始编写代码吧!
创建一个新的 Next.js 应用
我选择 Next.js 作为此应用程序的框架,以利用 URL 参数进行服务器端渲染,从而实现图像个性化。
要启动一个新的 Next.js 应用程序,请在终端中运行以下命令:
npx create-next-app ticket-app
这条命令会创建一个新目录,其中包含所有入门所需的代码。运行命令后,你应该会在终端中看到以下输出。(我用“/*这里还有更多内容*/”稍微截断了输出,但你要找的是✨完成!)
导航到项目目录的根目录并启动开发服务器:
cd ticket-app
npm run dev
在浏览器中访问https://localhost:3000,即可看到您新创建的 Next.js 应用运行起来。
让我们来创建购票页面吧!
创建您的页面
在 Next.js 应用中,添加到 pages 目录的任何 JavaScript 文件都会成为前端的一个路由。您可以选择index.js在 pages 目录中修改现有文件或创建新文件。在最终实现中,我选择fast-forward.js在 pages 目录下创建了一个路由,以便该应用能够用于未来的活动。为此,我确保所有对 index 页面的请求都被重定向到当前活动页面。在本教程中,我们将使用 index.js 文件,并在根 URL 下提供生成的门票/。
从一张白纸开始
删除 index.js 中的大部分样板代码,直到得到一个干净的空白画布:
// pages/index.js
import Head from "next/head";
/* We'll write some functions here! */
export default function Index() {
/* We'll configure the event data here! */
return (
<main>
<Head>
<title>My awesome event</title>
</Head>
{/* We'll build our page here! */}
</main>
);
}
配置服务器端属性
存储在 Cloudinary 中的图像将根据票主的姓名进行个性化设置,姓名取自 URL 参数。以下是我们开发过程中将使用的 URL。
http://localhost:3000?name=whitep4nth3r
在纯 JavaScript 应用中,你可以在客户端处理 URL 参数来构建页面内容;但使用 Next.js,我们可以直接getServerSideProps()在服务器端使用 URL 参数的值来渲染页面。这样可以避免访客在浏览器读取参数时看到未定义内容的闪烁或显示加载状态。更多关于 `getServerSideProps()` 的信息,请参阅 Next.js 文档。
将以下getServersideProps()函数添加到 index.js 文件的末尾。该函数将接收一个上下文对象作为参数,我们可以从中解构查询参数。我们将把name查询参数显示在工单上,并isShared根据页面是由工单持有人访问还是通过社交媒体分享和访问,来配置页面的显示方式。
接下来,配置 Index 组件以接收nameand isSharedprops。
// pages/index.js
import Head from "next/head";
/* We'll write some functions here! */
export default function Index({ name, isShared }) {
/* We'll configure the event data here! */
return (
/* … */
);
}
export async function getServerSideProps(context) {
const { name, shared } = context.query;
const isShared = shared !== undefined;
// return the properties so they are available in the `Index` component
return {
props: {
name: decodeURI(name),
isShared,
},
};
}
接下来,我们设置一些事件变量,以便在一些地方重复使用,从而避免大量的复制和粘贴。
配置您的活动详情
在 Index 组件中设置以下变量:eventName,,ticketAppUrl和title。description我们稍后会使用这些值。
// pages/index.js
import Head from "next/head";
/* We'll write some functions here! */
export default function Index({ name, isShared }) {
/* Event info config */
const eventName = "My awesome event";
const ticketAppUrl = "https://my-awesome-ticket-app.dev";
const title = `${decodeURIComponent(name)} is Going! | ${eventName}`;
const description = `Join ${name} at ${eventName}. Grab your free ticket on ${ticketAppUrl}.`;
return (
/* ... */
);
}
export async function getServerSideProps(context) {
/* ... */
}
(可选)生成票号(如果您还没有票号)
我没有 Fast Forward 2021 活动的官方票号,但我仍然想在设计中加入一个相对独特的票号,让个性化门票看起来更正式。最终实现的代码可以根据任何给定的字符串生成一个数字,返回值以 000 为前缀。每个唯一的字符串都会生成一个唯一的数字——这种方法的唯一缺点是,如果多个名为“whitep4nth3r”的人都获得了 Fast Forward 的门票,那么他们的票号就会相同。你明白我的意思了吧。🙈
在本教程中,我们可以用它Math.random()来生成票号。
// pages/index.js
import Head from "next/head";
export default function Index({ name, isShared }) {
/* Event info config... */
/* Generate a fake ticket number */
const ticketNo = `000${Math.random().toString().substr(2, 4)}`;
return (
/* ... */
);
}
export async function getServerSideProps(context) {
/* ... */
}
现在数据配置完毕,我们需要使用 Cloudinary 对图像进行个性化设置。让我们开始吧!
个性化您的 Cloudinary 图片
Cloudinary API 允许您通过 URL 参数对图像进行各种自定义。例如,以下是我自己的 Fast Forward 票据生成的 URL。Cloudinary 接受fastforward2021.png带有前缀参数的图像 URL(),参数之间用逗号分隔。
https://res.cloudinary.com/devrelcontentful/image/upload/w_831,h_466,c_fill,q_auto,f_auto,r_20/w_760,c_fit,co_rgb:ffffff,g_south_west,x_46,y_239,l_text:avenirdemi.otf_48:whitep4nth3r/w_760,c_fit,co_rgb:2a3039,a_90,g_north_east,x_84,y_100,l_text:avenirreg.otf_16:NO./w_760,c_fit,co_rgb:2a3039,a_90,g_north_east,x_55,y_140,l_text:avenirreg.otf_56:0007964/fastforward2021.png
URL 由以下部分组成:
- Cloudinary 基本 URL — https://res.cloudinary.com
- Cloudinary 云名称 — devrelcontentful
- 资产类型 —图片/上传
- 宽度 — w_831
- 高度 — h_466
- 裁剪模式 — c_fill
- 自动选择资源格式以获得最佳浏览器体验 — f_auto
- 圆角半径为 20px — r_20
- 文本区域宽度为 760 像素 — w_760
- 文本区域裁剪模式名称 — c_fit
- 文本颜色名称(十六进制代码,不含#号)— ffffff
- 名称文本重力 — g_south_west
- 名称文本位置坐标 — x_46,y_239
- 名称字体和大小 — l_text:avenirdemi.otf_48
- 名称文本值 — :whitep4nth3r
- 票号文本也同样如此。
- 最后,URL 以存储在 Cloudinary 中的图像名称结尾——fastforward2021.png
让我们来看一段用于生成类似 URL 的 JavaScript 代码。乍一看,它可能看起来有点复杂。但是,一旦你理解了它的工作原理,你就会想要抓住一切机会个性化图片!非常感谢 Jason Lengstorf 提供的这个代码库,它为我提供了灵感,并帮助我了解在使用 Cloudinary URL 时遇到的一些常见陷阱。
以下函数generateImageUrl()接受一些必需和可选参数,用于构建一个类似于我们上面讨论的 Cloudinary 图片 URL,从而生成个性化图片。根据您的图片以及您希望如何进行个性化设置,您可能需要调整默认的输入参数generateImageUrl(),尤其是偏移量、颜色、字体大小和重力值。请注意,我使用了“Arial”字体,而不是上面 URL 中使用的自定义字体。
有关如何配置这些值的更多信息,请参阅Cloudinary 图像转换文档。
最后,<img />在 Index 组件中添加一个标签,并添加 `image`src和 `image`alt属性来渲染你的个性化图像。
// pages/index.js
import Head from "next/head";
/* Encode characters for Cloudinary URL */
function cleanText(text) {
return encodeURIComponent(text).replace(/%(23|2C|2F|3F|5C)/g, "%25$1");
}
/* CONFIG vars */
const CLOUD_NAME = "the-claw";
const IMG_WIDTH = 831;
const IMG_HEIGHT = 466;
/* Build the Cloudinary Image URL */
function generateImageUrl({
name,
ticketNo,
imagePublicID,
cloudinaryUrlBase = "https://res.cloudinary.com",
imageWidth = IMG_WIDTH,
imageHeight = IMG_HEIGHT,
textAreaWidth = 760,
ticketNoFont = "Arial",
ticketNoGravity = "north_east",
ticketNoLeftOffset = 55,
ticketNoTopOffset = 140,
ticketNoColor = "2a3039",
ticketNoFontSize = 56,
noFont = "Arial",
noGravity = "north_east",
noLeftOffset = 84,
noTopOffset = 100,
noColor = "2a3039",
noFontSize = 16,
nameFont = "Arial",
nameGravity = "south_west",
nameBottomOffset = 239,
nameLeftOffset = 46,
nameColor = "ffffff",
nameFontSize = 48,
version = null,
}) {
// configure social media image dimensions, quality, and format
const imageConfig = [
`w_${imageWidth}`,
`h_${imageHeight}`,
"c_fill",
"q_auto",
"f_auto",
"r_20",
].join(",");
// configure the name text
const nameConfig = [
`w_${textAreaWidth}`,
"c_fit",
`co_rgb:${nameColor || textColor}`,
`g_${nameGravity}`,
`x_${nameLeftOffset}`,
`y_${nameBottomOffset}`,
`l_text:${nameFont}_${nameFontSize}:${cleanText(name)}`,
].join(",");
//configure the "NO." text
const noConfig = [
[
`w_${textAreaWidth}`,
"c_fit",
`co_rgb:${noColor}`,
`a_90`,
`g_${noGravity}`,
`x_${noLeftOffset}`,
`y_${noTopOffset}`,
`l_text:${noFont}_${noFontSize}:NO.`,
].join(","),
];
// configure the ticketNo text
const ticketNoConfig = ticketNo
? [
`w_${textAreaWidth}`,
"c_fit",
`co_rgb:${ticketNoColor}`,
`a_90`,
`g_${ticketNoGravity}`,
`x_${ticketNoLeftOffset}`,
`y_${ticketNoTopOffset}`,
`l_text:${ticketNoFont}_${ticketNoFontSize}:${cleanText(ticketNo)}`,
].join(",")
: undefined;
// combine all the pieces required to generate a Cloudinary URL
const urlParts = [
cloudinaryUrlBase,
CLOUD_NAME,
"image",
"upload",
imageConfig,
nameConfig,
noConfig,
ticketNoConfig,
version,
imagePublicID,
];
// remove any falsy sections of the URL (e.g. an undefined version)
const validParts = urlParts.filter(Boolean);
// join all the parts into a valid URL to the generated image
return validParts.join("/");
}
export default function Index({ name, isShared }) {
/* Event info config... */
/* Generate a fake ticket number... */
/* Build the Cloudinary image URL */
const imageUrl = generateImageUrl({
name: name,
ticketNo: ticketNo,
imagePublicID: "ticket_template.png",
});
return (
<main>
<Head>
{/* … */}
</Head>
<img alt="My ticket" src={imageUrl} />
</main>
);
}
export async function getServerSideProps(context) {
/* ... */
}
哇!我们已经通过 Cloudinary API 获取到了个性化图片的 URL!接下来,我们将利用这个 URL,在与会者在社交媒体上分享您的活动时,向他们展示门票预览。
配置 Open Graph 元数据以进行社交分享
你在 Twitter 和 LinkedIn 上看到的那些门票预览功能,其背后的强大功能都归功于Open Graph 协议的神奇之处。
点击上方推文即可查看 Open Graph 预览的实际效果!
Open Graph (OG) 协议于 2010 年在 Facebook 创建,旨在使网页链接成为富对象,使其具有与 Facebook 上发布的其他内容类似的功能和外观。
Open Graph 元标签用于<head>HTML 页面的 <head> 标签中,以便将网页信息暴露给社交媒体平台和其他能够解析 URL 元数据的应用程序。OG 元标签在 HTML 中通过以 <head> 为前缀的属性进行标识og。
<meta property="og:image" content="https://example.com/image.png" />
OG 元标签还可以用来根据网页分享的平台自定义网页的外观。例如,Twitter 基于 OG 协议推出了自己的定制版本,以下代码指示 Twitter 显示网页预览的大图。
<meta name="twitter:card" content="summary_large_image" />
<meta
name="twitter:image"
content="https://example.com/image.png"
/>
Next Head 组件(在文件顶部导入并在 Index 组件内渲染)会将我们定义的 meta 标签添加到生成的 HTML 页面的头部。
ogUrl在 Index 组件的 return 语句上方定义一个变量${ticketAppUrl}?name=${name}&shared=true。请注意,我们在 URL 末尾添加了第二个 URL 参数——shared该参数我们getSeverSideProps()之前已经配置过。这在接下来的几个步骤中会非常重要。
在 Next Head 组件标签内添加相关的 OG 元标签,即可在 Twitter 和 LinkedIn 上显示带有标题和描述的精美图片预览。您会注意到,我们充分利用了之前定义的事件配置变量。
// pages/index.js
import Head from "next/head";
/* ... */
export default function Index({ name, isShared }) {
/* Event info config... */
/* Generate a fake ticket number... */
/* Build the Cloudinary image URL... */
/* Configure Open Graph URL */
const ogUrl = `${ticketAppUrl}?name=${name}&shared=true`;
return (
<main>
<Head>
<title>{title}</title>
<meta name="description" content={description} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:site" content={ticketAppUrl} />
<meta name="twitter:creator" content="@your_twitter_username" />
<meta property="og:url" content={ogUrl} />
<meta property="og:type" content="website" />
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:locale" content="en_US" />
<meta property="og:image" content={imageUrl} />
<meta property="og:image:alt" content={eventName} />
<meta property="og:image:width" content={IMG_WIDTH} />
<meta property="og:image:height" content={IMG_HEIGHT} />
<meta property="og:site_name" content={eventName} />
</Head>
/* ... */
</main>
);
}
export async function getServerSideProps(context) {
/* ... */
}
现在,让我们创建这些社交分享链接,让您的与会者对您的活动充满期待!
添加 Twitter 和 LinkedIn 社交分享链接
这就是我们上面所有魔法汇聚的地方。我们将创建一个 Twitter Web Intent URL 和一个 LinkedIn 分享 URL,当你的网站上线后(这才是关键!)og:image ,它们会通过HTML 中的Open Graph 元标记,将你通过 Cloudinary 个性化的图片导入其中<head>。
以下代码示例展示了如何创建 Twitter 和 LinkedIn 分享链接。需要注意以下几点:
- 如果你想在推文中使用换行符(
/n),请确保将推文文本用括号括起来。encodeURIComponent() - 请务必
&shared=true在分享链接中包含这些信息——下一步您就会明白为什么了! - 请务必将 LinkedIn 分享链接中的所有等号 (=) 转换为 HTML 字符代码
%3D,否则链接将无法正常工作。
最后,在图像标签下方的 Index 组件中添加锚点链接,链接内容为已配置的 Twitter 和 LinkedIn 分享 URL。
// pages/index.js
import Head from "next/head";
/* ... */
export default function Index({ name, isShared }) {
/* Event info config... */
/* Generate a fake ticket number... */
/* Build the Cloudinary image URL... */
/* Configure Open Graph URL... */
/* Twitter Config */
const tweetText = encodeURIComponent("I just got my ticket to an awesome event!\n\nGrab your free ticket and join me!\n\n",
);
const twitterShareUrl = encodeURIComponent(`${ticketAppUrl}?name=${name}&shared=true`);
const twitterShareHref = `https://twitter.com/intent/tweet?url=${twitterShareUrl}&text=${tweetText}`;
/* LinkedIn Config */
const linkedInShareUrl = `${ticketAppUrl}?name%3D${name}&shared%3Dtrue`;
const linkedInShareHref = `https://www.linkedin.com/sharing/share-offsite/?url=${linkedInShareUrl}`;
return (
<main>
<Head>
{/* ... */}
</Head>
<img alt="My ticket" src={imageUrl} />
<a href={twitterShareHref} target="_blank" rel="noreferrer">
Share on Twitter
</a>
<a href={linkedInShareHref} target="_blank" rel="noreferrer">
Share on LinkedIn
</a>
</main>
);
}
export async function getServerSideProps(context) {
/* ... */
}
只剩最后一步了。最后,我们来配置一下通过社交媒体链接访问您网站的访客的网页。
配置您的网页以适应社交点击
还记得isShared我们之前拍到的那个道具getServerSideProps()吗?它现在要派上用场了。
对比我的 Fast Forward 门票确认链接和下面 Twitter 上分享的链接。
我的机票确认信息
这是仅包含 name 参数的完整 URL:https://tickets.contentful.com/fastforward2021? name=Salma
人们点击我推文中的链接后看到的内容
name这是包含参数的完整 URL shared:https://tickets.contentful.com/fastforward2021? name=Salma&shared=true
使用以下代码,可以根据isSharedIndex 组件接收到的参数值配置不同的标题和副标题。此外,未参加活动的用户将看到注册活动的行动号召,而不是在社交媒体上分享活动信息。
// pages/index.js
import Head from "next/head";
/* ... */
export default function Index({ name, isShared }) {
/* ... */
/* Page text config */
const headline = isShared ? `${name} is going!` : "You're in!";
const subtitle = isShared
? `Don't miss out! Sign up to register and join ${name} at ${eventName}.`
: `Add the event to your calendar and invite your friends to join you at ${eventName}.`;
return (
<main>
<Head>
{/* ... */}
</Head>
<h1>{headline}</h1>
<p>{subtitle}</p>
{isShared && <a href="https://my-awesome-ticket-app.dev/sign-up">Sign up!</a>}
{!isShared && (
<>
<a href={twitterShareHref} target="_blank" rel="noreferrer">
Share on Twitter
</a>
<a href={linkedInShareHref} target="_blank" rel="noreferrer">
Share on LinkedIn
</a>
</>
)}
{/* ... */}
</main>
);
}
export async function getServerSideProps(context) {
/* ... */
}
拍摄结束!
别忘了——为了确保你的 Open Graph 图片能够正常工作,你需要将应用部署到线上 URL。Vercel 让你的 Next.js 应用在几秒钟内就能轻松上线。注册 Vercel并通过 GitHub 连接你的项目——一切就绪!
Cloudinary 真是太神奇了,我迫不及待地想进一步探索它的更多可能性。更重要的是,我期待未来能为我的 Discord 和直播社区举办的各种活动开发类似的应用程序。如果你想查看本文中展示的代码,请访问此处的 GitHub 代码库(它完全不包含 CSS,所以你可以尽情发挥你的设计创意!)。
最后,别忘了注册Fast Forward 2021领取你的免费门票!我们准备了为期三天的活动,专为那些致力于打造下一代数字体验的人士而设,包括开发者、架构师、工程师、创意人员和技术爱好者。别忘了在社交媒体上分享你的门票——现在你知道怎么用了,肯定会更有乐趣!😉
文章来源:https://dev.to/whitep4nth3r/how-to-build-a-personalized-image-social-sharing-app-with-cloudinary-and-next-js-2640











