Spaces:
Running
Running
<html lang="en"> | |
<head> | |
<meta charset="UTF-8"> | |
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
<title>Hand-Sketch Animation</title> | |
<style> | |
body { | |
margin: 0; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
min-height: 100vh; | |
background-color: #f0f0f0; | |
} | |
#container { | |
position: relative; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
width: 100%; /* Set the width to 100% of the viewport */ | |
} | |
#canvas-wrapper { | |
flex: 1; /* Make it take up available space */ | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
position: relative; | |
} | |
#book-wrapper { | |
flex:1; | |
display: flex; | |
justify-content: center; | |
align-items: center; | |
position: relative; | |
} | |
canvas { | |
display: block; | |
background-color: white; | |
border: 2px solid black; | |
box-shadow: 2px 2px 5px rgba(0, 0, 0, 0.3); | |
position: absolute; | |
} | |
#hand { | |
position: absolute; | |
width: 100px; | |
height: auto; | |
pointer-events: none; | |
transform: translate(-50%,-50%); | |
} | |
#book { | |
width: 400px; | |
height: auto; | |
pointer-events: none; | |
transform: translate(-50%,-50%); | |
} | |
#character1 { | |
position: absolute; | |
width: 100px; | |
height: auto; | |
display: none; | |
transform: translate(-50%,-50%); | |
} | |
#character2 { | |
position: absolute; | |
width: 100px; | |
height: auto; | |
display: none; | |
transform: translate(-50%,-50%); | |
} | |
#character3 { | |
position: absolute; | |
width: 100px; | |
height: auto; | |
display: none; | |
transform: translate(-50%,-50%); | |
} | |
.doodle-text { | |
position: absolute; | |
font-size: 0.8rem; /* Adjust as needed */ | |
white-space: nowrap; /* Prevent wrapping */ | |
transform: translate(-50%,-50%); | |
} | |
#character1-text { | |
left: -20px; | |
top: 30px; | |
} | |
#character2-text { | |
left: -20px; | |
top: 30px; | |
} | |
#character3-text { | |
left: -20px; | |
top: 30px; | |
} | |
</style> | |
</head> | |
<body> | |
<div id="container"> | |
<div id="book-wrapper"> | |
<img id="book" src="https://i.ibb.co/RpZQqFCM/240-F-78487923-cord-V8-UB0-ZUyu3-WRb-LG4-IRVCP65-BExyh.webp"> | |
<img id="hand" src="https://cdn.pixabay.com/photo/2016/12/08/06/40/writing-1891073_1280.png"> | |
</div> | |
<div id="canvas-wrapper"> | |
<canvas id="sketchCanvas"></canvas> | |
</div> | |
<img id="character1" src="https://upload.wikimedia.org/wikipedia/en/6/65/Arthur_Read.svg" alt="Character 1"> | |
<div id="character1-text" class="doodle-text"> | |
{ { x 4 } 2 4 | |
</div> | |
<img id="character2" src="https://upload.wikimedia.org/wikipedia/en/6/65/Arthur_Read.svg" alt="Character 2"> | |
<div id="character2-text" class="doodle-text"> | |
} { 3 3 5 g | |
</div> | |
<img id="character3" src="https://upload.wikimedia.org/wikipedia/en/6/65/Arthur_Read.svg" alt="Character 3"> | |
<div id="character3-text" class="doodle-text"> | |
a g 9 | |
</div> | |
</div> | |
<script> | |
const container = document.getElementById('container'); | |
const canvasWrapper = document.getElementById('canvas-wrapper'); | |
const bookWrapper = document.getElementById('book-wrapper'); | |
const canvas = document.getElementById('sketchCanvas'); | |
const ctx = canvas.getContext('2d'); | |
const hand = document.getElementById('hand'); | |
const bookImg = document.getElementById('book'); | |
// Get bounding rectangles once and reuse | |
let canvasRect, handRect, containerRect, bookRect, bookWrapperRect, canvasWrapperRect; | |
// Function to update bounding rectangles if needed | |
const updateBoundingRects = () => { | |
canvasRect = canvas.getBoundingClientRect(); | |
handRect = hand.getBoundingClientRect(); | |
bookRect = bookImg.getBoundingClientRect(); | |
bookWrapperRect = bookWrapper.getBoundingClientRect(); | |
canvasWrapperRect = canvasWrapper.getBoundingClientRect(); | |
} | |
const character1 = document.getElementById('character1'); | |
const character2 = document.getElementById('character2'); | |
const character3 = document.getElementById('character3'); | |
let screenWidth = 0; | |
let canvasWidth = 0; | |
let canvasHeight = 0; | |
let currentPosition = {x:0, y:0}; | |
const aspectRatio = bookImg.width/bookImg.height; | |
const bookWidth = 400; | |
const bookHeight = bookWidth/aspectRatio; | |
const text = [ | |
{ | |
text: "THE 48 LAWS", | |
x: (bookWidth*0.25), | |
y: (bookHeight*0.2), | |
}, | |
{ | |
text: "OF", | |
x: (bookWidth*0.25), | |
y: (bookHeight*0.3), | |
}, | |
{ | |
text: "POWER", | |
x: (bookWidth*0.25), | |
y: (bookHeight*0.4), | |
}, | |
{ | |
text: "by Robert", | |
x: (bookWidth*0.25), | |
y: (bookHeight*0.6), | |
}, | |
{ | |
text: "Greene", | |
x: (bookWidth*0.25), | |
y: (bookHeight*0.7), | |
}, | |
{ | |
text: "LAW #33", | |
x: (bookWidth*0.6), | |
y: (bookHeight*0.2), | |
}, | |
{ | |
text: "DISCOVER", | |
x: (bookWidth*0.6), | |
y: (bookHeight*0.3), | |
}, | |
{ | |
text: "EACH MAN'S", | |
x: (bookWidth*0.6), | |
y: (bookHeight*0.4), | |
}, | |
{ | |
text: "THUMBSCREW", | |
x: (bookWidth*0.6), | |
y: (bookHeight*0.5), | |
}, | |
]; | |
const text2 = [ | |
{ | |
text: "Everyone has a weakness, a gap in the castle wall.", | |
x: 10, | |
y: 50 | |
}, | |
{ | |
text: "That weakness is usually insecurity, an uncontrollable", | |
x: 10, | |
y: 70 | |
}, | |
{ | |
text: "emotion or need; it can also be a small secret pleasure.", | |
x: 10, | |
y: 90 | |
}, | |
{ | |
text: "Either way, once found, it is a thumbscrew you can turn to", | |
x: 10, | |
y: 110 | |
}, | |
{ | |
text: "your advantage.", | |
x: 10, | |
y: 130 | |
}, | |
{ | |
text: "One of the most important things to realize about people", | |
x: 10, | |
y: 170 | |
}, | |
{ | |
text: "is that they all have a weakness, some part of their", | |
x: 10, | |
y: 190 | |
}, | |
{ | |
text: "psychological armor that will not resist, that will", | |
x: 10, | |
y: 210 | |
}, | |
{ | |
text: "bend to your will if you find it and push on it.", | |
x: 10, | |
y: 230 | |
}, | |
{ | |
text: "Some people wear their weaknesses openly, others disguise", | |
x: 10, | |
y: 250 | |
}, | |
{ | |
text: "them. Those who disguise them are often the ones most", | |
x: 10, | |
y: 270 | |
}, | |
{ | |
text: "effectively undone through that one chink in their armor.", | |
x: 10, | |
y: 290 | |
}, | |
]; | |
const text3 = [ | |
{ | |
text:"Richelieu", | |
x: 50, | |
y: 370 | |
}, | |
{ | |
text:"Louis XIII", | |
x: 200, | |
y: 370 | |
}, | |
{ | |
text:"Marie de' Medicis", | |
x: 350, | |
y: 370 | |
} | |
]; | |
let currentTextIndex = 0; | |
let currentLetterIndex = 0; | |
let currentLineIndex = 0; | |
const strokeStyle = 'black'; | |
const lineWidth = 1; | |
function getRandomNumber(min, max) { | |
return Math.random() * (max - min) + min; | |
} | |
function generatePointsFromText(txt, startX, startY) { | |
const points = []; | |
const letters = txt.split(""); | |
let currentX = startX; | |
const letterSpacing = 10; | |
for(const letter of letters) { | |
const letterPaths = getLetterPaths(letter); | |
for (const path of letterPaths) { | |
let path_points = [] | |
for (let i = 0; i < path.length; i += 2) { | |
path_points.push({x: currentX + path[i], y: startY + path[i+1]}); | |
} | |
points.push(path_points) | |
} | |
currentX += letterSpacing + getLetterWidth(letter); | |
} | |
return points; | |
} | |
function drawLine(x1, y1, x2, y2) { | |
ctx.beginPath(); | |
ctx.moveTo(x1, y1); | |
ctx.lineTo(x2, y2); | |
ctx.stroke(); | |
} | |
function drawSketchLine(x1, y1, x2, y2) { | |
const numPoints = 10; //Number of intermediate points for more smoothness | |
ctx.beginPath(); | |
ctx.moveTo(x1, y1); | |
for(let i = 1; i < numPoints; i++) { | |
const t = i/numPoints; | |
let x = x1 + (x2 - x1) * t; | |
let y = y1 + (y2 - y1) * t; | |
//jitter effect | |
x += getRandomNumber(-lineWidth, lineWidth) * 2; | |
y += getRandomNumber(-lineWidth, lineWidth)*2; | |
ctx.lineTo(x, y); | |
} | |
ctx.lineTo(x2, y2); | |
ctx.stroke(); | |
} | |
let allTextPoints = []; | |
function animateText() { | |
updateCanvasSize(); | |
updateBoundingRects(); | |
ctx.clearRect(0,0, canvas.width, canvas.height); | |
ctx.strokeStyle = strokeStyle; | |
ctx.lineWidth = lineWidth; | |
if(allTextPoints.length === 0) { | |
allTextPoints = text.map((t) => { | |
return {points:generatePointsFromText(t.text, t.x, t.y), index:0} | |
}); | |
} | |
if(currentTextIndex < allTextPoints.length) { | |
let currentAnimatedText = allTextPoints[currentTextIndex]; | |
let points = currentAnimatedText.points; | |
if (currentAnimatedText.index < points.length) { | |
let path = points[currentAnimatedText.index]; | |
if(currentLineIndex < path.length -1 ) { | |
let x1 = path[currentLineIndex].x | |
let y1 = path[currentLineIndex].y; | |
let x2 = path[currentLineIndex+1].x | |
let y2 = path[currentLineIndex+1].y; | |
// update the position of hand in relation to the current animation | |
hand.style.left = (x1 + canvasWrapperRect.left) + 'px'; | |
hand.style.top = (y1 + canvasWrapperRect.top ) + 'px'; | |
drawSketchLine(x1, y1, x2, y2); | |
currentLineIndex++; | |
requestAnimationFrame(animateText); | |
return; | |
} else { | |
currentLineIndex = 0; | |
currentAnimatedText.index++; | |
requestAnimationFrame(animateText); | |
return; | |
} | |
} else { | |
currentTextIndex++; | |
currentLineIndex = 0; | |
requestAnimationFrame(animateText); | |
return; | |
} | |
} else { | |
currentTextIndex = 0; | |
currentLetterIndex = 0; | |
currentLineIndex = 0; | |
allTextPoints = []; | |
hand.style.display = 'none'; | |
ctx.clearRect(0,0, canvas.width, canvas.height); | |
allTextPoints = text2.map((t) => { | |
return {points:generatePointsFromText(t.text, t.x, t.y), index:0} | |
}); | |
requestAnimationFrame(animateText2); | |
return; | |
} | |
} | |
function animateText2() { | |
updateCanvasSize(); | |
updateBoundingRects(); | |
ctx.clearRect(0,0, canvas.width, canvas.height); | |
ctx.strokeStyle = strokeStyle; | |
ctx.lineWidth = lineWidth; | |
if(allTextPoints.length === 0) { | |
allTextPoints = text2.map((t) => { | |
return {points:generatePointsFromText(t.text, t.x, t.y), index:0} | |
}); | |
} | |
if(currentTextIndex < allTextPoints.length) { | |
let currentAnimatedText = allTextPoints[currentTextIndex]; | |
let points = currentAnimatedText.points; | |
if (currentAnimatedText.index < points.length) { | |
let path = points[currentAnimatedText.index]; | |
if(currentLineIndex < path.length -1 ) { | |
let x1 = path[currentLineIndex].x | |
let y1 = path[currentLineIndex].y; | |
let x2 = path[currentLineIndex+1].x | |
let y2 = path[currentLineIndex+1].y; | |
// update the position of hand in relation to the current animation | |
hand.style.left = (x1 + canvasWrapperRect.left) + 'px'; | |
hand.style.top = (y1 + canvasWrapperRect.top ) + 'px'; | |
drawSketchLine(x1, y1, x2, y2); | |
currentLineIndex++; | |
requestAnimationFrame(animateText2); | |
return; | |
} else { | |
currentLineIndex = 0; | |
currentAnimatedText.index++; | |
requestAnimationFrame(animateText2); | |
return; | |
} | |
} else { | |
currentTextIndex++; | |
currentLineIndex = 0; | |
requestAnimationFrame(animateText2); | |
return; | |
} | |
} else { | |
currentTextIndex = 0; | |
currentLetterIndex = 0; | |
currentLineIndex = 0; | |
allTextPoints = []; | |
hand.style.display = 'none'; | |
ctx.clearRect(0,0, canvas.width, canvas.height); | |
character1.style.display='inline'; | |
character2.style.display='inline'; | |
character3.style.display='inline'; | |
character1.style.left = canvasWrapperRect.left + 'px'; | |
character1.style.top = canvasWrapperRect.top + (bookHeight*0.8) + 'px'; | |
character2.style.left = (canvasWrapperRect.left + canvasWrapperRect.width * 0.35)+ 'px'; | |
character2.style.top = canvasWrapperRect.top + (bookHeight*0.8) + 'px'; | |
character3.style.left = (canvasWrapperRect.left + canvasWrapperRect.width * 0.70) + 'px'; | |
character3.style.top = canvasWrapperRect.top + (bookHeight*0.8) + 'px'; | |
allTextPoints = text3.map((t) => { | |
return {points:generatePointsFromText(t.text, t.x, t.y), index:0} | |
}); | |
requestAnimationFrame(animateText3); | |
return; | |
} | |
} | |
function animateText3() { | |
updateBoundingRects(); | |
ctx.strokeStyle = strokeStyle; | |
ctx.lineWidth = lineWidth; | |
if(allTextPoints.length === 0) { | |
allTextPoints = text3.map((t) => { | |
return {points:generatePointsFromText(t.text, t.x, t.y), index:0} | |
}); | |
} | |
if(currentTextIndex < allTextPoints.length) { | |
let currentAnimatedText = allTextPoints[currentTextIndex]; | |
let points = currentAnimatedText.points; | |
if (currentAnimatedText.index < points.length) { | |
let path = points[currentAnimatedText.index]; | |
if(currentLineIndex < path.length -1 ) { | |
let x1 = path[currentLineIndex].x | |
let y1 = path[currentLineIndex].y; | |
let x2 = path[currentLineIndex+1].x | |
let y2 = path[currentLineIndex+1].y; | |
// update the position of hand in relation to the current animation | |
hand.style.left = (x1 + canvasWrapperRect.left) + 'px'; | |
hand.style.top = (y1 + canvasWrapperRect.top ) + 'px'; | |
drawSketchLine(x1, y1, x2, y2); | |
currentLineIndex++; | |
requestAnimationFrame(animateText3); | |
return; | |
} else { | |
currentLineIndex = 0; | |
currentAnimatedText.index++; | |
requestAnimationFrame(animateText3); | |
return; | |
} | |
} else { | |
currentTextIndex++; | |
currentLineIndex = 0; | |
requestAnimationFrame(animateText3); | |
return; | |
} | |
} else { | |
hand.style.display = 'none'; | |
return; | |
} | |
} | |
function updateCanvasSize() { | |
screenWidth = window.innerWidth; | |
canvasWidth = screenWidth*0.45; | |
canvasHeight = canvasWidth / aspectRatio ; | |
canvas.width = canvasWidth ; | |
canvas.height = canvasHeight; | |
bookImg.style.width = (screenWidth * 0.45) + 'px'; | |
hand.style.left = (bookWidth*0.2) + 'px' ; | |
hand.style.top = (bookHeight*0.2) + 'px'; | |
} | |
window.onload = () => { | |
updateCanvasSize(); | |
updateBoundingRects(); | |
hand.style.display = 'inline'; | |
animateText(); | |
} | |
window.addEventListener('resize', () => { | |
updateCanvasSize(); | |
updateBoundingRects(); | |
}); | |
// ---- DATA OF CHARACTERS OF LETTERS ------- | |
function getLetterPaths(letter) { | |
switch(letter){ | |
case 'A': | |
return [ | |
[0, 10, 10, 0, 20,10], | |
[5, 7, 15, 7] | |
]; | |
case 'B': | |
return [ | |
[0,0, 0, 10, 10, 10, 10, 5, 0,5], | |
[10, 5, 10, 0, 0, 0] | |
]; | |
case 'C': | |
return [ | |
[10, 0, 0, 0, 0, 10, 10, 10] | |
]; | |
case 'D': | |
return [ | |
[0, 0, 0, 10, 10, 10, 10, 0, 0, 0] | |
]; | |
case 'E': | |
return [ | |
[0,0, 0,10], | |
[0, 0, 10, 0], | |
[0,5, 7,5], | |
[0, 10, 10, 10], | |
]; | |
case 'F': | |
return [ | |
[0,0, 0, 10], | |
[0,0, 10, 0], | |
[0,5, 7,5], | |
]; | |
case 'G': | |
return [ | |
[10, 0, 0, 0, 0, 10, 10, 10], | |
[10, 10, 10, 5] | |
]; | |
case 'H': | |
return [ | |
[0, 0, 0, 10], | |
[10,0, 10, 10], | |
[0,5, 10, 5], | |
]; | |
case 'I': | |
return [ | |
[5,0, 5, 10] | |
]; | |
case 'J': | |
return [ | |
[7,0,7,10, 0, 10] | |
]; | |
case 'K': | |
return [ | |
[0,0, 0, 10], | |
[0,5, 10,0], | |
[0,5, 10, 10], | |
]; | |
case 'L': | |
return [ | |
[0, 0, 0, 10], | |
[0, 10, 10, 10], | |
]; | |
case 'M': | |
return [ | |
[0, 10, 0,0, 10,5, 20,0, 20, 10], | |
]; | |
case 'N': | |
return [ | |
[0, 10, 0,0, 10, 10, 10, 0] | |
]; | |
case 'O': | |
return [ | |
[10, 0, 0, 0, 0, 10, 10, 10, 10,0] | |
]; | |
case 'P': | |
return [ | |
[0,0, 0, 10, 10, 10, 10, 5, 0,5] | |
]; | |
case 'Q': | |
return [ | |
[10, 0, 0, 0, 0, 10, 10, 10, 10,0], | |
[10, 5, 15, 10] | |
]; | |
case 'R': | |
return [ | |
[0,0, 0, 10, 10, 10, 10, 5, 0,5], | |
[10,5, 20, 10] | |
]; | |
case 'S': | |
return [ | |
[10, 0, 0, 0, 0, 5, 10,5, 10, 10, 0, 10] | |
]; | |
case 'T': | |
return [ | |
[5, 0, 5, 10], | |
[0, 0, 10, 0], | |
]; | |
case 'U': | |
return [ | |
[0, 0, 0, 10, 10, 10, 10,0] | |
]; | |
case 'V': | |
return [ | |
[0,0, 5,10, 10,0], | |
]; | |
case 'W': | |
return [ | |
[0,0, 5,10, 10, 0, 15, 10, 20,0] | |
]; | |
case 'X': | |
return [ | |
[0,0, 10, 10], | |
[10,0, 0, 10] | |
]; | |
case 'Y': | |
return [ | |
[5, 0, 5,5, 0, 10], | |
[5,5, 10, 10] | |
]; | |
case 'Z': | |
return [ | |
[0,0, 10,0, 0,10, 10, 10] | |
]; | |
case ' ': | |
return [ | |
[] | |
] | |
default: | |
return [] | |
} | |
} | |
function getLetterWidth(letter) { | |
switch(letter){ | |
case 'A': return 20; | |
case 'B': return 10; | |
case 'C': return 10; | |
case 'D': return 10; | |
case 'E': return 10; | |
case 'F': return 10; | |
case 'G': return 10; | |
case 'H': return 10; | |
case 'I': return 5; | |
case 'J': return 7; | |
case 'K': return 10; | |
case 'L': return 10; | |
case 'M': return 20; | |
case 'N': return 10; | |
case 'O': return 10; | |
case 'P': return 10; | |
case 'Q': return 15; | |
case 'R': return 20; | |
case 'S': return 10; | |
case 'T': return 10; | |
case 'U': return 10; | |
case 'V': return 10; | |
case 'W': return 20; | |
case 'X': return 10; | |
case 'Y': return 10; | |
case 'Z': return 10; | |
default: return 0; | |
} | |
} | |
</script> | |
</body> | |
</html> |