user519753
user519753

Reputation: 1587

How can I make an Upvote/Downvote button?

I'm trying to make an upvote/downvote the same way that it's done on SO and Reddit, from what I can see they use arrow images as backgrounds and then position it, but I'm a CSS newbie and I need someone to walk me through it.

Upvotes: 30

Views: 23274

Answers (3)

oruchkin
oruchkin

Reputation: 1295

I'm doing project on django, and I'm trying to implement up-vote and down-vote on many posts, I've taken @Jan's code partly and finished it.

enter image description here enter image description here enter image description here

vote.html

<span onclick="like_function({{user_answer.pk}})" id="like-{{user_answer.pk}}" class="vote_up_off"></span>
<div id="counter-{{user_answer.pk}}">0</div>
<span onclick="dislike_function({{user_answer.pk}})" id="dislike-{{user_answer.pk}}" class="vote_down_off"></span>

vote.css

/* like dislike button */
.vote_up_off {
  display: inline-block;
  overflow: hidden;
  width: 40px;
  height: 25px;
  cursor: pointer;
  background: url(' https://i.sstatic.net/nxBdX.png');
  background-position: 0 -25px;
  margin-left: 5px;
} 

.vote_up_on {
  background-position: 0 2px;
  display: inline-block;
  overflow: hidden;
  width: 40px;
  height: 25px;
  cursor: pointer;
  background: url('https://i.sstatic.net/nxBdX.png');
  margin-left: 5px;
}

.vote_down_off {
  display: inline-block;
  overflow: hidden;
  width: 40px;
  height: 25px;
  cursor: pointer;
  background: url('https://i.sstatic.net/vWw7n.png');
  background-position: 0 -1px;
  margin-top: 3px;
} 


.vote_down_on {
  display: inline-block;
  overflow: hidden;
  width: 40px;
  height: 25px;
  cursor: pointer;
  background: url('https://i.sstatic.net/vWw7n.png');
  background-position: 0 -28px;
  margin-top: 3px;
} 

vote.js

function like_function(answer_id) {
    var like_button = document.getElementById('like-'+answer_id);
    var dislike_button = document.getElementById('dislike-'+answer_id);
    var counter_element = document.getElementById('counter-'+answer_id);
    let current_counter = parseInt(counter_element.innerText);

    //check if dislike is on(true) or off(false)
    let dislike_state = false
    if (dislike_button.className == "vote_down_on") {
        dislike_state = true
    }
    else {
        dislike_state = false
    }
    //if dislike is checked
    if (dislike_state) {
        current_counter += 2;
        dislike_button.className = 'vote_down_off'
        counter_element.innerText = current_counter
        like_button.className = 'vote_up_on'
    }
    // if dislike is not checked
    else {
        if (like_button.className == 'vote_up_off') {
            like_button.className = "vote_up_on"
            current_counter +=  1;
            counter_element.innerText = current_counter
        }
        else {
            like_button.className = "vote_up_off"
            current_counter +=  -1;
            counter_element.innerText = current_counter
        }
    }
}



function dislike_function(answer_id) {
    var like_button = document.getElementById('like-'+answer_id);
    var dislike_button = document.getElementById('dislike-'+answer_id);
    var counter_element = document.getElementById('counter-'+answer_id);
    let current_counter = parseInt(counter_element.innerText);

    //check if like is on(true) or off(false)
    let like_state = false
    if (like_button.className == "vote_up_on") {
        like_state = true
    }
    else {
        like_state = false
    }
    //if like is checked
    if (like_state) {
        console.log('это тру лайк (лайк нажат)')
        current_counter +=  -2;
        like_button.className = 'vote_up_off'
        counter_element.innerText = current_counter
        dislike_button.className = "vote_down_on"
    }
    //if like is not checked
    else {
        if (dislike_button.className == 'vote_down_off') {
            dislike_button.className = "vote_down_on"
            current_counter +=  -1;
            counter_element.innerText = current_counter
        }
        else {
            dislike_button.className = "vote_down_off"
            current_counter +=  1;
            counter_element.innerText = current_counter
        }
    }
}

Upvotes: 1

Jan
Jan

Reputation: 8141

You could do it by adding a different picture to the background, one for every state of the button. There is however a cleaner, easier, more modern way of achieving this result: Sprites.

A sprite is an image that is saved as a part of a larger image. One of the biggest advantages of using sprites is the reduction of round-trips to the server for all the images to just one request for the Sprites. The element to display a picture has the image as background. The background is moved relative to the element so the element displays only part of the image. Like when you move a photo-frame over a poster (or in this case: moving the poster under the frame)

At SO they make an image that contains all the states for the button. They give the element for the button (a span in this case) a fixed width and height and add the background to it with CSS. Then toggle a class for the state (on or off) with javascript on the click event. Now the only thing you have to do in CSS is change the position of the background with CSS classes:

The up/down sprite image

for (const btn of document.querySelectorAll('.vote')) {
  btn.addEventListener('click', event => {
    event.currentTarget.classList.toggle('on');
  });
}
.vote {
  display: inline-block;
  overflow: hidden;
  width: 40px;
  height: 25px;
  cursor: pointer;
  background: url('https://i.sstatic.net/iqN2k.png');
  background-position: 0 -25px;
} 


.vote.on {
  background-position: 0 2px;
}
Click to vote (using sprites): <span class="sprite vote"> </span>

You can easily add more states to the sprites like 'hover' and 'active' just the same way. SO even puts all the images for the whole page in a single image. You can verify this with firebug or the Chrome developer tools. Look for 'sprites.png'.

Update (2020)

It's been 10 years since I answered this question and in this time, the landscape has changed. Now you can use inline svg as well to achieve this effect. I've updated the code snippet to use svg. This is how stackoverflow currently does this.

It works by toggling the color property of a surrounding span element on button click. The span element contains an inline svg image of an arrow. The fill property of the path that makes up the arrow is initialized with currentColor, which instructs it to take whatever is the current text color.

for (const btn of document.querySelectorAll('.vote')) {
  btn.addEventListener('click', event => {
    event.currentTarget.classList.toggle('on');
  });
}
.vote {
  display: inline-block;
  cursor: pointer;
  color: #687074
} 


.vote.on {
  color: #f48024
}
Click to vote (using svg): 
<span class="vote">
  <svg width="36" height="36">
    <path d="M2 10h32L18 26 2 10z" fill="currentColor"></path>
  </svg>
</span>

Upvotes: 55

KillerFish
KillerFish

Reputation: 5180

You can do it by using two simple images ... design two images in some image editors like Photoshop, if u don't have MSPaint...

CSS code is

#voting{
   width:30px;
   height:40px;
}
.upvote{
   width:30px;
   height: 20px;
   cursor: pointer;
}
.downvote{
   width:30px;
   height: 20px;
   background: url('downvote.jpg') 0 0 no-repeat;
   cursor: pointer;
}

HTML code :

<div id="voting">
    <div class="upvote"></div>
    <div class="downvote"></div>
</div>

Upvotes: 5

Related Questions