```html
<!DOCTYPE html>
<html>
<head>
<style>
@keyframes bounce {
0%, 20%, 50%, 80%, 100% {transform: translateY(0);}
40% {transform: translateY(-30px);}
60% {transform: translateY(-15px);}
}
.bounce {
animation: bounce 1s;
}
@keyframes shake {
0% { transform: translate(1px, 1px) rotate(0deg); }
10% { transform: translate(-1px, -2px) rotate(-1deg); }
20% { transform: translate(-3px, 0px) rotate(1deg); }
30% { transform: translate(3px, 2px) rotate(0deg); }
40% { transform: translate(1px, -1px) rotate(1deg); }
50% { transform: translate(-1px, 2px) rotate(-1deg); }
60% { transform: translate(-3px, 1px) rotate(0deg); }
70% { transform: translate(3px, 1px) rotate(-1deg); }
80% { transform: translate(-1px, -1px) rotate(1deg); }
90% { transform: translate(1px, 2px) rotate(0deg); }
100% { transform: translate(1px, -2px) rotate(-1deg); }
}
.shake {
animation: shake 0.5s;
}
</style>
</head>
<body>
<div style="width: 400px; height: 400px; border: 2px solid #000; position: relative; overflow: hidden; margin: auto; background-color: #f0f0f0;">
<canvas id="c" width="400" height="400" style="border:1px solid #ccc;"></canvas>
<div style="position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%);">
<button id="clearBtn" style="padding: 10px; margin: 5px; font-size: 16px;">🧹クリア</button>
<button id="undoBtn" style="padding: 10px; margin: 5px; font-size: 16px;">↩️アンドゥ</button>
<button id="saveBtn" style="padding: 10px; margin: 5px; font-size: 16px;">💾保存</button>
</div>
<div style="position: absolute; top: 10px; left: 10px;">
<input type="file" id="imgLoader" accept="image/*" style="display: none;">
<button id="uploadBtn" style="padding: 10px; font-size: 16px;">📸アップロード</button>
</div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/fabric.js/4.6.0/fabric.min.js"></script>
<script>
const canvas = new fabric.Canvas('c');
const imgLoader = document.getElementById('imgLoader');
const uploadBtn = document.getElementById('uploadBtn');
const clearBtn = document.getElementById('clearBtn');
const undoBtn = document.getElementById('undoBtn');
const saveBtn = document.getElementById('saveBtn');
let imgInstance;
let undoStack = [];
uploadBtn.addEventListener('click', () => {
imgLoader.click();
uploadBtn.classList.add('bounce');
});
imgLoader.onchange = function handleImage(e) {
const reader = new FileReader();
reader.onload = function (event) {
const imgObj = new Image();
imgObj.src = event.target.result;
imgObj.onload = function () {
const image = new fabric.Image(imgObj);
const scale = Math.min(400 / image.width, 400 / image.height);
image.set({
left: 0,
top: 0,
scaleX: scale,
scaleY: scale,
selectable: false
});
canvas.clear();
canvas.add(image);
imgInstance = image;
canvas.sendToBack(image);
}
}
reader.readAsDataURL(e.target.files[0]);
uploadBtn.classList.remove('bounce');
}
clearBtn.addEventListener('click', () => {
canvas.getObjects().forEach(obj => {
if (obj !== imgInstance) {
canvas.remove(obj);
}
});
undoStack = [];
clearBtn.classList.add('shake');
setTimeout(() => clearBtn.classList.remove('shake'), 500);
});
undoBtn.addEventListener('click', () => {
const last = undoStack.pop();
if (last) {
canvas.remove(last);
undoBtn.classList.add('shake');
setTimeout(() => undoBtn.classList.remove('shake'), 500);
}
});
saveBtn.addEventListener('click', () => {
const dataURL = canvas.toDataURL({
format: 'png',
quality: 1
});
const link = document.createElement('a');
link.href = dataURL;
link.download = 'drawing.png';
link.click();
saveBtn.classList.add('bounce');
setTimeout(() => saveBtn.classList.remove('bounce'), 1000);
});
canvas.isDrawingMode = true;
canvas.freeDrawingBrush.width = 5;
canvas.freeDrawingBrush.color = "#FF0000";
canvas.on('path:created', function(opt) {
undoStack.push(opt.path);
});
canvas.on('mouse:down', () => {
canvas.discardActiveObject();
});
</script>
</body>
</html>
```