Skip to content

Virtual event badge using Cloudinary and Nuxt.js

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_ids 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_ids 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.

Back to top

Featured Post