With virtual and physical events happening every weekend, the need to have interactive features such as dynamic tag generators has increased.
In this tutorial, we learn how we can generate virtual tags dynamically. This tutorial is fully inspired by Jamstack Conf’s badge maker. The resources and the designs are obtained from their website. Feel free to register as well ;-D
The final project can be viewed on Codesandbox.
You can find the full source code on my Github repository.
To view the tag maker we will be analyzing, proceed to https://virtual-event-tag.netlify.app/.
To be able to follow along effectively with this tutorial, knowledge of HTML, CSS, and JavaScript will be required. Knowledge of Vue.Js is recommended but not required.
Nuxt.JS is an intuitive Vue.Js framework that boosts developer productivity and the end-user experience. To set up our project, we will use the create-nuxt-app utility. Ensure sure you have installed npx installed. It ships by default with yarn, npm v5.2+, or npm v6.1+.
Enter your preferred working directory and run the following command:
yarn create nuxt-app nuxtjs-conference-tag-generator
# OR
npx create-nuxt-app nuxtjs-conference-tag-generator
# OR
npm init nuxt-app nuxtjs-conference-tag-generator
Code language: PHP (php)
The above command will result in a series of setup questions. Here are our recommended defaults:
Project name: nuxtjs-conference-badge-generator
Programming language: Javascript
Package manager: Yarn
UI framework: Tailwind CSS
Nuxt.js modules: N/A
Linting tools: N/A
Testing frameworks: None
Rendering mode: Universal (SSR / SSG)
Deployment target: Server (Node.js hosting)
Development tools: N/A
What is your Github username? musebe
Version control system: Git
Once the setup is complete, you may run your project:
cd nuxtjs-conference-tag-generator
yarn dev
# OR
npm run dev
Code language: PHP (php)
The above command will run your project on http://localhost:3000.
Cloudinary is a media management platform that enables us to do much more than just rendering. To get started, ensure you have an account. If you do not have one you may register here.
@nuxtjs/cloudinary is the recommended plugin for using Cloudinary in our Nuxt.Js projects. To install it, run the following command:
yarn add @nuxtjs/cloudinary
# OR
npm install @nuxtjs/cloudinary
Code language: CSS (css)
Add @nuxjs/cloudinary
as a dependency in the modules
section of your nuxt.config.js
// nuxt.config.js
export default {
...
modules: [
'@nuxtjs/cloudinary'
]
...
}
Code language: JavaScript (javascript)
Add cloudinary
section in your nuxt.config.js
to configure your Cloudinary module:
// nuxt.config.js
...
cloudinary: {
cloudName: process.env.NUXT_CLOUDINARY_CLOUD_NAME,
useComponent: true
}
Code language: JavaScript (javascript)
As visible in the above configure, we make use of environmental variables. These are variables stored outside of our codebase as they are dependant on the development environment. We may also use them to store sensitive details we do not want to be exposed to in our codebase. To set up our environmental variable, we first create the .env file
touch .env
Code language: CSS (css)
We then obtain our Cloudinary cloud name from the console and add it to our .env
file.
<!-- .env -->
NUXT_CLOUDINARY_CLOUD_NAME=<cloudinary-cloud-name>
When you input your name and select your images, they are rendered immediately on the template tag. This is even before any network requests happen. In this section, we analyze how we can get this done.
The file input
elements for selecting the then and now images are hidden and triggered by a click on the label
elements which contains the viewable images. This is displayed in the below code snippet:
<!-- pages/index.vue -->
...
<div>
<div>
<input
type="file"
class="hidden"
ref="thenInput"
@change="setThen"
/>
<label
@click="$refs.thenInput.click()"
>
<img
src="https://jamstackconf.com/badge/img/btn-image-upload.svg"
/>
</label>
<p>How it started</p>
</div>
<div>
<input
type="file"
class="hidden"
ref="nowInput"
@change="setNow"
/>
<label
@click="$refs.nowInput.click()"
>
<img
src="https://jamstackconf.com/badge/img/btn-image-upload.svg"
/>
</label>
<p class="m-3 uppercase">How it's going</p>
</div>
</div>
...
Code language: HTML, XML (xml)
Once the images have been selected, the change
event is triggered which calls the setThen
and setNow
methods.
// pages/index.vue
<script>
export default {
data() {
return {
...
then: {
file: null,
url: null,
...
},
now: {
file: null,
url: null,
...
},
};
},
methods: {
setThen(e) {
this.then.file = e.target.files[0];
this.then.url = URL.createObjectURL(this.then.file);
},
setNow(e) {
this.now.file = e.target.files[0];
this.now.url = URL.createObjectURL(this.now.file);
},
...
},
};
</script>
Code language: HTML, XML (xml)
The setThen
and setNow
methods grab the files and save them in the state. Local URLs are then generated using the URL.createObjectURL
method and this is also saved in the local state.
To set the name, we use the v-model
directive to synchronize the value of the input into the name variable in the state.
<!-- pages/index.vue -->
...
<input
type="text"
placeholder="Enter your full name"
v-model="name"
/>
...
Code language: HTML, XML (xml)
We have to create the variable in our local state.
// pages/index.vue
<script>
export default {
data() {
return {
name: null,
...
};
},
...
Code language: HTML, XML (xml)
To render our selected images and name on the badge template, we will make use of the position CSS property.
We will use absolute
and relative
positioning to overlay the images and the name on the badge.
<!-- pages/index.vue -->
...
<div id="badge" class="relative">
<img
src="https://jamstackconf.com/badge/img/blue-badge-bg-2.png"
class="absolute w-full"
/>
<div id="then" class="absolute">
<img v-if="then.url" :src="then.url" />
</div>
<div id="now" class="absolute">
<img v-if="now.url" :src="now.url" />
</div>
<h1
id="name"
>
{{ name ? name: "An Excited Jamstacker" }}
</h1>
</div>
...
Code language: HTML, XML (xml)
To ensure that the positioning is right even as the viewport changes, we specify the specific pixel values.
/* pages/index.vue */
...
#badge {
width: 433px;
height: 920px;
}
#badge #then {
width: 112px;
top: 479px;
left: 56px;
height: 112px;
overflow: hidden;
}
#badge #now {
width: 150px;
top: 405px;
left: 220px;
height: 150px;
overflow: hidden;
}
#badge #name {
top: 650px;
left: 50px;
width: 350px;
text-align: left;
}
...
Code language: CSS (css)
<!-- pages/index.vue -->
...
<button @click="submit">
Laminate!
</button>
...
Code language: HTML, XML (xml)
Once the Laminate
button is clicked, the submit
method is called. This method is responsible for uploading the files to Cloudinary.
// pages/index.vue
...
async readData(f) {
return new Promise((resolve) => {
const reader = new FileReader();
reader.onloadend = () => resolve(reader.result);
reader.readAsDataURL(f);
});
},
async submit() {
this.uploading = true;
const thenData = await this.readData(this.then.file);
this.then.cloudinary = await this.$cloudinary.upload(thenData, {
upload_preset: "default-preset",
folder: "nuxtjs-confrence-tag-generator",
});
const nowData = await this.readData(this.now.file);
this.now.cloudinary = await this.$cloudinary.upload(nowData, {
upload_preset: "default-preset",
folder: "nuxtjs-confrence-tag-generator",
});
this.uploading = false;
const url =
"/tag?name=" +
this.name +
"&then=" +
this.then.cloudinary.public_id +
"&now=" +
this.now.cloudinary.public_id;
this.$router.push(url);
},
...
Code language: JavaScript (javascript)
The readData
method reads the file and returns the base64
data. This is uploaded to the nuxtjs-conference-tag-generator
folder in Cloudinary using the default-preset
upload preset. Upload presets are a set of preconfigured settings to be applied to files uploaded. It is a requirement for unsigned client-side upload. To create an upload preset, click here.
We recommend using the following settings:
Unique filename: true
Delivery type: upload
Access mode: public
Once the uploads are complete for both the then
and the now
images, we send their public_id
s together with the name as query parameters to the \tag
route. We will render the laminated tag in this route.
To generate the tag, we grab the query date we just sent using the $route
property within our page. We apply transformations on the template to render the final tag
<!-- pages/tag.vue -->
...
<cld-image
public-id="nuxtjs-confrence-tag-generator/template/blue-badge-bg-2.png"
fetchFormat="auto"
quality="auto"
loading="lazy"
width="433px"
height="920px"
>
<cld-transformation
:overlay="`${$route.query.then.replace('/', ':')}`"
width="110"
height="110"
x="56"
y="479"
gravity="north_west"
/>
<cld-transformation
:overlay="`${$route.query.now.replace('/', ':')}`"
width="150"
height="150"
x="63"
y="404"
gravity="north_east"
/>
<cld-transformation
:overlay="`text:Arial_36_bold_stroke_letter_spacing_2:${$route.query.name},co_rgb:FFFFFF,bo_5px_solid_black`"
y="225"
gravity="west"
x="60"
/>
</cld-image>
...
Code language: HTML, XML (xml)
We replace the slashes \
in the public_id
s with colons :
to enable the SDK to find the correct images. We then position the images in the right place with the correct width to ensure they are visually correct. We add a black 5px solid border to the text as well (bo_5px_solid_black
).
<!-- pages/tag.vue -->
...
<button @click="download" >
Download
</button>
...
Code language: HTML, XML (xml)
To download the tag, we grab the img
element from the container div (with the badge-cont
id). We then open the image src
in a new tab. Here is the code snippet.
// pages/tag.vue
...
download() {
const src = document.getElementById("badge-cont").children[0].src;
window.open(src);
},
...
Code language: JavaScript (javascript)
<!-- pages/tag.vue -->
...
<button @click="share" >
Show off your badge!
</button>
...
Code language: HTML, XML (xml)
To share tag, we create and open a simple Twitter share link. The link contains the Tweet text as well as a link to the generated image.
// pages/tag.vue
...
share() {
const src = document.getElementById("badge-cont").children[0].src;
const url = `https://twitter.com/intent/tweet?text=Got my badge ready for @jamstackconf 2021! See you there? ${src}`;
window.open(url);
},
...
Code language: JavaScript (javascript)
In the above tutorial, we have learned how to preview, create, download and share custom tags. Another use case of this knowledge is generating speaker badges, speaker introduction documents amongst other identifiers.
Feel free to explore the Cloudinary Vue.Js Integration Documentation to see the full capabilities of Cloudinary’s platform as well as its corresponding SDKs.