This tutorial will walk you through creating a beautiful animated rating system using Vue.js, Lottie animations, and Google Apps Script.

Important note:

This tutorial covers a basic implementation that is not ready to be published online. You should consider security measures, as this implementation allows anyone to submit unlimited ratings, spam, etc. There is no data validation, and anyone with the Apps Script URL can send requests.

The only use case should be locally on your own devices.

What We’ll Build

Customer Ratings Preview

  • An animated customer satisfaction rating page with 5 different emoji reactions.
  • Smooth animations and transitions.
  • Automatic data saving to Google Sheets.
  • Mobile-responsive design.

I assume you are familiar with HTML, a bit of Javascript (and Vuejs basics), and have a Google account to use for Google Sheets Integration. Even if you don’t you can probably get this working.

I am going to be using TailwindCss (You can follow the official Getting Started guide here), You can use vanila CSS if you want to.

Step 1: Creating the HTML Structure

Let’s start with our index.html file. We’ll need to include Vue.js and the Lottie player component:

<!DOCTYPE html>
<html lang="en">
	<head>
		<meta charset="UTF-8" />
		<meta name="viewport" content="width=device-width, initial-scale=1.0" />
		<title>Customer Feedback</title>
		<link rel="stylesheet" href="./css/style.css" />
		<!-- Google font; Sour Gummy -->
		<link rel="preconnect" href="https://fonts.googleapis.com" />
		<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
		<link
			href="https://fonts.googleapis.com/css2?family=Sour+Gummy&display=swap"
			rel="stylesheet"
		/>
		<!-- Include Vue.js -->
		<script src="https://unpkg.com/vue@3/dist/vue.global.js"></script>
		<!-- Include Lottie Player -->
		<script
			src="https://unpkg.com/@dotlottie/player-component@2.7.12/dist/dotlottie-player.mjs"
			type="module"
		></script>
	</head>
	<body>
		<main id="app" class="sour-gummy">
			<!-- Content will go here -->
		</main>
	</body>
</html>

Step 2: Setting Up the Rating Scale

The rating scale consists of five emotions, from very dissatisfied to very satisfied. Let’s create our Vue.js application with the rating scale data:

const { createApp } = Vue;
createApp({
	data() {
		return {
			ratingScale: [
				{
					label: "Very Dissatisfied",
					slug: "very-dissatisfied",
					lottie:
						"https://fonts.gstatic.com/s/e/notoemoji/latest/1f629/lottie.json",
				},
				{
					label: "Dissatisfied",
					slug: "dissatisfied",
					lottie:
						"https://fonts.gstatic.com/s/e/notoemoji/latest/2639_fe0f/lottie.json",
				},
				// ... add more rating emotions
			],
			currentRating: "",
			submitted: false,
			showSubmitFeedbackPanel: false,
			GOOGLE_SCRIPT_URL: "YOUR_APP_SCRIPT_URL",
		};
	},
	// ... methods will go here
}).mount("#app");

Step 3: Creating the Rating Interface

Add this HTML structure inside your main element. We will use VueJs to loop through the ratingScale array we created. You may see a warning in your browser console because dotlottie-player is not a VueJs component. You can ignore it.

<h1 class="text-5xl text-black font-semibold mb-5">How was your experience?</h1>
<span class="block text-2xl text-black/60 pb-24">
	Let us know how we did!
</span>

<div class="flex flex-wrap items-center justify-center gap-10 pb-20">
	<button
		v-for="item in ratingScale"
		:key="item.label"
		class="w-40 flex flex-col items-center gap-3"
		:class="[
            {'opacity-30': checkCurrentRating(item.slug)},
            {'animate-scaleAndDisappear': item.slug === currentRating}
        ]"
		:disabled="checkCurrentRating(item.slug)"
		@click="setCurrentRating(item)"
	>
		<dotlottie-player
			:src="item.lottie"
			background="transparent"
			speed="1"
			direction="1"
			playMode="normal"
			loop
			autoplay
		></dotlottie-player>
		<span class="text-xs text-gray-500">{{ item.label }}</span>
	</button>
</div>

Step 4: Adding the Feedback Submission Logic

Add these methods to your Vue application. I wanted the date to be formatted in a specific way, that is why there is so much date formating going on in this code. You don’t really need to do it this way, you can just use timestamp: new Date().toISOString() in the body: instead of formattedDateTime.

I have added a few timeouts setTimeout in setCurrentRating to show a Thank you message to the user and then reset the screen. With basic fade animations using CSS. Cos I wanted to load this HTML file into a Tablet and place it on a desk and there would be no need to reset the form after a customer leaves a rating.

methods: {
    async submitRating(rating) {
        try {
            const today = new Date();
            const formattedDateTime = `${(today.getMonth() + 1)
                .toString()
                .padStart(2, "0")}/${today
                .getDate()
                .toString()
                .padStart(2, "0")}/${today.getFullYear()} ${today
                .getHours()
                .toString()
                .padStart(2, "0")}:${today
                .getMinutes()
                .toString()
                .padStart(2, "0")}:${today
                .getSeconds()
                .toString()
                .padStart(2, "0")}`;

            const response = await fetch(this.GOOGLE_SCRIPT_URL, {
                method: "POST",
                mode: "no-cors",
                headers: {
                    "Content-Type": "application/json",
                },
                body: JSON.stringify({
                    rating: rating,
                    timestamp: formattedDateTime,
                }),
            });

            this.message = "Thank you for your feedback!";
        } catch (error) {
            console.error("Error:", error);
            this.message = "Error submitting feedback. Please try again.";
        }
    },
    setCurrentRating(item) {
        this.currentRating = item.slug;
        this.submitRating(item.label);

        // Animation timing sequence
        setTimeout(() => {
            this.submitted = true;
        }, 1000);

        setTimeout(() => {
            this.showSubmitFeedbackPanel = true;
        }, 1300);

        setTimeout(() => {
            this.showSubmitFeedbackPanel = false;
        }, 4800);

        setTimeout(() => {
            this.currentRating = "";
            this.submitted = false;
        }, 5100);
    }
}

Step 5: Setting Up Google Sheets Integration

Now, let’s go through the step-by-step process to set up the google sheet and Apps Script:

  1. Create a Google Sheet
  • Open Google Drive and create a new Google Sheet.
  • Rename it to “Ratings”.
  • Add headers in row 1: “Timestamp” | “Rating”.
  1. Set up Google Apps Script
  • In your Google Sheet, go to Extensions > Apps Script.
  • Replace the contents of Code.gs with this code:
function doPost(e) {
	const sheet = SpreadsheetApp.getActiveSheet();
	const data = JSON.parse(e.postData.contents);

	sheet.appendRow([data.timestamp, data.rating]);

	return ContentService.createTextOutput("Success");
}
  1. Deploy the Apps Script
  • Click on “Deploy” > “New deployment”
  • Click “Select type” > “Web app”
  • Set the following:
    • Description: “Rating Form”
    • Execute as: “Me”
    • Who has access: “Anyone”
  • Click “Deploy”
  • Authorize the application when prompted
  • Copy the deployment URL and update your GOOGLE_SCRIPT_URL in the Vue.js code

Step 6: Adding Styles

If you are using Tailwindcss like me, and have already followed the Tailwindcss Getting Started guide.

Here are my tailwind.css and tailwind.config.js file

/* tailwind.css */
@tailwind base;
@tailwind components;
@tailwind utilities;

.sour-gummy {
	font-family: "Sour Gummy", sans-serif;
	font-optical-sizing: auto;
	font-weight: 400;
	font-style: normal;
	font-variation-settings: "wdth" 100;
}
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
	content: ["./public/**/*.{html,js}"],
	theme: {
		extend: {
			keyframes: {
				scaleAndDisappear: {
					"0%": { transform: "scale(1)", opacity: "1", top: "0", left: "0" },
					"50%": {
						transform: "scale(3)",
						opacity: "1",
					},
					"100%": {
						transform: "scale(5)",
						opacity: "0",
					},
				},
			},
			animation: {
				scaleAndDisappear: "scaleAndDisappear 1s ease-out forwards",
			},
		},
	},
	plugins: [],
};

Conclusion

You now have a beautiful, interactive customer satisfaction rating system that saves responses directly to Google Sheets! The combination of Vue.js for reactivity, Lottie for animations, and Google Sheets for data storage makes this a powerful yet simple solution for collecting customer feedback.

Click to download the project files