This web application allows users to upload their photo and overlay various traditional clothing items. It features client-side image display, clothing selection, and manual positioning/resizing, simulating a virtual try-on experience.
<!-- index.html -->
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='UTF-8'>
<meta name='viewport' content='width=device-width, initial-scale=1.0'>
<title>Traditional Clothing Try-On</title>
<link rel='stylesheet' href='style.css'>
</head>
<body>
<div class='container'>
<h1>Virtual Traditional Clothing Try-On</h1>
<p>Upload your photo and try on traditional clothes from around the world!</p>
<!-- User Photo Upload Section -->
<div class='upload-section'>
<label for='userImageInput' class='button'>Upload Your Photo</label>
<input type='file' id='userImageInput' accept='image/*' style='display: none;'>
<div class='image-preview-area'>
<img id='userPhotoPreview' src='' alt='Your Photo' class='hidden'>
<p id='uploadPlaceholder'>Upload your photo here</p>
</div>
</div>
<!-- Clothing Selection Section -->
<div class='clothing-selection'>
<h2>Select Traditional Clothing</h2>
<div class='clothing-options' id='clothingOptions'>
<!-- Example clothing items; replace with your own assets or dynamic loading -->
<img src='https://via.placeholder.com/100x100?text=Kimono' data-clothing='https://upload.wikimedia.org/wikipedia/commons/thumb/1/12/Japanese_kimono_robe.jpg/250px-Japanese_kimono_robe.jpg' alt='Kimono' class='clothing-thumbnail'>
<img src='https://via.placeholder.com/100x100?text=Sari' data-clothing='https://upload.wikimedia.org/wikipedia/commons/thumb/c/c5/Woman_in_Sari.jpg/220px-Woman_in_Sari.jpg' alt='Sari' class='clothing-thumbnail'>
<img src='https://via.placeholder.com/100x100?text=Kilt' data-clothing='https://upload.wikimedia.org/wikipedia/commons/thumb/b/b5/Man_in_Kilt.jpg/220px-Man_in_Kilt.jpg' alt='Kilt' class='clothing-thumbnail'>
<img src='https://via.placeholder.com/100x100?text=Hanbok' data-clothing='https://upload.wikimedia.org/wikipedia/commons/thumb/6/64/Korean_Hanbok_dress_2.jpg/220px-Korean_Hanbok_dress_2.jpg' alt='Hanbok' class='clothing-thumbnail'>
<img src='https://via.placeholder.com/100x100?text=Dashiki' data-clothing='https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Dashiki_Kente.jpg/220px-Dashiki_Kente.jpg' alt='Dashiki' class='clothing-thumbnail'>
</div>
</div>
<!-- Try-On Area -->
<div class='tryon-area'>
<h2>Your Virtual Try-On</h2>
<div id='canvasContainer' class='canvas-container'>
<img id='baseImage' src='' alt='Your Base Photo' class='hidden'>
<img id='overlayClothing' src='' alt='Overlay Clothing' class='hidden draggable resizable rotatable'>
<p id='tryonPlaceholder'>Upload a photo and select clothing to begin!</p>
</div>
<div class='controls'>
<button id='resetButton' class='button hidden'>Reset Overlay</button>
<label for='scaleSlider' class='hidden'>Scale:</label>
<input type='range' id='scaleSlider' min='0.1' max='3' step='0.1' value='1' class='hidden'>
<label for='rotationSlider' class='hidden'>Rotate:</label>
<input type='range' id='rotationSlider' min='0' max='360' step='1' value='0' class='hidden'>
</div>
</div>
</div>
<script src='script.js'></script>
</body>
</html>
/* style.css */
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: flex-start;
min-height: 100vh;
margin: 0;
background-color: #f4f7f6;
color: #333;
padding: 20px;
box-sizing: border-box;
}
.container {
background-color: #fff;
padding: 30px;
border-radius: 10px;
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
max-width: 900px;
width: 100%;
text-align: center;
}
h1, h2 {
color: #0056b3;
}
p {
margin-bottom: 20px;
color: #555;
}
.button {
display: inline-block;
background-color: #007bff;
color: white;
padding: 10px 20px;
border: none;
border-radius: 5px;
cursor: pointer;
font-size: 16px;
text-decoration: none;
transition: background-color 0.3s ease;
}
.button:hover {
background-color: #0056b3;
}
.upload-section, .clothing-selection, .tryon-area {
margin-top: 30px;
padding: 20px;
border: 1px dashed #ccc;
border-radius: 8px;
background-color: #fafafa;
}
.image-preview-area {
width: 100%;
max-width: 300px;
height: 200px;
margin: 20px auto;
border: 1px solid #ddd;
display: flex;
justify-content: center;
align-items: center;
overflow: hidden;
background-color: #e9ecef;
border-radius: 5px;
}
#userPhotoPreview {
max-width: 100%;
max-height: 100%;
object-fit: contain;
}
.clothing-options {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 15px;
margin-top: 20px;
}
.clothing-thumbnail {
width: 100px;
height: 100px;
object-fit: cover;
border: 2px solid #eee;
border-radius: 5px;
cursor: pointer;
transition: transform 0.2s, border-color 0.2s;
}
.clothing-thumbnail:hover {
transform: translateY(-3px);
border-color: #007bff;
}
.clothing-thumbnail.selected {
border-color: #007bff;
box-shadow: 0 0 8px rgba(0, 123, 255, 0.5);
}
.canvas-container {
position: relative;
width: 400px;
height: 400px;
margin: 20px auto;
border: 2px solid #007bff;
overflow: hidden;
background-color: #e9ecef;
display: flex;
justify-content: center;
align-items: center;
border-radius: 5px;
}
#baseImage {
position: absolute;
max-width: 100%;
max-height: 100%;
object-fit: contain;
z-index: 1;
}
#overlayClothing {
position: absolute;
cursor: grab;
z-index: 2;
/* Initial transformation, will be overridden by JS */
transform: translate(-50%, -50%) scale(1) rotate(0deg);
transform-origin: top left;
left: 50%; /* Center initially */
top: 50%; /* Center initially */
}
#overlayClothing.hidden, #userPhotoPreview.hidden, #baseImage.hidden,
#uploadPlaceholder.hidden, #tryonPlaceholder.hidden,
.button.hidden, input[type='range'].hidden, .controls label.hidden {
display: none;
}
.controls {
margin-top: 20px;
display: flex;
justify-content: center;
align-items: center;
gap: 15px;
flex-wrap: wrap;
}
.controls label {
font-weight: bold;
color: #555;
}
input[type='range'] {
width: 150px;
-webkit-appearance: none;
height: 8px;
background: #d3d3d3;
outline: none;
opacity: 0.7;
-webkit-transition: .2s;
transition: opacity .2s;
border-radius: 5px;
}
input[type='range']::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 20px;
height: 20px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
}
input[type='range']::-moz-range-thumb {
width: 20px;
height: 20px;
border-radius: 50%;
background: #007bff;
cursor: pointer;
}
/* script.js */
// --- DOM Elements --- //
const userImageInput = document.getElementById('userImageInput');
const userPhotoPreview = document.getElementById('userPhotoPreview');
const uploadPlaceholder = document.getElementById('uploadPlaceholder');
const clothingOptions = document.getElementById('clothingOptions');
const tryonPlaceholder = document.getElementById('tryonPlaceholder');
const canvasContainer = document.getElementById('canvasContainer');
const baseImage = document.getElementById('baseImage');
const overlayClothing = document.getElementById('overlayClothing');
const resetButton = document.getElementById('resetButton');
const scaleSlider = document.getElementById('scaleSlider');
const rotationSlider = document.getElementById('rotationSlider');
const scaleLabel = document.querySelector('label[for="scaleSlider"]');
const rotationLabel = document.querySelector('label[for="rotationSlider"]');
// --- Global State Variables --- //
// These variables store the current transformation state of the overlay clothing.
let currentScale = 1;
let currentRotation = 0;
// currentX and currentY represent the top-left corner's offset from the canvasContainer's top-left.
let currentX = 0;
let currentY = 0;
let isDragging = false;
let startX, startY; // Stores initial mouse click position relative to the element being dragged.
// --- Event Listeners --- //
/**
* Handles the user's photo upload.
* Reads the selected file and displays it in the preview area and as the base image for try-on.
*/
userImageInput.addEventListener('change', (event) => {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
// Update photo preview in the upload section
userPhotoPreview.src = e.target.result;
userPhotoPreview.classList.remove('hidden');
uploadPlaceholder.classList.add('hidden');
// Set the base image for the try-on area
baseImage.src = e.target.result;
baseImage.classList.remove('hidden');
tryonPlaceholder.classList.add('hidden');
resetOverlay(); // Reset overlay position when a new base image is loaded
};
reader.readAsDataURL(file);
}
});
/**
* Handles the selection of a traditional clothing item.
* Updates the overlay clothing image and enables controls if a base image is present.
*/
clothingOptions.addEventListener('click', (event) => {
if (event.target.classList.contains('clothing-thumbnail')) {
// Remove 'selected' class from previously selected item
document.querySelectorAll('.clothing-thumbnail').forEach(img => {
img.classList.remove('selected');
});
// Add 'selected' class to the clicked item
event.target.classList.add('selected');
const clothingSrc = event.target.dataset.clothing;
if (baseImage.src) {
// Update the overlay clothing image
overlayClothing.src = clothingSrc;
overlayClothing.classList.remove('hidden');
// Show controls for manipulating the overlay
resetButton.classList.remove('hidden');
scaleSlider.classList.remove('hidden');
rotationSlider.classList.remove('hidden');
scaleLabel.classList.remove('hidden');
rotationLabel.classList.remove('hidden');
resetOverlay(); // Reset position and scale for the new clothing item
} else {
alert('Please upload your photo first!');
}
}
});
/**
* Initiates dragging of the overlay clothing image on mouse down.
* Calculates the initial offset to prevent the image from 'jumping'.
*/
overlayClothing.addEventListener('mousedown', (e) => {
if (e.button === 0) { // Only respond to left mouse button
isDragging = true;
overlayClothing.style.cursor = 'grabbing';
// Store the mouse position relative to the overlay image's current top-left corner.
// We calculate this based on the element's actual position relative to the document
// and the mouse's document position.
const rect = overlayClothing.getBoundingClientRect();
startX = e.clientX - rect.left;
startY = e.clientY - rect.top;
e.preventDefault(); // Prevent default browser drag behavior (e.g., image ghosting)
}
});
/**
* Updates the overlay clothing's position during a drag operation.
* Calculates new currentX and currentY based on mouse movement.
*/
document.addEventListener('mousemove', (e) => {
if (isDragging) {
// Get the canvas container's position to correctly offset the overlay
const containerRect = canvasContainer.getBoundingClientRect();
// Calculate new top-left position of the overlay relative to the container
currentX = e.clientX - containerRect.left - startX;
currentY = e.clientY - containerRect.top - startY;
applyTransform(); // Apply the new transform
}
});
/**
* Ends the drag operation on mouse up.
*/
document.addEventListener('mouseup', () => {
if (isDragging) {
isDragging = false;
overlayClothing.style.cursor = 'grab';
}
});
/**
* Handles scaling the overlay clothing via the scale slider.
*/
scaleSlider.addEventListener('input', (event) => {
currentScale = parseFloat(event.target.value);
applyTransform();
});
/**
* Handles rotating the overlay clothing via the rotation slider.
*/
rotationSlider.addEventListener('input', (event) => {
currentRotation = parseFloat(event.target.value);
applyTransform();
});
/**
* Handles resetting the overlay clothing's position, scale, and rotation.
*/
resetButton.addEventListener('click', resetOverlay);
// --- Functions --- //
/**
* Applies the current transformation (position, scale, rotation) to the overlay clothing image.
* Uses CSS `transform` property for smooth and efficient manipulation.
*/
function applyTransform() {
// The transform-origin is set to top left (0,0) in CSS for simplicity.
// currentX and currentY define the pixel offset of the image's top-left corner
// from the canvasContainer's top-left corner.
overlayClothing.style.transform = `translate(${currentX}px, ${currentY}px) scale(${currentScale}) rotate(${currentRotation}deg)`;
}
/**
* Resets the position, scale, and rotation of the overlay clothing image to its initial state.
* This places the clothing image at the top-left of the container with default scale and no rotation.
* Sliders are also reset to their default values.
*/
function resetOverlay() {
currentScale = 1;
currentRotation = 0;
currentX = 0; // Reset to top-left of the canvas container
currentY = 0;
scaleSlider.value = currentScale;
rotationSlider.value = currentRotation;
applyTransform();
}
// --- Initial Setup --- //
// Apply initial transforms to ensure sliders reflect the state and overlay is positioned.
resetOverlay();
/**
* MODERATION NOTE:
* This application provides a client-side interface for users to manually overlay
* clothing items onto their uploaded photos. It is NOT an AI-driven virtual try-on system.
* The core logic for 'making the user wear' clothing (i.e., automatically fitting,
* adjusting perspective, handling occlusions, or generating new images with clothing)
* would typically involve advanced server-side image processing, computer vision (e.g., OpenCV),
* or machine learning models (e.g., Generative Adversarial Networks - GANs, or diffusion models).
*
* To implement automated try-on, you would extend this application as follows:
* 1. Backend API: Create a server-side API (e.g., with Python/Flask/Django, Node.js/Express)
* that receives the user's photo and the selected clothing image (or its ID).
* 2. Image Processing/AI: Integrate a library or model in the backend to perform tasks like:
* a. Human pose/landmark detection in the user's photo.
* b. Segmentation of the user's body from the background.
* c. Warping, resizing, and superimposing the clothing image onto the detected body region,
* adjusting for perspective and body shape.
* d. Handling layering and potential occlusions (e.g., if the user's arm should be over the clothing).
* e. Potentially using AI to generate a more realistic merged image.
* 3. Frontend Integration: The frontend would then send the user's photo and selected clothing
* to this backend API and display the processed image returned by the server, replacing the manual overlay system.
*
* This current code provides the interactive frontend scaffolding required for such a system,
* focusing on user interaction and manual visual placement for demonstration purposes.
*/