
Twitter Like button with PHP, Ajax and MySQL
Like buttons have become very popular on social media and it is a simplified way of expressing whether we like the post or not. I did something similar using PHP, Ajax and MySQL but this time I used the IP address of the guest user to use it as id, you can use the session of the registered user instead of the IP.
Descripción
The user can like any publication, the button has a heart shape similar to that of Twitter including an animation this is thanks to a plugin that I found in 9lessons.info 🙏. When click is pressed, the number of likes the publication has is displayed in real time, when it is unchecked (unlike) it is subtracted likes. To test the demo make sure to use another IP for the system to register another guest user, this way you can increase more likes.

Remember that you can download the script at the end of this post, now I will explain a little what each file is about.
Database, there are columns that I do not use and do not influence the operation of the system, the number of likes is not saved in the database, a SQL query is made counting all the likes of each IP.
style.css, en este archivo sestyle.css, in this file you will find the CSS styles but the most important part is in line 274 .heart
these lines will be in charge of displaying the animated button.
@import url('https://fonts.googleapis.com/css2?family=Open+Sans:wght@400;600;700&display=swap');
*{
margin: 0;
padding: 0;
box-sizing: border-box;
font-family: 'Open Sans', sans-serif;
}
::selection{
color: #fff;
background: #0d2866;
}
.wrapper{
background: #fff;
max-width: 475px;
width: 100%;
border-radius: 15px;
padding: 25px 25px 15px 25px;
box-shadow: 0px 10px 15px rgba(0,0,0,0.1);
margin-bottom: 18px;
}
.likes-heart{
color: #98A5B1;
margin-top: 33px;
font-size: 14px;
}
.input-box{
padding-top: 10px;
border-bottom: 1px solid #e6e6e6;
}
.input-box .tweet-area{
position: relative;
min-height: 130px;
max-height: 170px;
overflow-y: auto;
}
.tweet-area::-webkit-scrollbar{
width: 0px;
}
.tweet-area .desc{
position: absolute;
margin-top: -3px;
font-size: 22px;
color: #98A5B1;
pointer-events: none;
}
.tweet-area .input{
outline: none;
font-size: 17px;
min-height: inherit;
word-wrap: break-word;
word-break: break-all;
}
.tweet-area .editable{
position: relative;
z-index: 5;
}
.tweet-area .readonly{
position: absolute;
top: 0;
left: 0;
z-index: -1;
color: transparent;
background: transparent;
}
.readonly .highlight{
background: #fd9bb0;
}
.bottom{
display: flex;
margin-top: 13px;
align-items: center;
justify-content: space-between;
}
.bottom .icons{
display: inline-flex;
}
.icons li{
list-style: none;
color: #0d2866;
font-size: 20px;
margin: 0 2px;
height: 38px;
width: 38px;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
transition: background 0.2s ease;
}
.bottom .content{
display: flex;
align-items: center;
justify-content: center;
}
.bottom .counter{
color: #333;
display: none;
font-weight: 500;
margin-right: 15px;
padding-right: 15px;
border-right: 1px solid #aab8c2;
}
.bottom button{
padding: 9px 18px;
border: none;
outline: none;
border-radius: 50px;
font-size: 16px;
font-weight: 700;
background: #0d2866;
color: #fff;
cursor: pointer;
opacity: 0.5;
pointer-events: none;
transition: background 0.2s ease;
}
.bottom button.active{
opacity: 1;
pointer-events: auto;
}
.bottom button:hover{
background: #0d8bd9;
}
*{
margin: 0px;
padding: 0px;
box-sizing: border-box;
}
body {
background: #0d2866;
color:#fff;
position: relative;
font-family: Raleway, sans-serif;
font-size: 16px;
font-weight: 400;
}
.text-center{
text-align: center;
}
footer{
background:#2F4881;
height:80px;
padding-top:20px;
}
.loader {
position: fixed;
left: 0px;
top: 0px;
width: 100%;
height: 100%;
z-index: 9999;
background: url(../image/loader.gif) 50% 50% no-repeat rgb(249,249,249);
}
html, body{
height: 100%;
width: 100%;
}
.login-page .container{
height: 100%;
width: 100%;
display: flex;
align-items: center;
justify-content: center;
}
.login-form{
width: 300px;
margin: 0px 15px;
text-align: center;
}
.form-group{
display: block;
width: 100%;
margin: 15px 0px;
}
.form-control {
display: block;
width: 100%;
height: calc(1.5em + .75rem + 2px);
padding: .375rem .75rem;
font-size: 1rem;
font-weight: 400;
line-height: 1.5;
color: #495057;
background-color: #fff;
background-clip: padding-box;
border: 1px solid #ced4da;
border-radius: .25rem;
transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
}
.help-block{
display: inline-block;
font-size: .9rem;
}
.form-group.has-error .help-block{
color: #ff2424;
}
.form-group.has-success .help-block{
color: #095236;
}
input, select, textarea{
font-family: inherit;
}
.sub-title{
font-weight: 400;
}
.container{
width: calc(100% - 30px);
margin: 15px;
max-width: 1200px;
margin: 0px auto;
}
.title{
font-size: 2rem;
font-weight: 400;
margin-top: 50px;
}
.mb-2{
margin-bottom: 2rem;
}
.img-responsive{
width: 100%;
}
.image_outer_container{
display: flex;
flex-wrap: wrap;
justify-content: center;
flex: 1 1;
}
.image_container{
display: flex;
flex-direction: column;
width: 300px;
margin: 15px;
flex-wrap: wrap;
justify-content: center;
}
.like_container {
text-align: center;
padding: 10px;
}
span.score {
padding: 5px;
font-size: 17px;
font-weight: 700;
}
.alert{
background-color: brown;
padding: 5px;
}
.underline{
text-decoration: underline;
}
.ft-1{
font-size: 1.5rem;
}
@media only screen and (min-width: 500px) {
.login-form{
width: 400px;
}
.image_container{
width: 500px;
}
}
/*button*/
.heart {
background: url(../image/web_heart_animation.png);
background-position: left;
background-repeat: no-repeat;
height: 50px;
width: 50px;
cursor: pointer;
position: absolute;
left:-14px;
background-size:2900%
}
.heart:hover, .heart:focus{
background-position: right;
}
@-webkit-keyframes heartBlast {
0% {
background-position: left;
}
100% {
background-position: right;
}
}
@keyframes heartBlast {
0% {
background-position: left;
}
100% {
background-position: right;
}
}
.heartAnimation {
display: inline-block;
-webkit-animation-name: heartBlast;
animation-name: heartBlast;
-webkit-animation-duration: .8s;
animation-duration: .8s;
-webkit-animation-iteration-count: 1;
animation-iteration-count: 1;
-webkit-animation-timing-function: steps(28);
animation-timing-function: steps(28);
background-position: right;
}
.feed p{
font-family: 'Georgia', Times, Times New Roman, serif;
font-size: 25px
}
.feed{
clear: both;
position: relative;
}
a{
color: #7ac9ed
}
p{
margin: 0px; padding: 0px
}
/*.likeCount{
font-family: 'Georgia', Times, Times New Roman, serif;
margin-top: 13px;
margin-left: 28px;
font-size: 16px;
color: #999999
}*/
app.js, through Ajax an update is sent to check_name.php, it also takes care of changing the new CSS styles when the heart is clicked.
$(window).load(function() {
$(".loader").fadeOut("slow");
});
/*button*/
$(document).on('click', '.heart', function() {
//var button = $(this);
id = $(this).attr('id');
user_id = $('#id').val();
container = $(this).closest('.feed').attr('id');
var B=id.split("like");
var messageID=B[1];
var C=parseInt($("#likeCount"+messageID).html());
$(this).css("background-position","")
var D=$(this).attr("rel");
if(D === 'like')
{
$("#likeCount"+messageID).html(C+1);
$(this).addClass("heartAnimation").attr("rel","unlike");
/*alert('like');*/
}else{
$("#likeCount"+messageID).html(C-1);
$(this).removeClass("heartAnimation").attr("rel","like");
$(this).css("background-position","left");
/*alert('unlike');*/
}
$.ajax({
type : "POST",
url : "check_name.php",
data : {
id : id,
user_id : user_id,
flag1 : 'update'
},
async : false,
success : function(data) {
$('#' + container).find('.score').html(data); //actualiza contador
//button.removeClass("heartAnimation").attr("rel","like");
//button.css("background-position","left");
}
});
});
config.php, here is the connection with the database in addition to the functions in charge of verifying the prepared queries, I forgot to comment that I use PDO.
<?php
/**
* file: config.php
* @author anthoncode
* @link https://anthoncode.com/en
*/
define('DB_HOST', 'localhost');
define('DB_NAME', 'star_db');
define('DB_USER','root');
define('DB_PASSWORD','');
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
if ($mysqli->connect_errno) {
echo "Failed to connect to MySQL: " . $mysqli -> connect_error;
exit();
}
function prepareStatement($sql, $params = null)
{
global $mysqli;
$stmt = $mysqli->prepare($sql);
if (!($stmt)) {
throw new Exception("Prepare failed: (" . $mysqli->errno . ") " . $mysqli->error);
}
if (!empty($params) && is_array($params)) {
$count = count($params);
$bindStr = str_repeat('s', $count);
$stmt->bind_param($bindStr, ...$params);
}
if (!$stmt->execute()) {
throw new Exception("Execute failed: (" . $stmt->errno . ") " . $stmt->error);
}
return $stmt;
}
/**
* GetResult
*/
function getResult($stmt)
{
if (!($res = $stmt->get_result())) {
throw new Exception("Getting result set failed: (" . $stmt->errno . ") " . $stmt->error);
}
return $res;
}
check_name.php, these lines get the updates sent through app.js, line 14 checks if the publication has likes, if it does, it proceeds to register a new like.
<?php
/**
* file: check_name.php
* @author anthoncode
* @link https://anthoncode.com/en
*/
require_once 'config.php';
require_once 'functions.php';
try {
if(isset($_POST['flag1'])){
$like_id=$_POST['id'];
$ip_address=$_SERVER['REMOTE_ADDR'];
$count = checkUserAlreadyPost($like_id, $ip_address);
if ($count==0) {
echo likePost($like_id, $ip_address);
} else {
echo dislikePost($like_id, $ip_address);
}
exit;
}
} catch (Exception $e) {
echo $e->getMessage();
exit;
}
functions.php, here are the queries prepared to check if the post has likes, it counts all the likes to show it in real time.
<?php
/**
* file: functions.php
* @author anthoncode
* @link https://anthoncode.com/en
*/
function checkUserAlreadyPost($post_id, $ip_address)
{
$sql = "SELECT id from post_likes where post_id=? and ip_address=?";
$stmt = prepareStatement($sql, [$post_id, $ip_address]);
$res = getResult($stmt);
return $res->num_rows; //returns 1 or 0
}
//function: to update the number of likes in real time
function getPostLikeCount($post_id)
{
$sql = "SELECT count(post_id) as count from post_likes where post_id=?";
$stmt = prepareStatement($sql, [$post_id]);
$res = getResult($stmt);
$result = $res->fetch_assoc();
return $result['count'] ? $result['count'] : 0;
}
function likePost($post_id, $ip_address)
{
global $mysqli;
$ip_address = $_SERVER['REMOTE_ADDR'];
$sql = "INSERT INTO post_likes (id, post_id, ip_address) VALUES (NULL,?,?)";
prepareStatement($sql, [$post_id, $ip_address]);
return getPostLikeCount($post_id); //to update the number of likes in real time
}
function dislikePost($post_id, $ip_address)
{
$ip_address = $_SERVER['REMOTE_ADDR'];
$sql = "DELETE FROM post_likes where post_id = ? and ip_address = ?";
prepareStatement($sql, [$post_id, $ip_address]);
return getPostLikeCount($post_id); //to update the number of likes in real time
}
//SQL query to show the number of likes each post has taking the IP address as id
function getPostLikes()
{
$sql = "SELECT Posts.id,
Posts.description,
count(post_id) as likesCount FROM `posts` Posts
LEFT JOIN post_likes PostLikes
ON PostLikes.post_id = Posts.id
GROUP BY Posts.id
order by Posts.id ASC";
$stmt = prepareStatement($sql);
$res = getResult($stmt);
return $res->fetch_all(MYSQLI_ASSOC);
}
index.php, here is the body of this little project.
<?php
/**
* file: index.php
* @author anthoncode
* @link https://anthoncode.com/en
*/
require_once 'config.php';
session_start();
require_once 'functions.php';
$posts = getPostLikes();
?>
<!DOCTYPE html>
<html class="no-js">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Twitter Like button with PHP, Ajax and MySQL</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.4/css/all.min.css" integrity="sha512-1ycn6IcaQQ40/MKBW2W4Rhis/DbILU74C1vSrLJxCq57o941Ym01SwNsOMqvEBFlcgUa6xLiPY/NS5R+E6ztJQ==" crossorigin="anonymous" referrerpolicy="no-referrer" />
<link href="css/style.css" rel="stylesheet" media="screen">
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<script type="text/javascript" src="js/app.js"></script>
<link rel="stylesheet" href="https://unicons.iconscout.com/release/v3.0.6/css/line.css">
</head>
<body>
<div class="container">
<h1 class="title text-center">Twitter Like button with PHP, Ajax and MySQL</h1>
<hr class="mb-2">
<div class="text-center">
<p> Post <strong class="ft-1 underline"></strong></p>
</div>
<?php
foreach ($posts as $key => $result) {
?>
<div class="wrapper">
<div class="input-box">
<div class="tweet-area">
<span class="desc"><?php echo $result['description']; ?></span>
</div>
</div>
<div class="bottom">
<ul class="icons">
<?php
$like_id=$result['id'];
$ip_address=$_SERVER['REMOTE_ADDR'];
$count = checkUserAlreadyPost($like_id, $ip_address);
?>
<div class="feed" id="feed<?php echo $result['id']; ?>">
<!-- check if the post has like -->
<?php if($count == 0){ ?>
<div class="heart" id="<?php echo $result['id']; ?>" rel="like" style="background-position: left center;"></div>
<?php } else { ?>
<div class="heart heartAnimation" id="<?php echo $result['id']; ?>" rel="unlike" style=""></div>
<?php } ?>
<div class="likes-heart">
<!-- class=SCORE is where the number of likes is updated. check app.js-->
<span class="score"><?php echo $result['likesCount']; ?></span>
<span>Likes</span>
</div>
</div>
<!-- <li><i class="far fa-file-image"></i></li>
<li><i class="fas fa-map-marker-alt"></i></li>
<li><i class="far fa-grin"></i></li>
<li><i class="far fa-user"></i></li> -->
</ul>
<div class="content">
<button>Answer</button>
</div>
</div>
</div>
<?php } ?>
<footer>
<p class="text-center" id="foot">©<a href="https://anthoncode.com/en" target="_blank"> anthoncode.com </a> <?php echo date('Y') ?></p>
</footer>
<div class="loader"></div>
</body>
</html>
I tried this script and it works. But not what im looking for. it created new rows in in database for every like. so if the website grows big with million post likes, itd have million rows.
This script uses the IP as ID, that is to say that if you enter with another IP it counts as another different data, if you wish you can change the IP for a session ID.