initial commit
commit
8e7cefdab1
@ -0,0 +1,2 @@
|
|||||||
|
.DS_STORE
|
||||||
|
venv/
|
@ -0,0 +1,19 @@
|
|||||||
|
# Imports
|
||||||
|
from flask_sqlalchemy import SQLAlchemy
|
||||||
|
|
||||||
|
# Database
|
||||||
|
database = SQLAlchemy()
|
||||||
|
|
||||||
|
# User
|
||||||
|
class User ( database.Model ):
|
||||||
|
id = database.Column(database.Integer, primary_key = True)
|
||||||
|
username = database.Column(database.String, nullable = False, unique = True)
|
||||||
|
password = database.Column(database.String, nullable = False)
|
||||||
|
memes = database.relationship("Meme", lazy = "dynamic")
|
||||||
|
|
||||||
|
# Meme
|
||||||
|
class Meme ( database.Model ):
|
||||||
|
id = database.Column(database.Integer, primary_key = True)
|
||||||
|
user = database.Column(database.String, database.ForeignKey("user.username"), nullable = False)
|
||||||
|
title = database.Column(database.String, nullable = False)
|
||||||
|
image = database.Column(database.String, nullable = False, unique = True)
|
@ -0,0 +1,132 @@
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
from flask import Flask, render_template, request, redirect, session
|
||||||
|
from flask_bcrypt import Bcrypt
|
||||||
|
from database import database, User, Meme
|
||||||
|
import os
|
||||||
|
|
||||||
|
app = Flask(__name__)
|
||||||
|
|
||||||
|
app.secret_key = "secret"
|
||||||
|
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///" + os.path.join(os.path.abspath(os.path.dirname(__file__)), 'db.sqlite')
|
||||||
|
database.init_app(app)
|
||||||
|
bcrypt = Bcrypt(app)
|
||||||
|
|
||||||
|
|
||||||
|
with app.app_context():
|
||||||
|
database.create_all()
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def usernameValid ( username ):
|
||||||
|
# Allowed Characters
|
||||||
|
characters = "abcdefghijklmnopqrstuvwxyzABDEFGHIJKLMNOPQRSTUVWXYZ_0123456789"
|
||||||
|
|
||||||
|
if len(username) < 4:
|
||||||
|
return False
|
||||||
|
|
||||||
|
for char in username:
|
||||||
|
if not char in characters:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def passwordValid ( password ):
|
||||||
|
if len(password) < 1:
|
||||||
|
return False
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/")
|
||||||
|
def root ():
|
||||||
|
memes = database.session.execute(database.Select(Meme).order_by(Meme.id.desc())).all()
|
||||||
|
if session.get("username"):
|
||||||
|
return render_template("index.html", memes = memes, loggedIn = True)
|
||||||
|
else:
|
||||||
|
return render_template("index.html", memes = memes, loggedIn = False)
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/sign-up", methods = [ "GET", "POST"])
|
||||||
|
def signUp ():
|
||||||
|
if session.get("username"):
|
||||||
|
return redirect("/")
|
||||||
|
if request.method == "GET":
|
||||||
|
return render_template("sign-up.html", loggedIn = False)
|
||||||
|
else:
|
||||||
|
if usernameValid(request.form.get("username")) and passwordValid(request.form.get("password")) and request.form.get("password") == request.form.get("repeatPassword"):
|
||||||
|
users = database.session.execute(database.select(User).filter_by(username=request.form.get("username"))).first()
|
||||||
|
if users == None:
|
||||||
|
database.session.add(User(username = request.form.get("username"), password = bcrypt.generate_password_hash(request.form.get("password"))))
|
||||||
|
database.session.commit()
|
||||||
|
return redirect("/sign-in")
|
||||||
|
return "Username taken"
|
||||||
|
else:
|
||||||
|
return "Invalid username and / or password"
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/sign-in", methods = [ "GET", "POST"])
|
||||||
|
def signIn ():
|
||||||
|
if session.get("username"):
|
||||||
|
return redirect("/")
|
||||||
|
if request.method == "GET":
|
||||||
|
return render_template("sign-in.html", loggedIn = False)
|
||||||
|
else:
|
||||||
|
if usernameValid(request.form.get("username")) and passwordValid(request.form.get("password")):
|
||||||
|
print("val")
|
||||||
|
user = database.session.execute(database.select(User).filter_by(username=request.form.get("username"))).first()
|
||||||
|
if user != None:
|
||||||
|
if bcrypt.check_password_hash(user[0].password, request.form.get("password")):
|
||||||
|
session["username"] = request.form.get("username")
|
||||||
|
return redirect("/")
|
||||||
|
return render_template("sign-in.html", loggedIn = False)
|
||||||
|
|
||||||
|
@app.route("/sign-out")
|
||||||
|
def signOut():
|
||||||
|
if session.get("username"):
|
||||||
|
session.pop("username")
|
||||||
|
return redirect("/")
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/upload", methods = ["GET", "POST"])
|
||||||
|
def upload ():
|
||||||
|
if session.get("username") == None:
|
||||||
|
return redirect("/")
|
||||||
|
if request.method == "GET":
|
||||||
|
return render_template("upload.html", loggedIn = True)
|
||||||
|
else:
|
||||||
|
|
||||||
|
if request.files.get("image").filename.split(".")[-1] in ["png", "jpg", "jpeg", "webp"]:
|
||||||
|
|
||||||
|
meme = database.session.execute(database.select(Meme).order_by(Meme.id.desc())).first()
|
||||||
|
if meme == None:
|
||||||
|
image_num = 1
|
||||||
|
else:
|
||||||
|
image_num = meme[0].id + 1
|
||||||
|
|
||||||
|
database.session.add(Meme( user = session.get("username") ,title = request.form.get("title"), image = str(image_num) + ".png"))
|
||||||
|
database.session.commit()
|
||||||
|
request.files.get("image").save(os.path.join("./static/memes", str(image_num) + ".png"))
|
||||||
|
|
||||||
|
|
||||||
|
return redirect("/upload")
|
||||||
|
|
||||||
|
return "oof"
|
||||||
|
|
||||||
|
|
||||||
|
@app.route("/user/<name>")
|
||||||
|
def user ( name ):
|
||||||
|
user = database.session.execute(database.select(User).filter_by(username = name )).first()
|
||||||
|
if user != None:
|
||||||
|
memes = database.session.execute(database.select(Meme).filter_by(user=name).order_by(Meme.id.desc())).all()
|
||||||
|
if session.get("username"):
|
||||||
|
return render_template("user.html", username = user[0].username, memes = memes, loggedIn = True)
|
||||||
|
else:
|
||||||
|
return render_template("user.html", username = user[0].username, memes = memes, loggedIn = False)
|
||||||
|
else:
|
||||||
|
return "no user"
|
||||||
|
|
||||||
|
app.run(debug=True)
|
@ -0,0 +1,13 @@
|
|||||||
|
bcrypt==4.0.1
|
||||||
|
click==8.1.3
|
||||||
|
Flask==2.2.3
|
||||||
|
Flask-Bcrypt==1.0.1
|
||||||
|
Flask-SQLAlchemy==3.0.3
|
||||||
|
importlib-metadata==6.6.0
|
||||||
|
itsdangerous==2.1.2
|
||||||
|
Jinja2==3.1.2
|
||||||
|
MarkupSafe==2.1.2
|
||||||
|
SQLAlchemy==2.0.10
|
||||||
|
typing_extensions==4.5.0
|
||||||
|
Werkzeug==2.2.3
|
||||||
|
zipp==3.15.0
|
Binary file not shown.
Binary file not shown.
After Width: | Height: | Size: 555 KiB |
@ -0,0 +1,143 @@
|
|||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: rgb(33, 33, 33);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: Writer-Regular;
|
||||||
|
src: url(/static/Writer-Regular.ttf);
|
||||||
|
}
|
||||||
|
|
||||||
|
nav {
|
||||||
|
width: 100vw;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: rgb(17, 17, 17);
|
||||||
|
}
|
||||||
|
|
||||||
|
.navLogo {
|
||||||
|
width: 30%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
.navLinks {
|
||||||
|
width: 30%;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
font-family: Writer-Regular;
|
||||||
|
font-size: 16px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.navLinks a {
|
||||||
|
margin: 20px;
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.memes {
|
||||||
|
width: 100vw;
|
||||||
|
height: fit-content;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: start;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
background: rgb(33, 33, 33);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.meme {
|
||||||
|
width: 90%;
|
||||||
|
max-width: 800px;
|
||||||
|
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
|
||||||
|
background: linear-gradient(135deg, rgba(50, 50, 50, 0.4), rgba(55, 55, 55, 0.4));
|
||||||
|
backdrop-filter: blur(10px);
|
||||||
|
-webkit-backdrop-filter: blur(10px);
|
||||||
|
border-radius: 20px;
|
||||||
|
border:1px solid rgba(255, 255, 255, 0.18);
|
||||||
|
box-shadow: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
|
||||||
|
|
||||||
|
transition: linear 0.2s;
|
||||||
|
margin: 20px;
|
||||||
|
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
font-family: Writer-Regular;
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meme h1 {
|
||||||
|
color: white;
|
||||||
|
margin: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meme h1 a {
|
||||||
|
color: white;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
.meme img {
|
||||||
|
aspect-ratio: 1 / 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 1800px) {
|
||||||
|
nav {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
.navLogo, .navLinks {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
.navLinks {
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.userTitle {
|
||||||
|
width: 100vw;
|
||||||
|
background: rgb(33, 33, 33);
|
||||||
|
color: white;
|
||||||
|
font-family: Writer-Regular;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 20px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadFormDiv {
|
||||||
|
padding: 10px;
|
||||||
|
background: rgb(33, 33, 33);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadFormDiv form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: white;
|
||||||
|
width: 90%;
|
||||||
|
max-width: 600px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.uploadFormDiv form input {
|
||||||
|
padding: 10px 20px;
|
||||||
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<link rel="stylesheet" href="/static/main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class = "navLogo">
|
||||||
|
<img src="/static/logo.png" width = "300px">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if loggedIn == True %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/upload">Upload</a>
|
||||||
|
<a href="/sign-out">Sign Out</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/sign-in">Sign In</a>
|
||||||
|
<a href="/sign-up">Sign Up</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
|
||||||
|
<div class = "memes">
|
||||||
|
|
||||||
|
{% for meme in memes %}
|
||||||
|
<div class = "meme">
|
||||||
|
<h1>{{ meme[0].title }} by <a href="/user/{{ meme[0].user}}">{{ meme[0].user}}</a></h1>
|
||||||
|
<img src="/static/memes/{{ meme[0].image }}" class = "memeImage">
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const items = document.querySelectorAll(".meme")
|
||||||
|
|
||||||
|
console.log(items)
|
||||||
|
items.forEach( item => {
|
||||||
|
item.addEventListener("mousemove", event => {
|
||||||
|
console.log(item)
|
||||||
|
item.style.transform = `perspective(500px) scale(1.02) rotateX(${ - 20 * (( item.getBoundingClientRect().bottom - event.clientY - item.clientHeight / 2 ) / item.clientHeight ) / 5 }deg) rotateY(${ 20 * (( item.getBoundingClientRect().right - event.clientX - item.clientWidth / 2) / item.clientWidth ) / 5 }deg)`;
|
||||||
|
})
|
||||||
|
|
||||||
|
item.addEventListener("mouseout", event => {
|
||||||
|
item.style.transform = "perspective(500px) scale(1) rotateX(0) rotateY(0)";
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,44 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<link rel="stylesheet" href="/static/main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class = "navLogo">
|
||||||
|
<img src="/static/logo.png" width = "300px">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if loggedIn == True %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/upload">Upload</a>
|
||||||
|
<a href="/sign-out">Sign Out</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/sign-in">Sign In</a>
|
||||||
|
<a href="/sign-up">Sign Up</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 class = "userTitle">Sign In</h1>
|
||||||
|
|
||||||
|
<div class = "uploadFormDiv">
|
||||||
|
<form action="/sign-in" method = "POST">
|
||||||
|
<input type="text" name = "username" placeholder="Username">
|
||||||
|
<input type="password" name = "password" placeholder="Password">
|
||||||
|
<input type="submit" value="Sign Up">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<link rel="stylesheet" href="/static/main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class = "navLogo">
|
||||||
|
<img src="/static/logo.png" width = "300px">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if loggedIn == True %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/upload">Upload</a>
|
||||||
|
<a href="/sign-out">Sign Out</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/sign-in">Sign In</a>
|
||||||
|
<a href="/sign-up">Sign Up</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 class = "userTitle">Sign Up</h1>
|
||||||
|
|
||||||
|
<div class = "uploadFormDiv">
|
||||||
|
<form action="/sign-up" method = "POST">
|
||||||
|
<input type="text" name = "username" placeholder="Username">
|
||||||
|
<input type="password" name = "password" placeholder="Password">
|
||||||
|
<input type="password" name="repeatPassword" placeholder="Repeat Password">
|
||||||
|
<input type="submit" value="Sign Up">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,45 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<link rel="stylesheet" href="/static/main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class = "navLogo">
|
||||||
|
<img src="/static/logo.png" width = "300px">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if loggedIn == True %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/upload">Upload</a>
|
||||||
|
<a href="/sign-out">Sign Out</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/sign-in">Sign In</a>
|
||||||
|
<a href="/sign-up">Sign Up</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 class = "userTitle">Upload</h1>
|
||||||
|
|
||||||
|
<div class = "uploadFormDiv">
|
||||||
|
<form action="/upload" method = "POST" enctype="multipart/form-data">
|
||||||
|
<input type="text" name = "title" placeholder="Title">
|
||||||
|
<input type="file" name="image">
|
||||||
|
<input type="submit" value="Upload">
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
@ -0,0 +1,64 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Document</title>
|
||||||
|
<link rel="stylesheet" href="/static/main.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
|
||||||
|
<nav>
|
||||||
|
<div class = "navLogo">
|
||||||
|
<img src="/static/logo.png" width = "300px">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{% if loggedIn == True %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/upload">Upload</a>
|
||||||
|
<a href="/sign-out">Sign Out</a>
|
||||||
|
</div>
|
||||||
|
{% else %}
|
||||||
|
<div class = "navLinks">
|
||||||
|
<a href="/">Home</a>
|
||||||
|
<a href="/sign-in">Sign In</a>
|
||||||
|
<a href="/sign-up">Sign Up</a>
|
||||||
|
</div>
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<h1 class = "userTitle">{{ username }}'s Memes</h1>
|
||||||
|
|
||||||
|
<div class = "memes">
|
||||||
|
|
||||||
|
{% for meme in memes %}
|
||||||
|
<div class = "meme">
|
||||||
|
<h1>{{ meme[0].title }}</h1>
|
||||||
|
<img src="/static/memes/{{ meme[0].image }}" class = "memeImage">
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
|
||||||
|
const items = document.querySelectorAll(".meme")
|
||||||
|
|
||||||
|
console.log(items)
|
||||||
|
items.forEach( item => {
|
||||||
|
item.addEventListener("mousemove", event => {
|
||||||
|
console.log(item)
|
||||||
|
item.style.transform = `perspective(500px) scale(1.02) rotateX(${ - 20 * (( item.getBoundingClientRect().bottom - event.clientY - item.clientHeight / 2 ) / item.clientHeight ) / 5 }deg) rotateY(${ 20 * (( item.getBoundingClientRect().right - event.clientX - item.clientWidth / 2) / item.clientWidth ) / 5 }deg)`;
|
||||||
|
})
|
||||||
|
|
||||||
|
item.addEventListener("mouseout", event => {
|
||||||
|
item.style.transform = "perspective(500px) scale(1) rotateX(0) rotateY(0)";
|
||||||
|
})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
Loading…
Reference in New Issue