<!DOCTYPE html>
<html lang="en" ng-app="drawingApp">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AngularJS Canvas Drawing App</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.8.2/angular.min.js"></script>
<script type="module" src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.esm.js"></script>
<script nomodule src="https://unpkg.com/ionicons@7.1.0/dist/ionicons/ionicons.js"></script>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
background: linear-gradient(135deg, #0d1b2a 0%, #1b263b 100%);
min-height: 100vh;
padding: 20px;
}
.container {
max-width: 1400px;
margin: 0 auto;
}
.header {
text-align: center;
color: white;
margin-bottom: 30px;
}
.header h1 {
font-size: 36px;
margin-bottom: 10px;
font-weight: 600;
}
.header p {
font-size: 16px;
opacity: 0.9;
}
.main-content {
display: grid;
grid-template-columns: 300px 1fr;
gap: 20px;
}
.toolbar {
background: white;
border-radius: 16px;
padding: 25px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
height: fit-content;
}
.toolbar h2 {
font-size: 20px;
color: #0d1b2a;
margin-bottom: 20px;
display: flex;
align-items: center;
gap: 10px;
}
.tool-section {
margin-bottom: 25px;
padding-bottom: 25px;
border-bottom: 1px solid #e0e0e0;
}
.tool-section:last-child {
border-bottom: none;
margin-bottom: 0;
padding-bottom: 0;
}
.tool-section h3 {
font-size: 14px;
color: #666;
margin-bottom: 12px;
text-transform: uppercase;
letter-spacing: 0.5px;
}
.tool-buttons {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 10px;
}
.tool-btn {
padding: 12px;
border: 2px solid #e0e0e0;
border-radius: 10px;
background: white;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
flex-direction: column;
align-items: center;
gap: 5px;
font-size: 12px;
color: #666;
}
.tool-btn ion-icon {
font-size: 24px;
color: #0d1b2a;
}
.tool-btn:hover {
border-color: #0d1b2a;
background: #f8f9fa;
}
.tool-btn.active {
border-color: #0d1b2a;
background: #0d1b2a;
color: white;
}
.tool-btn.active ion-icon {
color: white;
}
.color-palette {
display: grid;
grid-template-columns: repeat(5, 1fr);
gap: 10px;
}
.color-option {
width: 100%;
aspect-ratio: 1;
border-radius: 8px;
cursor: pointer;
border: 3px solid transparent;
transition: all 0.2s ease;
}
.color-option:hover {
transform: scale(1.1);
}
.color-option.active {
border-color: #0d1b2a;
transform: scale(1.15);
}
.custom-color-input {
width: 100%;
height: 40px;
border: 2px solid #e0e0e0;
border-radius: 8px;
cursor: pointer;
margin-top: 10px;
}
.slider-group {
margin-bottom: 15px;
}
.slider-group label {
display: flex;
justify-content: space-between;
font-size: 13px;
color: #666;
margin-bottom: 8px;
}
.slider-value {
font-weight: 600;
color: #0d1b2a;
}
input[type="range"] {
width: 100%;
height: 6px;
border-radius: 3px;
background: #e0e0e0;
outline: none;
-webkit-appearance: none;
}
input[type="range"]::-webkit-slider-thumb {
-webkit-appearance: none;
width: 18px;
height: 18px;
border-radius: 50%;
background: #0d1b2a;
cursor: pointer;
}
input[type="range"]::-moz-range-thumb {
width: 18px;
height: 18px;
border-radius: 50%;
background: #0d1b2a;
cursor: pointer;
border: none;
}
.action-buttons {
display: flex;
flex-direction: column;
gap: 10px;
}
.btn {
padding: 12px 20px;
border: none;
border-radius: 10px;
font-size: 14px;
font-weight: 600;
cursor: pointer;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.btn-primary {
background: linear-gradient(135deg, #0d1b2a 0%, #1b263b 100%);
color: white;
}
.btn-primary:hover {
transform: translateY(-2px);
box-shadow: 0 6px 15px rgba(13, 27, 42, 0.4);
}
.btn-secondary {
background: #e0e0e0;
color: #333;
}
.btn-secondary:hover {
background: #d0d0d0;
}
.btn-danger {
background: #ef233c;
color: white;
}
.btn-danger:hover {
background: #d90429;
}
.canvas-wrapper {
background: white;
border-radius: 16px;
padding: 20px;
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3);
}
.canvas-container {
background: #f8f9fa;
border-radius: 12px;
overflow: hidden;
display: flex;
align-items: center;
justify-content: center;
position: relative;
}
#drawingCanvas {
display: block;
cursor: crosshair;
background: white;
max-width: 100%;
height: auto;
}
.canvas-info {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 15px;
padding: 15px;
background: #f8f9fa;
border-radius: 10px;
}
.info-item {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
color: #666;
}
.info-item ion-icon {
font-size: 18px;
color: #0d1b2a;
}
.info-value {
font-weight: 600;
color: #0d1b2a;
}
@media (max-width: 1024px) {
.main-content {
grid-template-columns: 1fr;
}
.toolbar {
order: 2;
}
.canvas-wrapper {
order: 1;
}
#drawingCanvas {
width: 100%;
height: auto;
}
}
@media (max-width: 768px) {
body {
padding: 10px;
}
.header h1 {
font-size: 24px;
}
.header p {
font-size: 14px;
}
.toolbar {
padding: 20px;
}
.tool-buttons {
grid-template-columns: repeat(2, 1fr);
}
.color-palette {
grid-template-columns: repeat(4, 1fr);
}
.canvas-wrapper {
padding: 15px;
}
.canvas-info {
flex-direction: column;
gap: 10px;
}
.info-item {
width: 100%;
justify-content: center;
}
}
@media (max-width: 480px) {
.header h1 {
font-size: 20px;
}
.toolbar h2 {
font-size: 18px;
}
.tool-buttons {
grid-template-columns: 1fr;
}
.color-palette {
grid-template-columns: repeat(3, 1fr);
}
.tool-btn {
padding: 10px;
}
.tool-btn ion-icon {
font-size: 20px;
}
}
</style>
</head>
<body ng-controller="DrawingController">
<div class="container">
<div class="header">
<h1>Canvas Drawing App</h1>
<p>Create beautiful drawings with AngularJS and HTML5 Canvas</p>
</div>
<div class="main-content">
<!-- Toolbar -->
<div class="toolbar">
<h2>
<ion-icon name="brush-outline"></ion-icon>
Drawing Tools
</h2>
<!-- Drawing Tools -->
<div class="tool-section">
<h3>Tools</h3>
<div class="tool-buttons">
<button class="tool-btn" ng-class="{active: tool === 'brush'}" ng-click="selectTool('brush')">
<ion-icon name="brush-outline"></ion-icon>
Brush
</button>
<button class="tool-btn" ng-class="{active: tool === 'pencil'}" ng-click="selectTool('pencil')">
<ion-icon name="create-outline"></ion-icon>
Pencil
</button>
<button class="tool-btn" ng-class="{active: tool === 'eraser'}" ng-click="selectTool('eraser')">
<ion-icon name="remove-outline"></ion-icon>
Eraser
</button>
<button class="tool-btn" ng-class="{active: tool === 'line'}" ng-click="selectTool('line')">
<ion-icon name="remove-outline"></ion-icon>
Line
</button>
<button class="tool-btn" ng-class="{active: tool === 'rectangle'}" ng-click="selectTool('rectangle')">
<ion-icon name="square-outline"></ion-icon>
Rectangle
</button>
<button class="tool-btn" ng-class="{active: tool === 'circle'}" ng-click="selectTool('circle')">
<ion-icon name="ellipse-outline"></ion-icon>
Circle
</button>
</div>
</div>
<!-- Colors -->
<div class="tool-section">
<h3>Colors</h3>
<div class="color-palette">
<div class="color-option"
ng-repeat="color in colors"
ng-style="{background: color}"
ng-class="{active: selectedColor === color}"
ng-click="selectColor(color)">
</div>
</div>
<input type="color" class="custom-color-input" ng-model="selectedColor" ng-change="updateColor()">
</div>
<!-- Brush Size -->
<div class="tool-section">
<h3>Brush Settings</h3>
<div class="slider-group">
<label>
<span>Brush Size</span>
<span class="slider-value">{{brushSize}}px</span>
</label>
<input type="range" min="1" max="50" ng-model="brushSize">
</div>
<div class="slider-group">
<label>
<span>Opacity</span>
<span class="slider-value">{{opacity}}%</span>
</label>
<input type="range" min="0" max="100" ng-model="opacity">
</div>
</div>
<!-- Actions -->
<div class="tool-section">
<h3>Actions</h3>
<div class="action-buttons">
<button class="btn btn-primary" ng-click="undo()" ng-disabled="!canUndo()">
<ion-icon name="arrow-undo-outline"></ion-icon>
Undo
</button>
<button class="btn btn-primary" ng-click="redo()" ng-disabled="!canRedo()">
<ion-icon name="arrow-redo-outline"></ion-icon>
Redo
</button>
<button class="btn btn-secondary" ng-click="downloadCanvas()">
<ion-icon name="download-outline"></ion-icon>
Download
</button>
<button class="btn btn-danger" ng-click="clearCanvas()">
<ion-icon name="trash-outline"></ion-icon>
Clear Canvas
</button>
</div>
</div>
</div>
<!-- Canvas Area -->
<div class="canvas-wrapper">
<div class="canvas-container">
<canvas id="drawingCanvas" width="900" height="600"></canvas>
</div>
<div class="canvas-info">
<div class="info-item">
<ion-icon name="resize-outline"></ion-icon>
<span>Size: <span class="info-value">{{canvasWidth}} x {{canvasHeight}}</span></span>
</div>
<div class="info-item">
<ion-icon name="brush-outline"></ion-icon>
<span>Tool: <span class="info-value">{{tool | uppercase}}</span></span>
</div>
<div class="info-item">
<ion-icon name="color-palette-outline"></ion-icon>
<span>Color: <span class="info-value">{{selectedColor}}</span></span>
</div>
</div>
</div>
</div>
</div>
<script>
angular.module('drawingApp', [])
.controller('DrawingController', function($scope, $timeout) {
$scope.canvas = null;
$scope.ctx = null;
$scope.isDrawing = false;
$scope.startX = 0;
$scope.startY = 0;
$scope.tool = 'brush';
$scope.selectedColor = '#0d1b2a';
$scope.brushSize = 5;
$scope.opacity = 100;
$scope.canvasWidth = 900;
$scope.canvasHeight = 600;
$scope.history = [];
$scope.historyStep = -1;
// Responsive canvas sizing
function setCanvasSize() {
var container = document.querySelector('.canvas-container');
if (container) {
var maxWidth = container.clientWidth - 40;
if (maxWidth < 900) {
$scope.canvasWidth = Math.max(300, maxWidth);
$scope.canvasHeight = Math.round($scope.canvasWidth * 0.667);
} else {
$scope.canvasWidth = 900;
$scope.canvasHeight = 600;
}
if ($scope.canvas) {
// Save current drawing
var imgData = $scope.ctx.getImageData(0, 0, $scope.canvas.width, $scope.canvas.height);
// Resize canvas
$scope.canvas.width = $scope.canvasWidth;
$scope.canvas.height = $scope.canvasHeight;
// Restore drawing (will be scaled)
$scope.ctx.putImageData(imgData, 0, 0);
$scope.$apply();
}
}
}
// Listen for window resize
window.addEventListener('resize', function() {
setCanvasSize();
});
$scope.colors = [
'#000000', '#FFFFFF', '#0d1b2a', '#1b263b', '#415a77',
'#ef233c', '#d90429', '#f72585', '#7209b7', '#3a0ca3',
'#4361ee', '#4cc9f0', '#06ffa5', '#ffd60a', '#ff6700'
];
$timeout(function() {
$scope.canvas = document.getElementById('drawingCanvas');
$scope.ctx = $scope.canvas.getContext('2d');
// Set initial canvas size
setCanvasSize();
$scope.ctx.lineCap = 'round';
$scope.ctx.lineJoin = 'round';
$scope.saveState();
// Mouse events
$scope.canvas.addEventListener('mousedown', function(e) {
$scope.$apply(function() {
$scope.startDrawing(e);
});
});
$scope.canvas.addEventListener('mousemove', function(e) {
$scope.$apply(function() {
$scope.draw(e);
});
});
$scope.canvas.addEventListener('mouseup', function(e) {
$scope.$apply(function() {
$scope.stopDrawing();
});
});
$scope.canvas.addEventListener('mouseleave', function() {
$scope.$apply(function() {
$scope.stopDrawing();
});
});
// Touch events for mobile
$scope.canvas.addEventListener('touchstart', function(e) {
e.preventDefault();
var touch = e.touches[0];
var mouseEvent = new MouseEvent('mousedown', {
clientX: touch.clientX,
clientY: touch.clientY
});
$scope.canvas.dispatchEvent(mouseEvent);
});
$scope.canvas.addEventListener('touchmove', function(e) {
e.preventDefault();
var touch = e.touches[0];
var mouseEvent = new MouseEvent('mousemove', {
clientX: touch.clientX,
clientY: touch.clientY
});
$scope.canvas.dispatchEvent(mouseEvent);
});
$scope.canvas.addEventListener('touchend', function(e) {
e.preventDefault();
var mouseEvent = new MouseEvent('mouseup', {});
$scope.canvas.dispatchEvent(mouseEvent);
});
}, 0);
$scope.selectTool = function(tool) {
$scope.tool = tool;
};
$scope.selectColor = function(color) {
$scope.selectedColor = color;
};
$scope.updateColor = function() {
// Color updated via ng-model
};
$scope.startDrawing = function(e) {
$scope.isDrawing = true;
var rect = $scope.canvas.getBoundingClientRect();
$scope.startX = e.clientX - rect.left;
$scope.startY = e.clientY - rect.top;
if ($scope.tool === 'brush' || $scope.tool === 'pencil') {
$scope.ctx.beginPath();
$scope.ctx.moveTo($scope.startX, $scope.startY);
}
if ($scope.tool === 'line' || $scope.tool === 'rectangle' || $scope.tool === 'circle') {
$scope.tempCanvas = $scope.ctx.getImageData(0, 0, $scope.canvas.width, $scope.canvas.height);
}
};
$scope.draw = function(e) {
if (!$scope.isDrawing) return;
var rect = $scope.canvas.getBoundingClientRect();
var x = e.clientX - rect.left;
var y = e.clientY - rect.top;
$scope.ctx.strokeStyle = $scope.tool === 'eraser' ? '#ffffff' : $scope.selectedColor;
$scope.ctx.lineWidth = $scope.tool === 'eraser' ? $scope.brushSize * 2 :
$scope.tool === 'pencil' ? 2 : $scope.brushSize;
$scope.ctx.globalAlpha = $scope.opacity / 100;
if ($scope.tool === 'brush' || $scope.tool === 'pencil' || $scope.tool === 'eraser') {
$scope.ctx.lineTo(x, y);
$scope.ctx.stroke();
} else if ($scope.tool === 'line') {
$scope.ctx.putImageData($scope.tempCanvas, 0, 0);
$scope.ctx.beginPath();
$scope.ctx.moveTo($scope.startX, $scope.startY);
$scope.ctx.lineTo(x, y);
$scope.ctx.stroke();
} else if ($scope.tool === 'rectangle') {
$scope.ctx.putImageData($scope.tempCanvas, 0, 0);
$scope.ctx.beginPath();
$scope.ctx.rect($scope.startX, $scope.startY, x - $scope.startX, y - $scope.startY);
$scope.ctx.stroke();
} else if ($scope.tool === 'circle') {
$scope.ctx.putImageData($scope.tempCanvas, 0, 0);
var radius = Math.sqrt(Math.pow(x - $scope.startX, 2) + Math.pow(y - $scope.startY, 2));
$scope.ctx.beginPath();
$scope.ctx.arc($scope.startX, $scope.startY, radius, 0, 2 * Math.PI);
$scope.ctx.stroke();
}
};
$scope.stopDrawing = function() {
if ($scope.isDrawing) {
$scope.isDrawing = false;
$scope.saveState();
}
};
$scope.saveState = function() {
$scope.historyStep++;
if ($scope.historyStep < $scope.history.length) {
$scope.history.length = $scope.historyStep;
}
$scope.history.push($scope.canvas.toDataURL());
};
$scope.undo = function() {
if ($scope.canUndo()) {
$scope.historyStep--;
$scope.restoreState($scope.history[$scope.historyStep]);
}
};
$scope.redo = function() {
if ($scope.canRedo()) {
$scope.historyStep++;
$scope.restoreState($scope.history[$scope.historyStep]);
}
};
$scope.canUndo = function() {
return $scope.historyStep > 0;
};
$scope.canRedo = function() {
return $scope.historyStep < $scope.history.length - 1;
};
$scope.restoreState = function(state) {
var img = new Image();
img.src = state;
img.onload = function() {
$scope.ctx.clearRect(0, 0, $scope.canvas.width, $scope.canvas.height);
$scope.ctx.drawImage(img, 0, 0);
};
};
$scope.clearCanvas = function() {
if (confirm('Are you sure you want to clear the canvas?')) {
$scope.ctx.clearRect(0, 0, $scope.canvas.width, $scope.canvas.height);
$scope.saveState();
}
};
$scope.downloadCanvas = function() {
var link = document.createElement('a');
link.download = 'drawing-' + Date.now() + '.png';
link.href = $scope.canvas.toDataURL();
link.click();
};
});
</script>
</body>
</html>
No comments yet. Be the first!