add new system

This commit is contained in:
2025-07-22 15:58:34 +03:00
parent d5a95eb281
commit 95e0551540
6 changed files with 446 additions and 186 deletions

19
package-lock.json generated
View File

@@ -12,7 +12,9 @@
"@sveltejs/adapter-node": "^5.2.13", "@sveltejs/adapter-node": "^5.2.13",
"cropperjs": "^2.0.0", "cropperjs": "^2.0.0",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"html2canvas": "^1.4.1" "html2canvas": "^1.4.1",
"svelte-crop-window": "^0.1.1",
"svelte-easy-crop": "^4.0.1"
}, },
"devDependencies": { "devDependencies": {
"@cloudparker/easy-cropperjs-svelte": "^2.4.0", "@cloudparker/easy-cropperjs-svelte": "^2.4.0",
@@ -2353,6 +2355,21 @@
"node": ">=18" "node": ">=18"
} }
}, },
"node_modules/svelte-crop-window": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/svelte-crop-window/-/svelte-crop-window-0.1.1.tgz",
"integrity": "sha512-N5696nYO38iHuYYaafOR6xWJmh4lnCnRBwdTbMBAiAnILMAmR4LoleIlfeCyUVuxTaDtw3tG9KB2MVntQP4R9w==",
"license": "MIT"
},
"node_modules/svelte-easy-crop": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/svelte-easy-crop/-/svelte-easy-crop-4.0.1.tgz",
"integrity": "sha512-0k7vVpHVLrPyobSXqey5IJUmFVxOoCaQrobFEsFXpSCyK8N5jTkRj1VX6NuCOZK8XXcMAqUvV0MktB8D5x1oCw==",
"license": "MIT",
"peerDependencies": {
"svelte": "^5.0.0"
}
},
"node_modules/tailwindcss": { "node_modules/tailwindcss": {
"version": "4.1.11", "version": "4.1.11",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz", "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.11.tgz",

View File

@@ -26,6 +26,8 @@
"@sveltejs/adapter-node": "^5.2.13", "@sveltejs/adapter-node": "^5.2.13",
"cropperjs": "^2.0.0", "cropperjs": "^2.0.0",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"html2canvas": "^1.4.1" "html2canvas": "^1.4.1",
"svelte-crop-window": "^0.1.1",
"svelte-easy-crop": "^4.0.1"
} }
} }

40
src/lib/Alert.svelte Normal file
View File

@@ -0,0 +1,40 @@
<script lang="ts">
export let isOpen = false;
function closeModal() {
isOpen = false;
close();
}
</script>
<div class="inline-flex items-center text-base font-semibold text-gray-900">
<div>
{#if isOpen}
<div
class="relative z-10"
aria-labelledby="dialog-title"
role="dialog"
aria-modal="true"
>
<!-- Затемнение фона -->
<div
class="fixed inset-0 bg-gray-700/20 bg-opacity-75 transition-opacity"
aria-hidden="true"
on:click={closeModal}
></div>
<!-- Центрирование по вертикали и горизонтали -->
<div class="fixed inset-0 z-10 flex items-center justify-center p-4">
<div
class="w-full max-w-md transform rounded-lg bg-white p-6 shadow-xl transition-all"
>
<!-- Центрированное содержимое -->
<div class="flex flex-col items-center space-y-4 text-center">
<slot></slot>
</div>
</div>
</div>
</div>
{/if}
</div>
</div>

View File

@@ -1,63 +1,99 @@
<script lang="ts"> <script>
import EasyCropperjs from "@cloudparker/easy-cropperjs-svelte"; import Cropper from "svelte-easy-crop";
let easyCropperjsRef: EasyCropperjs | null = $state(null); let image = $props(); // начальное изображение
let file: File = $props(); let crop = { x: 0, y: 0 };
let zoom = 1;
let cropSize = { width: 512, height: 512 };
let maxZoom = 10;
let minZoom = 1;
let restrictPosition = true;
let showGrid = false;
let croppedImage = null; // хранение вырезанного изображения
let imageLoaded = false;
// Функция для обработки обрезанного изображения // Функция для обработки загрузки изображения
async function handleCrop() { function handleImageUpload(event) {
const data = await easyCropperjsRef?.crop({ const file = event.target.files[0];
outputWidth: 512, // Ширина обрезанного изображения if (file) {
outputFormat: "png", // Формат обрезанного изображения const reader = new FileReader();
outputQuality: 0.6, // Качество reader.onload = (e) => {
outputType: "file", // Тип данных image = e.target.result; // Set the image
}); imageLoaded = true; // Mark image as loaded
};
// Если данные обрезки получены и это Blob reader.readAsDataURL(file); // Convert to base64
if (data instanceof Blob) {
const downloadLink = document.createElement("a"); // Создаем элемент <a> для скачивания
downloadLink.href = URL.createObjectURL(data); // Создаем URL для обрезанного изображения
downloadLink.download = "cropped_image.png"; // Имя файла для скачивания
downloadLink.click(); // Имитируем клик для скачивания
} else {
console.log("Нет данных для скачивания");
} }
} }
// Получение результата обрезки // Функция для обрезки изображения
function handleCropResult(ev: CustomEvent) { function cropImage(x, y, width, height) {
let base64ImageUrl: string = ev.detail; const img = new Image();
// Используйте base64ImageUrl по вашему усмотрению img.src = image;
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
// Обрезаем изображение с помощью canvas
ctx.drawImage(img, x, y, width, height, 0, 0, width, height);
// Получаем обрезанное изображение в формате base64
croppedImage = canvas.toDataURL("image/png");
};
} }
// Функция для получения файла // Функция для скачивания обрезанного изображения
async function getFile(url: string) { function downloadImage() {
const fileName = url.split("/").pop() || "file.png"; const link = document.createElement("a");
const blob = await (await fetch(url)).blob(); link.href = croppedImage;
const file = new File([blob], fileName, { type: blob.type }); link.download = "cropped-image.png";
return file; link.click();
}
// Обработчик события cropcomplete
function handleCropComplete(event) {
if (!imageLoaded) return; // Prevent cropping if image is not loaded yet
const { x, y, width, height } = event.detail;
cropImage(x, y, width, height);
} }
</script> </script>
<div class="p-4"> <div class="flex flex-col">
<div <div class="h-[1024px] w-[1024px] relative">
class="w-[512px] h-[512px] overflow-hidden relative border-2 border-gray-300" <Cropper
> {image}
{#if file} {cropSize}
<EasyCropperjs {maxZoom}
bind:this={easyCropperjsRef} {minZoom}
outputAspectRatio={1} {restrictPosition}
inputImageFile={file} {showGrid}
onCrop={handleCropResult} bind:crop
/> bind:zoom
{/if} on:cropcomplete={handleCropComplete}
/>
</div> </div>
<!-- Кнопка для обрезки изображения --> <button on:click={downloadImage}> Скачать вырезанное изображение </button>
<button
onclick={handleCrop}
class="mt-4 p-2 bg-blue-500 text-white rounded hover:bg-blue-700"
>
Crop
</button>
</div> </div>
<style>
.upload-btn {
margin: 10px 0;
}
.download-btn {
margin-top: 10px;
padding: 10px 20px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.download-btn:hover {
background-color: #45a049;
}
</style>

View File

@@ -1,28 +1,41 @@
<script> <script lang="ts">
import domtoimage from "dom-to-image"; import domtoimage from "dom-to-image";
import Scropper from "$lib/Scropper.svelte"; import Cropper, {
type CropArea,
type OnCropComplete,
type OnCropCompleteEvent,
} from "svelte-easy-crop";
let image = null; let crop = { x: 0, y: 0 };
let text = ""; let zoom = 1;
let error = ""; let cropSize = { width: 512, height: 512 };
let isDragging = false; let maxZoom = 10;
let btnDisabled = image != null; let minZoom = 1;
let isModalOpen = $state(false); let restrictPosition = true;
let showGrid = false;
let croppedImage = null; // хранение вырезанного изображения
let image: string;
let incorrectImage: string;
let text: string = "";
let error: string = "";
let isDragging: boolean = false;
let isModalOpen: boolean = false;
// Проверка и загрузка изображения // Проверка и загрузка изображения
async function validateAndLoadImage(file) { async function validateAndLoadImage(file: File): Promise<void> {
error = ""; error = "";
const img = new Image(); const img = new Image();
const reader = new FileReader(); const reader = new FileReader();
reader.onload = (e) => { reader.onload = (e: ProgressEvent<FileReader>) => {
img.src = e.target.result; img.src = e.target?.result as string;
img.onload = () => { img.onload = () => {
if (img.width === 512 && img.height === 512) { if (img.width === 512 && img.height === 512) {
image = e.target.result; image = e.target?.result as string;
} else { } else {
incorrectImage = e.target?.result as string;
isModalOpen = true; isModalOpen = true;
error = "Изображение должно быть 512×512 пикселей";
} }
}; };
}; };
@@ -30,164 +43,203 @@
} }
// Обработка Drag and Drop // Обработка Drag and Drop
function handleDrop(e) { function handleDrop(e: DragEvent): void {
e.preventDefault(); e.preventDefault();
isDragging = false; isDragging = false;
const file = e.dataTransfer.files[0]; const file = (e.dataTransfer as DataTransfer).files[0]; // Explicitly casting to DataTransfer
if (file && file.type.startsWith("image/")) { if (file && file.type.startsWith("image/")) {
validateAndLoadImage(file); validateAndLoadImage(file);
} }
} }
function handleDragOver(e) { function handleDragOver(e: DragEvent): void {
e.preventDefault(); e.preventDefault();
isDragging = true; isDragging = true;
} }
function handleDragLeave() { function handleDragLeave(): void {
isDragging = false; isDragging = false;
} }
async function saveImage() { async function saveImage(): Promise<void> {
if (!image) return; if (!image) return;
let node = document.getElementById("result"); let node = document.getElementById("result");
// Убираем лишние стили // Убираем лишние стили
node.style.border = "none"; if (node) {
node.style.padding = "0"; node.style.border = "none";
node.style.margin = "0"; node.style.padding = "0";
node.style.outline = "none"; node.style.margin = "0";
node.style.outline = "none";
domtoimage domtoimage
.toPng(node, { .toPng(node, {
style: { style: {
border: "none", // Убираем рамки border: "none", // Убираем рамки
padding: "0", // Убираем отступы padding: "0", // Убираем отступы
margin: "0", // Убираем маргины margin: "0", // Убираем маргины
outline: "none", // Убираем outline outline: "none", // Убираем outline
}, },
}) })
.then(function (dataUrl) { .then((dataUrl: string) => {
var link = document.createElement("a"); var link = document.createElement("a");
link.download = "avatar.png"; link.download = "avatar.png";
link.href = dataUrl; link.href = dataUrl;
link.click(); // Скачать изображение link.click(); // Скачать изображение
}); });
}
} }
const openModal = () => { const openModal = (): void => {
isModalOpen = true; isModalOpen = true;
}; };
// Закрыть модальное окно // Закрыть модальное окно
const closeModal = () => { const closeModal = (): void => {
isModalOpen = false; isModalOpen = false;
}; };
function cropImage(x: number, y: number, width: number, height: number) {
const img = new Image();
img.src = incorrectImage;
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = 512;
canvas.height = 512;
// Обрезаем изображение с помощью canvas
ctx.drawImage(img, x, y, width, height, 0, 0, 512, 512);
// Получаем обрезанное изображение в формате base64
croppedImage = canvas.toDataURL("image/png");
};
}
const handleCropComplete = (event: OnCropCompleteEvent) => {
const { x, y, width, height } = event.pixels;
cropImage(x, y, width, height);
};
function saveToMainImage() {
image = croppedImage; // Ссылка на обрезанное изображение
isModalOpen = false;
}
</script> </script>
<div class="bg-white rounded-xl shadow-lg p-6"> <div>
<div {#if isModalOpen}
class="preview-container relative bg-gray-50 rounded-lg mb-4 overflow-hidden border-2 transition-colors h-[512px]" <div class="bg-white rounded-xl shadow-lg p-6">
ondrop={handleDrop} <div class="flex flex-col">
ondragover={handleDragOver} <div class="h-[700px] w-[700px] relative rounded-2xl">
ondragleave={handleDragLeave} <Cropper
style="cursor: pointer" image={incorrectImage}
onclick={() => document.getElementById("fileInput").click()} {cropSize}
> {maxZoom}
<!-- Состояние без изображения --> {minZoom}
{#if !image} {restrictPosition}
<div {showGrid}
class="absolute inset-0 flex flex-col items-center justify-center cursor-pointer" bind:crop
> bind:zoom
<svg oncropcomplete={handleCropComplete}
class="w-16 h-16 text-gray-400 mb-3" />
fill="none" </div>
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
></path>
</svg>
<p class="text-lg text-gray-500">Перетащите изображение 512×512</p>
<p class="text-sm text-gray-400 mt-1">или кликните для выбора</p>
</div>
{/if}
<!-- Загруженное изображение --> <button
{#if image} onclick={saveToMainImage}
<div id="result" class="relative"> class="px-6 py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white font-medium rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
<img src={image} class="object-cover" /> >
<!-- Текст --> Сохранить
{#if text} </button>
<div class="absolute bottom-0 left-0 right-0 py-5 bg-black/70"> </div>
<h1 </div>
class="text-center text-5xl font-bold text-transparent bg-clip-text bg-cover bg-right leading-17 tracking-tight" {:else}
style="background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAADICAYAAACeXFkKAAAACXBIWXMAAA7EAAAOxAGVKw4bAAADs0lEQVR42u3WTU7DMBCA0RkOySU4HScMi4bW/06zYcF7EhKV09SeptKXn1/fR0RExhEREZkR8ft/RFRrERFZrh3Pax7vna2d/+drrbsu6/d098ij20+5p/Faeab2/vvzjs7UXbc9UzO/6Zmae+RgfuU9Fmcq97Q/U3NdDuZ34UzlLG+dafLsrM5UP1d3zrQ+b/nsDJ+r7kzXfivP++fN30q37/1vpbp/c6bjuXL+Zdavu/XXTofr5wcexe67e+Xo3lm/zv7e1edmrPd5DnG+/viCp2vVPt6byWoet2eSq31enEnz3Q6v3c0ki/kuZjb9Xqf7zM3Mms/NzTkiNzO78Kw/93FvJvNn+f2ZrJ/lizNpvtuPAAD+HQEAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAA/tYPVhsMy8Jj9qoAAAAASUVORK5CYII=');" <div class="bg-white rounded-xl shadow-lg p-6">
<div
class="preview-container relative bg-gray-50 rounded-lg mb-4 overflow-hidden border-2 transition-colors h-[512px]"
ondrop={handleDrop}
ondragover={handleDragOver}
ondragleave={handleDragLeave}
style="cursor: pointer"
onclick={() => document.getElementById("fileInput").click()}
>
<!-- Состояние без изображения -->
{#if !image}
<div
class="absolute inset-0 flex flex-col items-center justify-center cursor-pointer"
>
<svg
class="w-16 h-16 text-gray-400 mb-3"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
> >
{text} <path
</h1> stroke-width="2"
d="M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
></path>
</svg>
<p class="text-lg text-gray-500">Перетащите изображение</p>
<p class="text-sm text-gray-400 mt-1">или кликните для выбора</p>
</div> </div>
{/if} {/if}
</div>
{/if}
<input <!-- Загруженное изображение -->
id="fileInput" {#if image}
type="file" <div id="result" class="relative">
accept="image/*" <img src={image} class="object-cover" />
class="hidden" <!-- Текст -->
onchange={(e) => validateAndLoadImage(e.target.files[0])} {#if text}
/> <div
</div> class="absolute bottom-0 left-0 right-0 py-5 bg-black/70 rounded-t-4xl"
>
<!-- Сообщение об ошибке --> <h1
{#if error} class="text-center text-5xl font-bold text-transparent bg-clip-text bg-cover bg-right leading-17 tracking-tight"
<p class="text-red-500 text-sm mb-4 text-center">{error}</p> style="background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAgAAAADICAYAAACeXFkKAAAACXBIWXMAAA7EAAAOxAGVKw4bAAADs0lEQVR42u3WTU7DMBCA0RkOySU4HScMi4bW/06zYcF7EhKV09SeptKXn1/fR0RExhEREZkR8ft/RFRrERFZrh3Pax7vna2d/+drrbsu6/d098ij20+5p/Faeab2/vvzjs7UXbc9UzO/6Zmae+RgfuU9Fmcq97Q/U3NdDuZ34UzlLG+dafLsrM5UP1d3zrQ+b/nsDJ+r7kzXfivP++fN30q37/1vpbp/c6bjuXL+Zdavu/XXTofr5wcexe67e+Xo3lm/zv7e1edmrPd5DnG+/viCp2vVPt6byWoet2eSq31enEnz3Q6v3c0ki/kuZjb9Xqf7zM3Mms/NzTkiNzO78Kw/93FvJvNn+f2ZrJ/lizNpvtuPAAD+HQEAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAABAAAIAAAAAEAAAgAAEAAAAACAAAQAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAACAAAQAAAAAIAABAAAIAAAAAEAAAgAAAAAQAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAAEAAAgAAAAAQAACAAAAABAAAIAABAAAAAAgAABAAAIAAAAAEAAAgAAEAAAAACAAAQAACAAAAA/tYPVhsMy8Jj9qoAAAAASUVORK5CYII=');"
{/if} >
{text}
<!-- Поле ввода текста --> </h1>
<div class="flex flex-col sm:flex-row gap-3 mb-4"> </div>
<input {/if}
type="text" </div>
bind:value={text}
placeholder="Должность"
class="flex-grow px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onclick={saveImage}
disabled={btnDisabled}
class="px-6 py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white font-medium rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
>
Сохранить 512×512
</button>
</div>
<p class="text-sm text-gray-500 text-center">
Поддерживаются только изображения размером 512×512 пикселей
</p>
<!-- Модальное окно -->
{#if isModalOpen}
<div
class="fixed inset-0 bg-gray-900 bg-opacity-50 flex justify-center items-center z-50"
>
<div class="bg-white p-4 rounded shadow-lg w-[512px] h-[512px]">
<h2 class="text-xl font-semibold mb-4">Crop your image</h2>
{#if file}
<Scropper {file}></Scropper>
{/if} {/if}
<div class="mt-4 flex justify-between">
<button <input
class="px-4 py-2 bg-gray-500 text-white rounded hover:bg-gray-700" id="fileInput"
onclick={closeModal} type="file"
> accept="image/*"
Close class="hidden"
</button> onchange={(e) =>
</div> validateAndLoadImage((e.target as HTMLInputElement).files[0])}
/>
</div> </div>
<!-- Сообщение об ошибке -->
{#if error}
<p class="text-red-500 text-sm mb-4 text-center">{error}</p>
{/if}
<!-- Поле ввода текста -->
<div class="flex flex-col sm:flex-row gap-3 mb-4">
<input
type="text"
bind:value={text}
placeholder="Должность"
class="flex-grow px-4 py-3 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
/>
<button
onclick={saveImage}
class="px-6 py-3 bg-gradient-to-r from-purple-600 to-blue-600 text-white font-medium rounded-lg hover:opacity-90 transition-opacity disabled:opacity-50 disabled:cursor-not-allowed"
>
Сохранить 512×512
</button>
</div>
<p class="text-sm text-gray-500 text-center">
Размер изображения 512×512 пикселей
</p>
</div> </div>
{/if} {/if}
</div> </div>

View File

@@ -0,0 +1,113 @@
<script>
import Cropper from "svelte-easy-crop";
let image = ""; // начальное изображение
let crop = { x: 0, y: 0 };
let zoom = 1;
let cropSize = { width: 512, height: 512 };
let maxZoom = 10;
let minZoom = 1;
let restrictPosition = true;
let showGrid = false;
let croppedImage = null; // хранение вырезанного изображения
// Функция для обработки загрузки изображения
function handleImageUpload(event) {
const file = event.target.files[0];
if (file) {
const reader = new FileReader();
reader.onload = (e) => {
image = e.target.result; // Обновляем изображение
};
reader.readAsDataURL(file); // Преобразуем файл в Data URL
}
}
// Функция для обрезки изображения
function cropImage(x, y, width, height) {
const img = new Image();
img.src = image;
img.onload = () => {
const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
canvas.width = width;
canvas.height = height;
// Обрезаем изображение с помощью canvas
ctx.drawImage(img, x, y, width, height, 0, 0, width, height);
// Получаем обрезанное изображение в формате base64
croppedImage = canvas.toDataURL("image/png");
};
}
// Функция для скачивания обрезанного изображения
function downloadImage() {
if (croppedImage) {
const link = document.createElement("a");
link.href = croppedImage;
link.download = "cropped-image.png";
link.click();
} else {
alert("Сначала выполните обрезку изображения!");
}
}
// Обработчик события cropcomplete
function handleCropComplete(event) {
const { x, y, width, height } = event.detail;
cropImage(x, y, width, height);
}
</script>
<div class="flex flex-col">
<div class="h-[1024px] w-[1024px] relative">
<Cropper
{image}
{cropSize}
{maxZoom}
{minZoom}
{restrictPosition}
{showGrid}
bind:crop
bind:zoom
on:cropcomplete={handleCropComplete}
/>
</div>
<div>
<!-- Кнопка для загрузки изображения -->
<input
type="file"
accept="image/*"
on:change={handleImageUpload}
class="upload-btn"
/>
</div>
{#if croppedImage}
<!-- Кнопка для скачивания вырезанного изображения -->
<button on:click={downloadImage}> Скачать вырезанное изображение </button>
{/if}
</div>
<style>
.upload-btn {
margin: 10px 0;
}
.download-btn {
margin-top: 10px;
padding: 10px 20px;
background-color: #4caf50;
color: white;
border: none;
border-radius: 5px;
cursor: pointer;
}
.download-btn:hover {
background-color: #45a049;
}
</style>