DEV Community

Bridget Amana
Bridget Amana

Posted on • Edited on

4 1 1

How to Add a Clickable Visitor Counter to Your Website

I was thinking of something fun and interactive to add to my portfolio, and a clickable counter that visitors could use to leave a little "I was here" message felt like a great idea. It’s simple, engaging, and a great way to make your website feel a bit more personal.

If this sounds like something you'd like to create, this guide will walk you through it step by step. Let’s dive in!

1. Setting Up JSONBin

We need a place to store the total number of clicks and the IP addresses of visitors who have clicked (so they can’t click more than once). For this, we’ll use JSONBin.io, a free online service that lets you store and retrieve JSON data.

Follow these steps to set it up:

  1. Go to JSONBin.io and sign up for a free account.
  2. Create a new bin with this initial data:
   {
     "totalClicks": 0,
     "clickedIPs": []
   }
Enter fullscreen mode Exit fullscreen mode
  1. After saving, copy the Bin ID from the bin details page.
  2. Go to your account settings and generate an API Key.
  3. Replace YOUR_BIN_ID and YOUR_API_KEY in the JavaScript code with your actual Bin ID and API Key.

This will act as our backend storage, keeping track of how many times the button has been clicked and by whom.

2. The HTML: Building the Counter

Now, let’s set up the HTML structure for our view counter. We'll use a simple button with an eye icon to make it visually engaging.

<div class="eye-counter">
  <button class="eye-button" aria-label="View counter">
    <svg viewBox="0 0 24 24" width="24" height="24" class="eye-icon">
      <path d="M12 4.5C7 4.5 2.73 7.61 1 12c1.73 4.39 6 7.5 11 7.5s9.27-3.11 11-7.5c-1.73-4.39-6-7.5-11-7.5zM12 17c-2.76 0-5-2.24-5-5s2.24-5 5-5 5 2.24 5 5-2.24 5-5 5zm0-8c-1.66 0-3 1.34-3 3s1.34 3 3 3 3-1.34 3-3-1.34-3-3-3z"/>
    </svg>
    <span class="view-count">0</span>
  </button>
</div>
Enter fullscreen mode Exit fullscreen mode

3. The CSS: Styling the Counter

To make our counter look clean and centered, we’ll add some simple styles:

.eye-counter {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
}

.eye-button {
  background: none;
  border: none;
  cursor: pointer;
  display: flex;
  align-items: center;
  gap: 0.5rem;
  padding: 0.5rem 1rem;
  border-radius: 20px;
  background-color: rgba(255, 255, 255, 0.1);
  color: #333;
  transition: transform 0.3s ease, background-color 0.3s ease;
}

.eye-button:hover {
  transform: scale(1.05);
  background-color: rgba(255, 255, 255, 0.2);
}

.eye-icon {
  fill: currentColor;
}

.view-count {
  font-size: 1rem;
  font-weight: bold;
}
Enter fullscreen mode Exit fullscreen mode

4. Bringing It to Life with JavaScript

Now for the main event, making the counter work.
Here’s the JavaScript, broken down into functions to keep it simple and easy to follow:

Step 1: Retrieving the Visitor’s IP Address

Before we start counting clicks, we need a way to identify unique visitors. The best way to do this is by getting each visitor's IP address (a unique identifier assigned to every device connected to the internet).

Here’s the function that does that:

// Function to get the visitor's IP address
async function getVisitorIP() {
  try {
    const response = await fetch('https://api.ipify.org?format=json');
    const data = await response.json();
    return data.ip;
  } catch (error) {
    console.error('Error fetching IP:', error);
    return null;
  }
}
Enter fullscreen mode Exit fullscreen mode

Explanation

  1. We make a request to https://api.ipify.org?format=json

    • This is an API that returns your device’s public IP address in JSON format
    • Example response:
     { "ip": "192.168.1.1" }
    
  2. We wait for the API to respond using await response.json()

    • This converts the response into an object we can use in our code.
  3. We return the IP address (data.ip)

    • If the request is successful, the function will return something like "192.168.1.1".
  4. Error Handling

    • If there’s an issue (like no internet connection), we log an error and return null instead of breaking the code.

By using this function, we can uniquely track visitors.

Step 2: Fetching and Updating the Click Count

Now that we have a way to identify unique visitors, we need a place to store the number of clicks and the IP addresses of visitors who have already clicked.

For this, we’ll use our JSONBin already created

Fetching the Click Count

const BIN_URL = 'https://api.jsonbin.io/v3/b/YOUR_BIN_ID'; 
const API_KEY = 'YOUR_API_KEY'; 

async function fetchBinData() {
  const response = await fetch(BIN_URL, { headers: { 'X-Master-Key': API_KEY } });
  const result = await response.json();
  return result.record;
}
Enter fullscreen mode Exit fullscreen mode

Explanation

  1. BIN_URL – This is the unique URL where our data is stored in JSONBin.
  2. API_KEY – A secret key that allows us to access and modify the data.
  3. We send a request to JSONBin using fetch()

    • The headers section includes the API key for authentication.
    • If the request is successful, we get back an object like this:
     {
       "totalClicks": 25,
       "clickedIPs": ["192.168.1.1", "203.0.113.5"]
     }
    
  4. We return result.record

    • This extracts and returns the stored click count and IP addresses.

Updating the Click Count

Now, we need a function to update the stored data when someone clicks the counter.

async function updateBinData(data) {
  await fetch(BIN_URL, {
    method: 'PUT',
    headers: {
      'Content-Type': 'application/json',
      'X-Master-Key': API_KEY
    },
    body: JSON.stringify(data)
  });
}
Enter fullscreen mode Exit fullscreen mode

Explanation

  1. We use fetch() to send a request to JSONBin.
  2. The method: 'PUT' part tells JSONBin to replace the existing data.
  3. We convert the new data into JSON format using JSON.stringify(data).
  4. If successful, the click count and IP list get updated.

With these two functions, we can now fetch the existing counter value and update it when needed.

Step 3: Handling Click Events

We need to:

  • Check if the visitor has already clicked (using their IP).
  • If not, increase the count and update JSONBin.
  • Prevent multiple clicks from the same visitor.

Here’s the full implementation:

document.addEventListener('DOMContentLoaded', async () => {
  const eyeButton = document.querySelector('.eye-button');
  const viewCount = document.querySelector('.view-count');

  const visitorIP = await getVisitorIP();
  if (!visitorIP) {
    eyeButton.classList.add('disabled');
    return;
  }

  const binData = await fetchBinData();
  viewCount.textContent = binData.totalClicks;

  if (binData.clickedIPs.includes(visitorIP)) {
    eyeButton.classList.add('disabled');
  }

  eyeButton.addEventListener('click', async () => {
    if (!eyeButton.classList.contains('disabled')) {
      binData.totalClicks += 1;
      binData.clickedIPs.push(visitorIP);

      await updateBinData(binData);

      viewCount.textContent = binData.totalClicks;
      eyeButton.classList.add('disabled');
    }
  });
});
Enter fullscreen mode Exit fullscreen mode

Explanation

  1. Wait for the page to load

    • The DOMContentLoaded event ensures our code only runs after the page has fully loaded.
  2. Get the visitor’s IP address

    • We call getVisitorIP() to determine if the visitor is unique.
  3. If the IP can’t be retrieved, disable the button

    • This prevents errors in case the IP-fetching API fails.
  4. Fetch stored click data from JSONBin

    • The function fetchBinData() retrieves the current number of clicks and IPs that have already clicked.
  5. Update the display with the current click count

    • We set viewCount.textContent = binData.totalClicks; so users see the latest number.
  6. Check if the visitor has already clicked

    • If their IP exists in binData.clickedIPs, we disable the button so they can’t click again.
  7. Handle the click event

    • If the visitor hasn’t clicked before, we:
      • Increase the count (binData.totalClicks += 1).
      • Add their IP to the list (binData.clickedIPs.push(visitorIP)).
      • Save the updated data to JSONBin (await updateBinData(binData)).
      • Update the displayed number of clicks.
      • Disable the button to prevent another click.

Final Thoughts

By following these structured steps, we ensure:

  • Visitors can only click once(tracked by IP).
  • Clicks are stored and updated in real-time using JSONBin.
  • The counter remains interactive and visually engaging.

That’s it! You’ve built a fun and interactive view counter. It’s a simple way to engage visitors and add a touch of personality to your site.

Feel free to visit my portfolio to see it in action

Image of Timescale

🚀 pgai Vectorizer: SQLAlchemy and LiteLLM Make Vector Search Simple

We built pgai Vectorizer to simplify embedding management for AI applications—without needing a separate database or complex infrastructure. Since launch, developers have created over 3,000 vectorizers on Timescale Cloud, with many more self-hosted.

Read full post →

Top comments (0)

A Workflow Copilot. Tailored to You.

Pieces.app image

Our desktop app, with its intelligent copilot, streamlines coding by generating snippets, extracting code from screenshots, and accelerating problem-solving.

Read the docs

👋 Kindness is contagious

Please leave a ❤️ or a friendly comment on this post if you found it helpful!

Okay