您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在WhatsApp Web中创建自定义贴纸。
// ==UserScript== // @name WhatsApp Sticker Creator with Custom Maker Enhanced // @version 1.2 // @name:af Persoonlike Stickertjies // @description:af Skep persoonlike stickertjies in WhatsApp Web. // @name:ar ملصقات مخصصة // @description:ar إنشاء ملصقات مخصصة في WhatsApp Web. // @name:az Fərdi stikerlər // @description:az WhatsApp Web-də fərdi stikerlər yaradın. // @name:bg Персонализирани стикери // @description:bg Създаване на персонализирани стикери в WhatsApp Web. // @name:bn কাস্টম স্টিকার // @description:bn WhatsApp Web-এ কাস্টম স্টিকার তৈরি করুন। // @name:bs Prilagođene naljepnice // @description:bs Kreirajte prilagođene naljepnice u WhatsApp Webu. // @name:ca Gomets personalitzats // @description:ca Crea gomets personalitzats a WhatsApp Web. // @name:cs Vlastní nálepky // @description:cs Vytvářejte vlastní nálepky ve WhatsApp Webu. // @name:cy Stickeriaid addasedig // @description:cy Creu stickeriaid addasedig yn WhatsApp Web. // @name:da Brugerdefinerede stickers // @description:da Opret brugerdefinerede stickers i WhatsApp Web. // @name:de Benutzerdefinierte Aufkleber // @description:de Erstellen Sie benutzerdefinierte Aufkleber in WhatsApp Web. // @name:el Προσαρμοσμένα αυτοκόλλητα // @description:el Δημιουργήστε προσαρμοσμένα αυτοκόλλητα στο WhatsApp Web. // @name:en Custom Stickers // @description:en Create custom stickers in WhatsApp Web. // @name:eo Propraj glumarkoj // @description:eo Kreu proprajn glumarkojn en WhatsApp Web. // @name:es Stickers personalizados // @description:es Crear stickers personalizados en WhatsApp Web. // @name:et Kohandatud kleepsud // @description:et Looge WhatsApp Web-is kohandatud kleepsud. // @name:eu Pertsonalizatutako itsaskiak // @description:eu Sortu pertsonalizatutako itsaskiak WhatsApp Web-en. // @name:fa برچسبهای سفارشی // @description:fa ایجاد برچسبهای سفارشی در WhatsApp Web. // @name:fi Mukautetut tarrat // @description:fi Luo mukautettuja tarranauhoja WhatsApp Webiin. // @name:fr Autocollants personnalisés // @description:fr Créer des autocollants personnalisés dans WhatsApp Web. // @name:gl Adhesivos personalizados // @description:gl Crea adhesivos personalizados en WhatsApp Web. // @name:gu કસ્ટમ સ્ટિકર્સ // @description:gu WhatsApp Web માં કસ્ટમ સ્ટિકર્સ બનાવો. // @name:he מדבקות מותאמות אישית // @description:he צור מדבקות מותאמות אישית ב-WhatsApp Web. // @name:hi कस्टम स्टिकर // @description:hi WhatsApp Web में कस्टम स्टिकर बनाएं। // @name:hr Prilagođene naljepnice // @description:hr Stvorite prilagođene naljepnice u WhatsApp Webu. // @name:hu Egyéni matricák // @description:hu Hozzon létre egyéni matricákat a WhatsApp Webben. // @name:id Stiker kustom // @description:id Buat stiker kustom di WhatsApp Web. // @name:it Sticker personalizzati // @description:it Crea sticker personalizzati su WhatsApp Web. // @name:ja カスタムステッカー // @description:ja WhatsApp Webでカスタムステッカーを作成します。 // @name:ka მორგებული სტიკერები // @description:ka შექმენით მორგებული სტიკერები WhatsApp Web-ში. // @name:kk Таңбалар // @description:kk WhatsApp Web-де тұтынушыға сәйкес таңбалар жасаңыз. // @name:km ស្លាកតាមតម្រូវការ // @description:km បង្កើតស្លាកតាមតម្រូវការនៅលើ WhatsApp Web។ // @name:kn ಅನುಗುಣವಾದ ಸ್ಟಿಕರ್ಗಳು // @description:kn WhatsApp Web ನಲ್ಲಿ ಅನುಗುಣವಾದ ಸ್ಟಿಕರ್ಗಳನ್ನು ರಚಿಸಿ. // @name:ko 사용자 정의 스티커 // @description:ko WhatsApp 웹에서 사용자 정의 스티커를 만듭니다. // @name:ku Stikerên xwerû // @description:ku Di WhatsApp Web de stikerên xwerû biafirîne. // @name:ky Көнүлгө ылайыктуу стикерлер // @description:ky WhatsApp Web'de кардардын көңүлүнө ылайыктуу стикерлерди түзгүлө. // @name:lt Pasirinktini lipdukai // @description:lt Sukurkite pasirinktinius lipdukus „WhatsApp Web“. // @name:lv Pielāgotas uzlīmes // @description:lv Izveidojiet pielāgotas uzlīmes WhatsApp tīmeklī. // @name:mk Прилагодени стикери // @description:mk Креирајте прилагодени стикери во WhatsApp Web. // @name:ml ആവശ്യമനുസരിച്ച് സ്റ്റിക്കർ // @description:ml WhatsApp വെബിൽ ആവശ്യമനുസരിച്ച് സ്റ്റിക്കർ സൃഷ്ടിക്കുക. // @name:mn Өөрийн хүссэн шошго // @description:mn WhatsApp Web дээр өөрийн хүссэн шошго үүсгэх. // @name:mr कस्टम स्टिकर // @description:mr WhatsApp Web मध्ये कस्टम स्टिकर तयार करा. // @name:ms Pelekat tersuai // @description:ms Cipta pelekat tersuai di WhatsApp Web. // @name:my စိတ်ကြိုက်နှိပ်ပုံများ // @description:my WhatsApp Web တွင်စိတ်ကြိုက်သတ်မှတ်ထားသော နှိပ်ပုံများဖန်တီးပါ။ // @name:nb Egne klistremerker // @description:nb Lag egne klistremerker i WhatsApp Web. // @name:ne अनुकूलित स्टिकरहरू // @description:ne WhatsApp वेबमा अनुकूलित स्टिकरहरू सिर्जना गर्नुहोस्। // @name:nl Aangepaste stickers // @description:nl Maak aangepaste stickers in WhatsApp Web. // @name:nn Tilpassa klistremerke // @description:nn Lag tilpassa klistremerke i WhatsApp Web. // @name:no Egne klistremerker // @description:no Lag egne klistremerker i WhatsApp Web. // @name:pa ਕਸਟਮ ਸਟਿੱਕਰ // @description:pa WhatsApp ਵੈਬ ਵਿੱਚ ਕਸਟਮ ਸਟਿੱਕਰ ਬਣਾਓ। // @name:pl Niestandardowe naklejki // @description:pl Twórz niestandardowe naklejki w WhatsApp Web. // @name:pt Adesivos personalizados // @description:pt Criar adesivos personalizados no WhatsApp Web. // @name:ro Autocolante personalizate // @description:ro Creați autocolante personalizate în WhatsApp Web. // @name:ru Стикеры // @description:ru Создавайте собственные стикеры в WhatsApp Web. // @name:si විශේෂිත සටිකර // @description:si WhatsApp Web හි විශේෂිත සටිකර සාදන්න. // @name:sk Vlastné nálepky // @description:sk Vytvorte vlastné nálepky v službe WhatsApp Web. // @name:sl Prilagojene nalepke // @description:sl Ustvarite prilagojene nalepke v WhatsApp Spletu. // @name:sq Ngjitës të personalizuar // @description:sq Krijoni ngjitës të personalizuar në WhatsApp Web. // @name:sr Прилагодљиве налепнице // @description:sr Направите прилагодљиве налепнице у ВхатсАпп Вебу. // @name:sv Anpassade klistermärken // @description:sv Skapa anpassade klistermärken i WhatsApp Web. // @name:sw Lebo maalum // @description:sw Tengeneza lebo maalum katika WhatsApp Web. // @name:ta தனிப்பயனாக அட்டைகள் // @description:ta WhatsApp வலைதளத்தில் தனிப்பயனாக அட்டைகள் உருவாக்கவும். // @name:te అనుకూలిత స్టికర్లు // @description:te WhatsApp వెబ్లో అనుకూలిత స్టికర్లు సృష్టించండి. // @name:th สติกเกอร์แบบกำหนดเอง // @description:th สร้างสติกเกอร์แบบกำหนดเองใน WhatsApp Web // @name:tr Özel etiketler // @description:tr WhatsApp Web'de özel etiketler oluşturun. // @name:uk Власні наклейки // @description:uk Створюйте власні наклейки в WhatsApp Web. // @name:ur کسٹم اسٹکر // @description:ur WhatsApp ویب میں کسٹم اسٹکر بنائیں۔ // @name:uz Maxsus stikerlar // @description:uz WhatsApp Web-da maxsus stikerlar yarating. // @name:vi Nhãn dán tùy chỉnh // @description:vi Tạo nhãn dán tùy chỉnh trong WhatsApp Web. // @name:zh 自定义贴纸 // @description:zh 在WhatsApp Web中创建自定义贴纸。 // @name:zh-CN 自定义贴纸 // @description:zh-CN 在WhatsApp Web中创建自定义贴纸。 // @name:zh-TW 自訂貼紙 // @description:zh-TW 在WhatsApp Web中建立自訂貼紙。 // @author DeveloperMDCM // @match https://web.whatsapp.com/ // @icon https://static-00.iconduck.com/assets.00/whatsapp-icon-1020x1024-iykox85t.png // @grant GM_addStyle // @run-at document-end // @compatible chrome // @compatible firefox // @compatible opera // @compatible safari // @compatible edge // @license MIT // @namespace https://github.com/DeveloperMDCM/ // @homepage https://github.com/DeveloperMDCM/ // @description Create custom stickers in WhatsApp Web. // ==/UserScript== (function () { 'use strict'; console.log('Script en ejecución by: DeveloperMDCM'); const HEADER_STYLE = 'color: #F00; font-size: 24px; font-family: sans-serif;'; const MESSAGE_STYLE = 'color: #00aaff; font-size: 16px; font-family: sans-serif;'; const CODE_STYLE = 'font-size: 14px; font-family: monospace;'; console.log( '%cStiker Maker for Whatsapp Web\n' + '%cRun %c(v1.0)\n' + 'By: DeveloperMDCM.', HEADER_STYLE, CODE_STYLE, MESSAGE_STYLE ); // Variables globales para rotación y hover let isRotating = false; let initialRotateAngle = 0; let initialElementRotation = 0; let hoveredElement = null; let currentMousePos = { x: 0, y: 0 }; const colorsText = ["#000000", "#ff0000", "#00ff00", "#0000ff", "#ffff00", "#00ffff", "#ff00ff", "#ffffff", "#ff000000"]; GM_addStyle(` /* Panel principal */ #stickerPanel { position: fixed; top: 0; right: 0; width: 580px; height: auto; max-height: 90vh; overflow-y: auto; background-color: #111b21; border: 1px solid #202c33; padding: 10px; z-index: 10000; box-shadow: 0 2px 8px rgba(0,0,0,0.2); font-family: "Helvetica Neue", Helvetica, Arial, sans-serif; } /* Pestañas */ .tabsContainer { display: flex; justify-content: space-around; margin-bottom: 10px; } .tabsContainer button { flex: 1; padding: 8px; border: none; cursor: pointer; background-color: #005c4b; color: #fff; font-weight: bold; } .tabsContainer button:first-child { margin-right: 5px; } .tabsContainer button:last-child { margin-left: 5px; } /* Sección Clásica */ .dropZone { border: 2px dashed #ccc; padding: 20px; text-align: center; margin-bottom: 10px; cursor: pointer; background-color: #111b21; } input[type="file"] { display: none; } #createSticker, #createCustomSticker { width: 100%; padding: 8px; margin-top: 5px; background-color: #005c4b; border: none; color: #fff; font-weight: bold; border-radius: 3px; } #status, #customStatus { font-size: 12px; color: #555; text-align: center; margin-top: 5px; } #previewCanvas { display: none; } /* Sección Personalizada */ #customSection { display: none; } /* Toolbar y menús emergentes */ #customToolbar { display: flex; flex-wrap: wrap; gap: 5px; margin-bottom: 5px; align-items: center; } #customToolbar button { padding: 5px 8px; cursor: pointer; border: none; background-color: #005c4b; color: #fff; border-radius: 3px; } #customToolbar select { padding: 4px; } /* Panel de opciones del lápiz y de formas */ #pencilOptionsPanel, #shapeOptionsPanel { display: none; margin: 5px 0; padding: 5px; border: 1px solid #ddd; background-color: #005c4b; font-size: 12px; border-radius: 3px; } /* Botones de color y tamaño */ .colorButton, .sizeButton { width: 15px; height: 15px; border-radius: 50%; border: 2px solid #ccc; display: inline-block; margin: 2px; cursor: pointer; } .sizeButton[data-size="2"] { width: 8px; height: 8px; } .sizeButton[data-size="4"] { width: 12px; height: 12px; } .sizeButton[data-size="6"] { width: 16px; height: 16px; } .sizeButton[data-size="8"] { width: 20px; height: 20px; } #pencilColorContainer, #pencilSizeContainer { display: inline-block; vertical-align: middle; } /* Panel para formas */ #shapeOptionsPanel button { margin-right: 5px; padding: 3px 6px; border: none; color: #fff; border-radius: 3px; cursor: pointer; } /* Área de canvas con fondo ajedrezado */ .canvasContainer { border: 2px dashed #ccc; width: 100%; height: 60vh; max-height: 60vh; margin: auto; position: relative; } #customCanvas { width: 100%; height: 100%; background-size: 20px 20px; background-image: linear-gradient(45deg, #ccc 25%, transparent 25%), linear-gradient(-45deg, #ccc 25%, transparent 25%), linear-gradient(45deg, transparent 75%, #ccc 75%), linear-gradient(-45deg, #fff 75%, #ccc 75%); background-size: 20px 20px; background-position: 0 0, 0 10px, 10px -10px, -10px 0px; display: block; } /* Botón flotante */ #openStickerPanel { position: fixed; bottom: 20px; right: 20px; padding: 10px 15px; background-color: #005c4b; color: #fff; border: none; border-radius: 5px; cursor: pointer; z-index: 10000; box-shadow: 0 2px 8px rgba(0,0,0,0.2); } /* Panel de edición de texto */ #textEditorPanel { margin-top: 10px; padding: 5px; display: none; border-radius: 3px; } #textEditorPanel label { margin: 3px 5px; } /* Panel de emojis */ #emojiContainer { position: fixed; top: 0; right: 600px; width: auto; height: 400px; background-color: #111b21; border: 1px solid #ddd; box-shadow: 0 4px 12px rgba(0,0,0,0.2); z-index: 10000; display: none; flex-direction: column; border-radius: 3px; } #emojiCategoryContainer { display: flex; justify-content: space-around; padding: 5px; } #emojiCategoryContainer button { background-color: #005c4b; color: #fff; border: none; padding: 5px; cursor: pointer; flex: 1; margin: 0 2px; border-radius: 3px; } #emojiContent { overflow-y: auto; height: 350px; padding: 10px 0 30px 10px; display: grid; background-color: black; grid-template-columns: repeat(6, 1fr); gap: 5px; } #textFontSelect { width: auto; appearance: auto; } #textFontSelect:not(:invalid) { color: #fff; } .textEditorContent { display: flex; flex-direction: column; gap: 6px; } `); // ========================= // Espera a que la página se cargue // ========================= window.addEventListener("load", () => { setTimeout(initStickerTool, 3000); }); const emojis = { faces_emotion: [ { "emoji": "😀" }, { "emoji": "😁" }, { "emoji": "😂" }, { "emoji": "🤣" }, { "emoji": "😃" }, { "emoji": "😄" }, { "emoji": "😅" }, { "emoji": "😆" }, { "emoji": "😉" }, { "emoji": "😊" }, { "emoji": "😋" }, { "emoji": "😎" }, { "emoji": "😍" }, { "emoji": "😘" }, { "emoji": "🥰" }, { "emoji": "😗" }, { "emoji": "😙" }, { "emoji": "🥲" }, { "emoji": "😚" }, { "emoji": "☺️" }, { "emoji": "🙂" }, { "emoji": "🤗" }, { "emoji": "🤩" }, { "emoji": "🤔" }, { "emoji": "🫡" }, { "emoji": "🤨" }, { "emoji": "😐" }, { "emoji": "😑" }, { "emoji": "😶" }, { "emoji": "🫥" }, { "emoji": "😶🌫️" }, { "emoji": "🙄" }, { "emoji": "😏" }, { "emoji": "😣" }, { "emoji": "😥" }, { "emoji": "😮" }, { "emoji": "🤐" }, { "emoji": "😯" }, { "emoji": "😪" }, { "emoji": "😫" }, { "emoji": "🥱" }, { "emoji": "😴" }, { "emoji": "😌" }, { "emoji": "😛" }, { "emoji": "😜" }, { "emoji": "😝" }, { "emoji": "🤤" }, { "emoji": "😒" }, { "emoji": "😓" }, { "emoji": "😔" }, { "emoji": "😕" }, { "emoji": "🫤" }, { "emoji": "🙃" }, { "emoji": "🫠" }, { "emoji": "🤑" }, { "emoji": "😲" }, { "emoji": "☹️" }, { "emoji": "🙁" }, { "emoji": "😖" }, { "emoji": "😞" }, { "emoji": "😟" }, { "emoji": "😤" }, { "emoji": "😢" }, { "emoji": "😭" }, { "emoji": "😦" }, { "emoji": "😧" }, { "emoji": "😨" }, { "emoji": "😩" }, { "emoji": "🤯" }, { "emoji": "😬" }, { "emoji": "😮💨" }, { "emoji": "😰" }, { "emoji": "😱" }, { "emoji": "🥵" }, { "emoji": "🥶" }, { "emoji": "😳" }, { "emoji": "🤪" }, { "emoji": "😵" }, { "emoji": "😵💫" }, { "emoji": "🥴" }, { "emoji": "😠" }, { "emoji": "😡" }, { "emoji": "🤬" }, { "emoji": "😷" }, { "emoji": "🤒" }, { "emoji": "🤕" }, { "emoji": "🤢" }, { "emoji": "🤮" }, { "emoji": "🤧" }, { "emoji": "😇" }, { "emoji": "🥳" }, { "emoji": "🥸" }, { "emoji": "🥺" }, { "emoji": "🥹" }, { "emoji": "🤠" }, { "emoji": "🤡" }, { "emoji": "🤥" }, { "emoji": "🫨" }, { "emoji": "🤫" }, { "emoji": "🤭" }, { "emoji": "🫢" }, { "emoji": "🫣" }, { "emoji": "🧐" }, { "emoji": "🤓" }, { "emoji": "😈" }, { "emoji": "👿" }, { "emoji": "👹" }, { "emoji": "👺" }, { "emoji": "💀" }, { "emoji": "☠️" }, { "emoji": "👻" }, { "emoji": "👽" }, { "emoji": "👾" }, { "emoji": "💩" }, { "emoji": "🤖" } ], animals: [ { "emoji": "😺" }, { "emoji": "😸" }, { "emoji": "😹" }, { "emoji": "😻" }, { "emoji": "😼" }, { "emoji": "😽" }, { "emoji": "🙀" }, { "emoji": "😿" }, { "emoji": "😾" }, { "emoji": "🙈" }, { "emoji": "🙉" }, { "emoji": "🙊" }, { "emoji": "🐵" }, { "emoji": "🐶" }, { "emoji": "🐺" }, { "emoji": "🐱" }, { "emoji": "🦁" }, { "emoji": "🐯" }, { "emoji": "🦒" }, { "emoji": "🦊" }, { "emoji": "🦝" }, { "emoji": "🐮" }, { "emoji": "🐷" }, { "emoji": "🐗" }, { "emoji": "🐭" }, { "emoji": "🐹" }, { "emoji": "🐰" }, { "emoji": "🐻" }, { "emoji": "🐨" }, { "emoji": "🐼" }, { "emoji": "🐸" }, { "emoji": "🦓" }, { "emoji": "🐴" }, { "emoji": "🫎" }, { "emoji": "🫏" }, { "emoji": "🦄" }, { "emoji": "🐔" }, { "emoji": "🐲" }, { "emoji": "🐽" }, { "emoji": "🐾" }, { "emoji": "🐒" }, { "emoji": "🦍" }, { "emoji": "🦧" }, { "emoji": "🦮" }, { "emoji": "🐩" }, { "emoji": "🐕" }, { "emoji": "🐈" }, { "emoji": "🐅" }, { "emoji": "🐆" }, { "emoji": "🦌" }, { "emoji": "🦬" }, { "emoji": "🦏" }, { "emoji": "🐘" }, { "emoji": "🐁" }, { "emoji": "🐀" }, { "emoji": "🦔" }, { "emoji": "🐇" }, { "emoji": "🦎" }, { "emoji": "🐊" }, { "emoji": "🐢" }, { "emoji": "🐍" }, { "emoji": "🐉" }, { "emoji": "🦕" }, { "emoji": "🦖" }, { "emoji": "🐬" }, { "emoji": "🐳" }, { "emoji": "🐋" }, { "emoji": "🐟" }, { "emoji": "🐠" }, { "emoji": "🐡" }, { "emoji": "🦀" }, { "emoji": "🐚" } ] }; function initStickerTool() { if (document.getElementById("stickerPanel")) return; // Crear panel principal const panel = document.createElement("div"); panel.id = "stickerPanel"; panel.style.display = "none"; // Pestañas const tabsContainer = document.createElement("div"); tabsContainer.className = "tabsContainer"; const btnClassic = document.createElement("button"); btnClassic.textContent = "Classic Sticker"; const btnCustom = document.createElement("button"); btnCustom.textContent = "Custom Sticker"; tabsContainer.appendChild(btnClassic); tabsContainer.appendChild(btnCustom); panel.appendChild(tabsContainer); // Sección Clásica const classicSection = document.createElement("div"); classicSection.id = "classicSection"; classicSection.innerHTML = ` <div id="dropZone" class="dropZone">Drag or click to select image</div> <input type="file" id="fileInput" accept="image/*" /> <button id="createSticker" disabled>Create Sticker</button> <p id="status"></p> <canvas id="previewCanvas"></canvas> `; panel.appendChild(classicSection); // Sección Personalizada const customSection = document.createElement("div"); customSection.id = "customSection"; customSection.innerHTML = ` <!-- Toolbar con íconos --> <div id="customToolbar"> <button id="addCustomImage"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-photo-plus"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M12.5 21h-6.5a3 3 0 0 1 -3 -3v-12a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v6.5" /><path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l4 4" /><path d="M14 14l1 -1c.67 -.644 1.45 -.824 2.182 -.54" /><path d="M16 19h6" /><path d="M19 16v6" /></svg></button> <button id="openEmojiPanel"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-mood-smile"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /><path d="M9 10l.01 0" /><path d="M15 10l.01 0" /><path d="M9.5 15a3.5 3.5 0 0 0 5 0" /></svg></button> <input type="file" id="customFileInput" accept="image/*" /> <button id="toggleDrawing"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-pencil"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 20h4l10.5 -10.5a2.828 2.828 0 1 0 -4 -4l-10.5 10.5v4" /><path d="M13.5 6.5l4 4" /></svg></button> <button id="toggleShapes"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-square"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /></svg></button> <button id="bringForward"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-layers-selected"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 10.5l6.492 -6.492" /><path d="M13.496 16l6.504 -6.504z" /><path d="M8.586 15.414l10.827 -10.827" /><path d="M8 6a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z" /><path d="M16 16v2a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-8a2 2 0 0 1 2 -2h2" /></svg></button> <button id="sendBackward"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-layers-selected-bottom"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 14.5l4 -4" /><path d="M9.496 20l4.004 -4z" /><path d="M4.586 19.414l3.914 -3.914" /><path d="M8 6a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2z" /><path d="M16 16v2a2 2 0 0 1 -2 2h-8a2 2 0 0 1 -2 -2v-8a2 2 0 0 1 2 -2h2" /></svg></button> <button id="deleteElement"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-trash"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M4 7l16 0" /><path d="M10 11l0 6" /><path d="M14 11l0 6" /><path d="M5 7l1 12a2 2 0 0 0 2 2h8a2 2 0 0 0 2 -2l1 -12" /><path d="M9 7v-3a1 1 0 0 1 1 -1h4a1 1 0 0 1 1 1v3" /></svg></button> <button id="clearCanvas"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-restore"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3.06 13a9 9 0 1 0 .49 -4.087" /><path d="M3 4.001v5h5" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg></button> <button id="toggleMultiSelect"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-select-all"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M8 8m0 1a1 1 0 0 1 1 -1h6a1 1 0 0 1 1 1v6a1 1 0 0 1 -1 1h-6a1 1 0 0 1 -1 -1z" /><path d="M12 20v.01" /><path d="M16 20v.01" /><path d="M8 20v.01" /><path d="M4 20v.01" /><path d="M4 16v.01" /><path d="M4 12v.01" /><path d="M4 8v.01" /><path d="M4 4v.01" /><path d="M8 4v.01" /><path d="M12 4v.01" /><path d="M16 4v.01" /><path d="M20 4v.01" /><path d="M20 8v.01" /><path d="M20 12v.01" /><path d="M20 16v.01" /><path d="M20 20v.01" /></svg></btton> <button id="addText"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-letter-t"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6 4l12 0" /><path d="M12 4l0 16" /></svg></button> <button id="downloadImage"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-photo-down"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 8h.01" /><path d="M12.5 21h-6.5a3 3 0 0 1 -3 -3v-12a3 3 0 0 1 3 -3h12a3 3 0 0 1 3 3v6.5" /><path d="M3 16l5 -5c.928 -.893 2.072 -.893 3 0l4 4" /><path d="M14 14l1 -1c.653 -.629 1.413 -.815 2.13 -.559" /><path d="M19 16v6" /><path d="M22 19l-3 3l-3 -3" /></svg></button> <button id="undo"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-back-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 14l-4 -4l4 -4" /><path d="M5 10h11a4 4 0 1 1 0 8h-1" /></svg></button> <button id="redo"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-forward-up"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 14l4 -4l-4 -4" /><path d="M19 10h-11a4 4 0 1 0 0 8h1" /></svg></button> </div> <!-- Panel emergente para opciones del lápiz --> <div id="pencilOptionsPanel"> <div>Pincel - Colores:</div> <div id="pencilColorContainer"></div> <div>Pincel - Grosor:</div> <div id="pencilSizeContainer"></div> </div> <!-- Panel emergente para opciones de formas --> <div id="shapeOptionsPanel"> <button id="shapeSquare"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-square"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M3 3m0 2a2 2 0 0 1 2 -2h14a2 2 0 0 1 2 2v14a2 2 0 0 1 -2 2h-14a2 2 0 0 1 -2 -2z" /></svg></button> <button id="shapeCircle"><svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-circle"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0 -18 0" /></svg></button> </div> <div class="canvasContainer"> <canvas id="customCanvas" width="512" height="512"></canvas> </div> <button id="createCustomSticker">Create Custom Sticker</button> <p id="customStatus"></p> <!-- Panel de edición de texto --> <div id="textEditorPanel"> <div class="textEditorContent"> <label>Text: <input type="text" id="textContentInput"></label> <div> <label>Color: <span id="textColorButtons"></span></label> <label>Bg: <span id="textBgButtons"></span></label> </div> <div> <label>Font: <select id="textFontSelect"> <option value="" disabled selected>Select font</option> <option value="Arial">Arial</option> <option value="Courier New">Courier New</option> <option value="Times New Roman">Times New Roman</option> <option value="Verdana">Verdana</option> <option value="Georgia">Georgia</option> </select> </label> <label>Size: <input type="range" id="textFontSizeInput" min="10" max="100" value="30"></label> </div> </dib> </div> `; panel.appendChild(customSection); document.body.appendChild(panel); // Botón flotante para abrir/cerrar el panel addFloatingButton(panel); // Configurar secciones setupClassicSection(); setupCustomSection(); // Cambio de pestañas btnClassic.addEventListener("click", () => { document.getElementById("classicSection").style.display = "block"; document.getElementById("customSection").style.display = "none"; }); btnCustom.addEventListener("click", () => { document.getElementById("classicSection").style.display = "none"; document.getElementById("customSection").style.display = "block"; }); } // Botón flotante function addFloatingButton(panel) { const btn = document.createElement("button"); btn.id = "openStickerPanel"; btn.textContent = "Sticker"; btn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-sticker-2"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M6 4h12a2 2 0 0 1 2 2v7h-5a2 2 0 0 0 -2 2v5h-7a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2z" /><path d="M20 13v.172a2 2 0 0 1 -.586 1.414l-4.828 4.828a2 2 0 0 1 -1.414 .586h-.172" /></svg>` document.body.appendChild(btn); btn.addEventListener("click", () => { panel.style.display = panel.style.display === "none" ? "block" : "none"; const emojiContainer = document.getElementById("emojiContainer"); if (emojiContainer.style.display === "flex") { emojiContainer.style.display = "none"; } else { emojiContainer.style.display = "none"; } }); } // ========================= // MODO CLÁSICO // ========================= function setupClassicSection() { const dropZone = document.getElementById("dropZone"); const fileInput = document.getElementById("fileInput"); const createStickerButton = document.getElementById("createSticker"); const statusText = document.getElementById("status"); const previewCanvas = document.getElementById("previewCanvas"); let selectedImageCanvas = null; let createClicked = false; dropZone.addEventListener("click", () => fileInput.click()); dropZone.addEventListener("dragover", (e) => { e.preventDefault(); dropZone.style.borderColor = "#000"; }); dropZone.addEventListener("dragleave", (e) => { e.preventDefault(); dropZone.style.borderColor = "#ccc"; }); dropZone.addEventListener("drop", (e) => { e.preventDefault(); dropZone.style.borderColor = "#ccc"; if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFile(e.dataTransfer.files[0]); } }); fileInput.addEventListener("change", () => { if (fileInput.files && fileInput.files[0]) { handleFile(fileInput.files[0]); } }); function handleFile(file) { const reader = new FileReader(); reader.onload = function (event) { const img = new Image(); img.onload = function () { previewCanvas.width = previewCanvas.parentElement.clientWidth; previewCanvas.height = previewCanvas.parentElement.clientHeight; const ctx = previewCanvas.getContext("2d"); ctx.clearRect(0, 0, previewCanvas.width, previewCanvas.height); const ratio = Math.min(previewCanvas.width / img.width, previewCanvas.height / img.height); const newWidth = img.width * ratio; const newHeight = img.height * ratio; const dx = (previewCanvas.width - newWidth) / 2; const dy = (previewCanvas.height - newHeight) / 2; ctx.drawImage(img, dx, dy, newWidth, newHeight); selectedImageCanvas = previewCanvas; createStickerButton.disabled = false; statusText.textContent = "Image uploaded successfully"; createClicked = false; }; img.src = event.target.result; }; reader.readAsDataURL(file); } createStickerButton.addEventListener("click", () => { if (!selectedImageCanvas || createClicked) return; createClicked = true; createStickerButton.disabled = true; statusText.textContent = "Generating sticker..."; selectedImageCanvas.toBlob(function (blob) { if (!blob) { statusText.textContent = "Error al convertir la imagen."; return; } const stickerFile = new File([blob], "sticker.webp", { type: "image/webp" }); simulateWhatsAppFileUpload(stickerFile, statusText); }, "image/webp"); }); } // ========================= // MODO PERSONALIZADO (STICKER MAKER) // ========================= function setupCustomSection() { const customCanvas = document.getElementById("customCanvas"); const ctx = customCanvas.getContext("2d"); customCanvas.width = customCanvas.offsetWidth; customCanvas.height = customCanvas.offsetHeight; const addCustomImageButton = document.getElementById("addCustomImage"); const customFileInput = document.getElementById("customFileInput"); const createCustomStickerButton = document.getElementById("createCustomSticker"); const customStatus = document.getElementById("customStatus"); const toggleDrawingButton = document.getElementById("toggleDrawing"); const toggleShapesButton = document.getElementById("toggleShapes"); const bringForwardButton = document.getElementById("bringForward"); const sendBackwardButton = document.getElementById("sendBackward"); const deleteElementButton = document.getElementById("deleteElement"); const clearCanvasButton = document.getElementById("clearCanvas"); const toggleMultiSelectButton = document.getElementById("toggleMultiSelect"); const addTextButton = document.getElementById("addText"); const fontSelect = document.getElementById("fontSelect"); const downloadButton = document.getElementById("downloadImage"); const undoButton = document.getElementById("undo"); const redoButton = document.getElementById("redo"); // Paneles emergentes const pencilOptionsPanel = document.getElementById("pencilOptionsPanel"); const pencilColorContainer = document.getElementById("pencilColorContainer"); const pencilSizeContainer = document.getElementById("pencilSizeContainer"); const shapeOptionsPanel = document.getElementById("shapeOptionsPanel"); const shapeSquareButton = document.getElementById("shapeSquare"); const shapeCircleButton = document.getElementById("shapeCircle"); // Panel de edición de texto const textEditorPanel = document.getElementById("textEditorPanel"); const textContentInput = document.getElementById("textContentInput"); const textColorButtons = document.getElementById("textColorButtons"); const textBgButtons = document.getElementById("textBgButtons"); const textFontSelect = document.getElementById("textFontSelect"); const textFontSizeInput = document.getElementById("textFontSizeInput"); // Variables internas para el lápiz let drawingColor = "#000000"; const brushSizeInput = { value: 2 }; // Variables para elementos en el canvas let customElements = []; let selectedElement = null; let isDrawingMode = false; let drawingInProgress = false; let currentDrawing = null; let customCreateClicked = false; let offsetX = 0, offsetY = 0; let isDragging = false; let isResizing = false, resizeStartX = 0, resizeStartY = 0; let originalFontSize = 0; let originalWidth = 0, originalHeight = 0; // Variables para multi-select let isMultiSelectMode = false; let multiSelectedElements = []; let multiSelectRect = null; let isGroupDragging = false; let groupDragStart = null; // Variables para undo/redo let history = []; let historyIndex = -1; function resizeCanvas() { const container = customCanvas.parentElement; customCanvas.width = container.clientWidth; customCanvas.height = container.clientHeight; drawCustomCanvas(); } resizeCanvas(); window.addEventListener('resize', resizeCanvas); function cloneCustomElements(elements) { return elements.map(el => { let newEl = Object.assign({}, el); if (el.points) newEl.points = el.points.map(p => ({ x: p.x, y: p.y })); if (el.type === "image" && el.img && el.img.src) { const newImg = new Image(); newImg.src = el.img.src; newEl.img = newImg; } return newEl; }); } function saveHistory() { history = history.slice(0, historyIndex + 1); history.push(cloneCustomElements(customElements)); historyIndex++; } // Helper: tamaño por defecto para imágenes function getDefaultImageSize(img) { let width = img.width, height = img.height; if (width > 300) { const ratio = 300 / width; width = img.width * ratio; height = img.height * ratio; } return { width, height }; } // Función para detectar si un punto está en un elemento (considerando rotación) function isPointInElement(el, x, y) { if (el.rotation && el.rotation !== 0) { const cx = el.x + el.width / 2; const cy = el.y + el.height / 2; // Convertir (x,y) al sistema de coordenadas del elemento const dx = x - cx; const dy = y - cy; const angle = -el.rotation; const rx = dx * Math.cos(angle) - dy * Math.sin(angle); const ry = dx * Math.sin(angle) + dy * Math.cos(angle); return rx >= -el.width / 2 && rx <= el.width / 2 && ry >= -el.height / 2 && ry <= el.height / 2; } else { return x >= el.x && x <= el.x + el.width && y >= el.y && y <= el.y + el.height; } } // Variable para almacenar la posición actual del mouse let currentMousePos = { x: 0, y: 0 }; // Función para dibujar un elemento (aplica rotación si tiene) function drawElement(el) { if (el.rotation && el.rotation !== 0) { const cx = el.x + el.width / 2, cy = el.y + el.height / 2; ctx.save(); ctx.translate(cx, cy); ctx.rotate(el.rotation); if (el.type === "image") { ctx.drawImage(el.img, -el.width / 2, -el.height / 2, el.width, el.height); } else if (el.type === "emoji") { ctx.font = el.fontSize + "px sans-serif"; ctx.textBaseline = "top"; ctx.fillText(el.emoji, -el.width / 2, -el.height / 2); } else if (el.type === "drawing") { ctx.beginPath(); el.points.forEach((p, index) => { index === 0 ? ctx.moveTo(p.x - cx, p.y - cy) : ctx.lineTo(p.x - cx, p.y - cy); }); ctx.strokeStyle = el.color; ctx.lineWidth = el.brushSize; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.stroke(); } else if (el.type === "text") { ctx.font = el.fontSize + "px " + el.fontFamily; ctx.textBaseline = "top"; if (el.bgColor) { const metrics = ctx.measureText(el.text); const padding = 2; ctx.fillStyle = el.bgColor; ctx.fillRect(-el.width / 2 - padding, -el.height / 2 - padding, metrics.width + 2 * padding, el.fontSize + 2 * padding); } ctx.fillStyle = el.color; ctx.fillText(el.text, -el.width / 2, -el.height / 2); } else if (el.type === "shape") { ctx.strokeStyle = el.color; ctx.lineWidth = 2; if (el.shape === "square") { ctx.strokeRect(-el.width / 2, -el.height / 2, el.width, el.height); } else if (el.shape === "circle") { ctx.beginPath(); ctx.arc(0, 0, el.width / 2, 0, Math.PI * 2); ctx.stroke(); } } ctx.restore(); } else { if (el.type === "image") { ctx.drawImage(el.img, el.x, el.y, el.width, el.height); } else if (el.type === "emoji") { ctx.font = el.fontSize + "px sans-serif"; ctx.textBaseline = "top"; ctx.fillText(el.emoji, el.x, el.y); const metrics = ctx.measureText(el.emoji); el.width = metrics.width; el.height = el.fontSize; } else if (el.type === "drawing") { ctx.beginPath(); el.points.forEach((p, index) => { index === 0 ? ctx.moveTo(p.x, p.y) : ctx.lineTo(p.x, p.y); }); ctx.strokeStyle = el.color; ctx.lineWidth = el.brushSize; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.stroke(); } else if (el.type === "text") { ctx.font = el.fontSize + "px " + el.fontFamily; ctx.textBaseline = "top"; if (el.bgColor) { const metrics = ctx.measureText(el.text); const padding = 2; ctx.fillStyle = el.bgColor; ctx.fillRect(el.x - padding, el.y - padding, metrics.width + 2 * padding, el.fontSize + 2 * padding); } ctx.fillStyle = el.color; ctx.fillText(el.text, el.x, el.y); const metrics = ctx.measureText(el.text); el.width = metrics.width; el.height = el.fontSize; } else if (el.type === "shape") { ctx.strokeStyle = el.color; ctx.lineWidth = 2; if (el.shape === "square") ctx.strokeRect(el.x, el.y, el.width, el.height); else if (el.shape === "circle") { ctx.beginPath(); ctx.arc(el.x + el.width / 2, el.y + el.height / 2, el.width / 2, 0, Math.PI * 2); ctx.stroke(); } } } } // Función para dibujar el canvas completo, incluyendo handles y resaltado por hover/selección function drawCustomCanvas(forceRedraw = false) { const rect = customCanvas.getBoundingClientRect(); // Verificar si hay cambio de tamaño if (customCanvas.width !== rect.width || customCanvas.height !== rect.height || forceRedraw) { customCanvas.width = rect.width; customCanvas.height = rect.height; } ctx.clearRect(0, 0, customCanvas.width, customCanvas.height); customElements.forEach(el => { drawElement(el); }); // Si hay un elemento hover (y no está seleccionado) se dibuja su borde en verde if (hoveredElement && hoveredElement !== selectedElement) { ctx.save(); ctx.strokeStyle = "green"; ctx.lineWidth = 2; if (hoveredElement.rotation && hoveredElement.rotation !== 0) { const cx = hoveredElement.x + hoveredElement.width / 2; const cy = hoveredElement.y + hoveredElement.height / 2; ctx.translate(cx, cy); ctx.rotate(hoveredElement.rotation); ctx.strokeRect(-hoveredElement.width / 2, -hoveredElement.height / 2, hoveredElement.width, hoveredElement.height); } else { ctx.strokeRect(hoveredElement.x, hoveredElement.y, hoveredElement.width, hoveredElement.height); } ctx.restore(); } // Si hay un elemento seleccionado, dibujar borde verde, fondo semitransparente e indicadores if (selectedElement) { ctx.save(); if (selectedElement.rotation && selectedElement.rotation !== 0) { const cx = selectedElement.x + selectedElement.width / 2; const cy = selectedElement.y + selectedElement.height / 2; ctx.translate(cx, cy); ctx.rotate(selectedElement.rotation); // Fondo verde semitransparente y borde ctx.fillStyle = "rgba(0,255,0,0.2)"; ctx.fillRect(-selectedElement.width / 2, -selectedElement.height / 2, selectedElement.width, selectedElement.height); ctx.strokeStyle = "green"; ctx.lineWidth = 2; ctx.strokeRect(-selectedElement.width / 2, -selectedElement.height / 2, selectedElement.width, selectedElement.height); // Indicador de rotación (círculo verde con flecha) ctx.beginPath(); ctx.arc(0, -selectedElement.height / 2 - 20, 8, 0, Math.PI * 2); ctx.fillStyle = "green"; ctx.fill(); ctx.strokeStyle = "white"; ctx.lineWidth = 2; // Dibujar flecha circular ctx.beginPath(); ctx.arc(0, -selectedElement.height / 2 - 20, 12, -Math.PI / 2, Math.PI / 2, false); ctx.stroke(); // Punta de la flecha ctx.beginPath(); ctx.moveTo(4, -selectedElement.height / 2 - 20); ctx.lineTo(8, -selectedElement.height / 2 - 24); ctx.lineTo(12, -selectedElement.height / 2 - 20); ctx.stroke(); // Indicador de redimensión (cuadrado verde) ctx.fillStyle = "green"; ctx.fillRect(selectedElement.width / 2 - 8, selectedElement.height / 2 - 8, 16, 16); ctx.strokeStyle = "white"; ctx.strokeRect(selectedElement.width / 2 - 8, selectedElement.height / 2 - 8, 16, 16); } else { // Fondo verde semitransparente y borde ctx.fillStyle = "rgba(0,255,0,0.2)"; ctx.fillRect(selectedElement.x, selectedElement.y, selectedElement.width, selectedElement.height); ctx.strokeStyle = "green"; ctx.lineWidth = 2; ctx.strokeRect(selectedElement.x, selectedElement.y, selectedElement.width, selectedElement.height); // Indicador de rotación ctx.beginPath(); ctx.arc(selectedElement.x + selectedElement.width / 2, selectedElement.y - 20, 8, 0, Math.PI * 2); ctx.fillStyle = "green"; ctx.fill(); ctx.strokeStyle = "white"; ctx.lineWidth = 2; // Dibujar flecha circular ctx.beginPath(); ctx.arc(selectedElement.x + selectedElement.width / 2, selectedElement.y - 20, 12, -Math.PI / 2, Math.PI / 2, false); ctx.stroke(); // Punta de la flecha ctx.beginPath(); ctx.moveTo(selectedElement.x + selectedElement.width / 2 + 4, selectedElement.y - 20); ctx.lineTo(selectedElement.x + selectedElement.width / 2 + 8, selectedElement.y - 24); ctx.lineTo(selectedElement.x + selectedElement.width / 2 + 12, selectedElement.y - 20); ctx.stroke(); // Indicador de redimensión ctx.fillStyle = "green"; ctx.fillRect(selectedElement.x + selectedElement.width - 8, selectedElement.y + selectedElement.height - 8, 16, 16); ctx.strokeStyle = "white"; ctx.strokeRect(selectedElement.x + selectedElement.width - 8, selectedElement.y + selectedElement.height - 8, 16, 16); } ctx.restore(); } // Dibujar bordes azul dashed para multi-selección multiSelectedElements.forEach(el => { ctx.save(); ctx.strokeStyle = "blue"; ctx.lineWidth = 1; ctx.setLineDash([5, 5]); ctx.strokeRect(el.x, el.y, el.width, el.height); ctx.restore(); }); // Dibujar rectángulo de selección múltiple si está activo if (multiSelectRect) { ctx.save(); ctx.strokeStyle = "blue"; ctx.lineWidth = 1; ctx.setLineDash([5, 5]); const rx = Math.min(multiSelectRect.startX, multiSelectRect.currentX); const ry = Math.min(multiSelectRect.startY, multiSelectRect.currentY); const rw = Math.abs(multiSelectRect.currentX - multiSelectRect.startX); const rh = Math.abs(multiSelectRect.currentY - multiSelectRect.startY); ctx.strokeRect(rx, ry, rw, rh); ctx.restore(); } } // Actualizar variable hoveredElement según la posición del mouse customCanvas.addEventListener("mousemove", (e) => { const rect = customCanvas.getBoundingClientRect(); currentMousePos = { x: e.clientX - rect.left, y: e.clientY - rect.top }; // Si no se está arrastrando, redimensionando, rotando o dibujando, detectar hover if (!isDragging && !isResizing && !isRotating && !drawingInProgress) { hoveredElement = null; for (let i = customElements.length - 1; i >= 0; i--) { const el = customElements[i]; if (isPointInElement(el, currentMousePos.x, currentMousePos.y)) { hoveredElement = el; break; } } drawCustomCanvas(); } }); // ============================= // Eventos del canvas // ============================= customCanvas.addEventListener("mousedown", (e) => { const rect = customCanvas.getBoundingClientRect(); const x = e.clientX - rect.left, y = e.clientY - rect.top; // Si hay un elemento seleccionado, comprobar handle de rotación if (selectedElement) { const cx = selectedElement.x + selectedElement.width / 2; const cy = selectedElement.y + selectedElement.height / 2; const rot = selectedElement.rotation || 0; const handleOffset = { x: 0, y: -(selectedElement.height / 2 + 20) }; const rx = cx + handleOffset.x * Math.cos(rot) - handleOffset.y * Math.sin(rot); const ry = cy + handleOffset.x * Math.sin(rot) + handleOffset.y * Math.cos(rot); if (distance({ x, y }, { x: rx, y: ry }) < 15) { isRotating = true; initialRotateAngle = Math.atan2(y - cy, x - cx); initialElementRotation = selectedElement.rotation || 0; return; } // Comprobar handle de resize const vectorBR = { x: selectedElement.width / 2, y: selectedElement.height / 2 }; const brx = cx + vectorBR.x * Math.cos(rot) - vectorBR.y * Math.sin(rot); const bry = cy + vectorBR.x * Math.sin(rot) + vectorBR.y * Math.cos(rot); if (distance({ x, y }, { x: brx, y: bry }) < 15) { isResizing = true; resizeStartX = x; resizeStartY = y; if (selectedElement.type === "text" || selectedElement.type === "emoji") { originalWidth = selectedElement.width; originalFontSize = selectedElement.fontSize; } else { originalWidth = selectedElement.width; originalHeight = selectedElement.height; } return; } } if (isDrawingMode) { drawingInProgress = true; currentDrawing = { type: "drawing", points: [{ x, y }], color: drawingColor, brushSize: brushSizeInput.value, rotation: 0 }; selectedElement = null; } else if (isMultiSelectMode) { let inSelected = multiSelectedElements.some(el => x >= el.x && x <= el.x + el.width && y >= el.y && y <= el.y + el.height); if (inSelected && multiSelectedElements.length > 0) { isGroupDragging = true; groupDragStart = { startX: x, startY: y, positions: multiSelectedElements.map(el => ({ x: el.x, y: el.y })) }; } else { multiSelectRect = { startX: x, startY: y, currentX: x, currentY: y }; multiSelectedElements = []; } selectedElement = null; } else { let found = false; for (let i = customElements.length - 1; i >= 0; i--) { const el = customElements[i]; if (el.type === "drawing") { const xs = el.points.map(p => p.x), ys = el.points.map(p => p.y); const minX = Math.min(...xs), maxX = Math.max(...xs); const minY = Math.min(...ys), maxY = Math.max(...ys); if (x >= minX && x <= maxX && y >= minY && y <= maxY) { selectedElement = el; offsetX = x - minX; offsetY = y - minY; found = true; break; } } else { if (isPointInElement(el, x, y)) { selectedElement = el; if (x >= el.x + el.width - 15 && x <= el.x + el.width + 15 && y >= el.y + el.height - 15 && y <= el.y + el.height + 15) { isResizing = true; resizeStartX = x; resizeStartY = y; if (el.type === "text" || el.type === "emoji") { originalWidth = el.width; originalFontSize = el.fontSize; } else { originalWidth = el.width; originalHeight = el.height; } } else { isDragging = true; offsetX = x - el.x; offsetY = y - el.y; } found = true; break; } } } if (!found) { selectedElement = null; } drawCustomCanvas(); updateTextEditorPanel(); } }); customCanvas.addEventListener("mousemove", (e) => { const rect = customCanvas.getBoundingClientRect(); const x = e.clientX - rect.left, y = e.clientY - rect.top; currentMousePos = { x, y }; // Si se está en modo rotación if (isRotating && selectedElement) { const cx = selectedElement.x + selectedElement.width / 2; const cy = selectedElement.y + selectedElement.height / 2; const currentAngle = Math.atan2(y - cy, x - cx); selectedElement.rotation = initialElementRotation + (currentAngle - initialRotateAngle); drawCustomCanvas(); return; } if (isDrawingMode && drawingInProgress && currentDrawing) { currentDrawing.points.push({ x, y }); drawCustomCanvas(); ctx.strokeStyle = currentDrawing.color; ctx.lineWidth = currentDrawing.brushSize; ctx.lineJoin = "round"; ctx.lineCap = "round"; ctx.beginPath(); currentDrawing.points.forEach((point, index) => { index === 0 ? ctx.moveTo(point.x, point.y) : ctx.lineTo(point.x, point.y); }); ctx.stroke(); } else if (isMultiSelectMode) { if (isGroupDragging && groupDragStart) { const deltaX = x - groupDragStart.startX, deltaY = y - groupDragStart.startY; multiSelectedElements.forEach((el, idx) => { const initPos = groupDragStart.positions[idx]; el.x = initPos.x + deltaX; el.y = initPos.y + deltaY; }); drawCustomCanvas(); } else if (multiSelectRect) { multiSelectRect.currentX = x; multiSelectRect.currentY = y; drawCustomCanvas(); } else if (isDragging && selectedElement && !isDrawingMode && !isResizing && !isRotating) { selectedElement.x = x - offsetX; selectedElement.y = y - offsetY; drawCustomCanvas(); } } else { if (isResizing && selectedElement) { let newWidth = originalWidth + (x - resizeStartX); if (newWidth < 20) newWidth = 20; if (selectedElement.type === "text" || selectedElement.type === "emoji") { let scale = newWidth / originalWidth; selectedElement.fontSize = originalFontSize * scale; selectedElement.width = newWidth; selectedElement.height = originalFontSize * scale; } else { let newHeight = originalHeight + (y - resizeStartY); if (newHeight < 20) newHeight = 20; selectedElement.width = newWidth; selectedElement.height = newHeight; } drawCustomCanvas(); } else if (isDragging && selectedElement && !isDrawingMode && !isResizing && !isRotating) { selectedElement.x = x - offsetX; selectedElement.y = y - offsetY; drawCustomCanvas(); } } // Actualizar cursor sobre handles (para rotación y resize) if (!isDragging && !isResizing && !isRotating && selectedElement) { const cx = selectedElement.x + selectedElement.width / 2; const cy = selectedElement.y + selectedElement.height / 2; const rot = selectedElement.rotation || 0; const handleOffset = { x: 0, y: -(selectedElement.height / 2 + 20) }; const rx = cx + handleOffset.x * Math.cos(rot) - handleOffset.y * Math.sin(rot); const ry = cy + handleOffset.x * Math.sin(rot) + handleOffset.y * Math.cos(rot); const vectorBR = { x: selectedElement.width / 2, y: selectedElement.height / 2 }; const brx = cx + vectorBR.x * Math.cos(rot) - vectorBR.y * Math.sin(rot); const bry = cy + vectorBR.x * Math.sin(rot) + vectorBR.y * Math.cos(rot); if (distance({ x, y }, { x: rx, y: ry }) < 15) customCanvas.style.cursor = "grab"; else if (distance({ x, y }, { x: brx, y: bry }) < 15) customCanvas.style.cursor = "nwse-resize"; else customCanvas.style.cursor = "default"; } }); customCanvas.addEventListener("mouseup", () => { if (isDrawingMode && drawingInProgress && currentDrawing) { customElements.push(currentDrawing); saveHistory(); currentDrawing = null; drawingInProgress = false; } if (isMultiSelectMode) { if (isGroupDragging) { isGroupDragging = false; groupDragStart = null; saveHistory(); } else if (multiSelectRect) { const rx = Math.min(multiSelectRect.startX, multiSelectRect.currentX); const ry = Math.min(multiSelectRect.startY, multiSelectRect.currentY); const rw = Math.abs(multiSelectRect.currentX - multiSelectRect.startX); const rh = Math.abs(multiSelectRect.currentY - multiSelectRect.startY); multiSelectedElements = customElements.filter(el => (el.x >= rx && el.y >= ry && (el.x + el.width) <= (rx + rw) && (el.y + el.height) <= (ry + rh))); multiSelectRect = null; drawCustomCanvas(); } } else { if (isDragging || isResizing || isRotating) saveHistory(); } isResizing = false; isDragging = false; isRotating = false; drawCustomCanvas(); }); customCanvas.addEventListener("mouseleave", () => { if (isDrawingMode && drawingInProgress && currentDrawing) { customElements.push(currentDrawing); saveHistory(); currentDrawing = null; drawingInProgress = false; } isResizing = false; isDragging = false; isGroupDragging = false; multiSelectRect = null; isRotating = false; drawCustomCanvas(); }); // ============================= // Agregar imagen personalizada // ============================= addCustomImageButton.addEventListener("click", () => customFileInput.click()); customFileInput.addEventListener("change", () => { if (customFileInput.files && customFileInput.files[0]) { const file = customFileInput.files[0]; const reader = new FileReader(); reader.onload = function (event) { const img = new Image(); img.onload = function () { const size = getDefaultImageSize(img); if (file.type === "image/gif") { const element = { type: "image", isGif: true, originalBlob: file, img: new Image(), x: 50, y: 50, width: size.width, height: size.height, rotation: 0 }; element.img.src = URL.createObjectURL(file); customElements.push(element); } else { const element = { type: "image", img: img, x: 50, y: 50, width: size.width, height: size.height, rotation: 0 }; customElements.push(element); } saveHistory(); drawCustomCanvas(); }; img.src = event.target.result; }; reader.readAsDataURL(file); } }); // ============================= // Panel de emojis // ============================= let currentCategory = 'faces_emotion'; const emojiContainer = document.createElement("div"); emojiContainer.id = "emojiContainer"; const emojiCategoryContainer = document.createElement("div"); emojiCategoryContainer.id = "emojiCategoryContainer"; const btnEmotions = document.createElement("button"); btnEmotions.textContent = "Emociones"; const btnAnimals = document.createElement("button"); btnAnimals.textContent = "Animales"; btnEmotions.addEventListener("click", () => { currentCategory = 'faces_emotion'; loadEmojis(currentCategory); }); btnAnimals.addEventListener("click", () => { currentCategory = 'animals'; loadEmojis(currentCategory); }); emojiCategoryContainer.appendChild(btnEmotions); emojiCategoryContainer.appendChild(btnAnimals); const emojiContent = document.createElement("div"); emojiContent.id = "emojiContent"; emojiContainer.appendChild(emojiCategoryContainer); emojiContainer.appendChild(emojiContent); document.body.appendChild(emojiContainer); const emojiToggleButton = document.getElementById("openEmojiPanel"); emojiToggleButton.addEventListener("click", () => { if (emojiContainer.style.display === "none" || emojiContainer.style.display === "") { emojiContainer.style.display = "flex"; loadEmojis(currentCategory); } else { emojiContainer.style.display = "none"; } }); function loadEmojis(category) { emojiContent.innerHTML = ""; const data = emojis[category]; data.forEach(emojiData => { const btn = document.createElement("button"); btn.textContent = emojiData.emoji; btn.style.fontSize = "24px"; btn.style.padding = "5px"; btn.classList.add("addEmoji"); btn.style.border = "1px solid #ccc"; btn.style.borderRadius = "5px"; btn.style.cursor = "pointer"; btn.style.backgroundColor = "#f9f9f9"; btn.addEventListener("click", () => { const element = { type: "emoji", emoji: emojiData.emoji, x: 50, y: 50, fontSize: 70, width: 0, height: 0, rotation: 0 }; customElements.push(element); saveHistory(); drawCustomCanvas(); }); emojiContent.appendChild(btn); }); } loadEmojis(currentCategory); // ============================= // Configurar menú emergente del lápiz // ============================= pencilColorContainer.innerHTML = ""; const pencilColors = colorsText; pencilColors.forEach(color => { const btn = document.createElement("div"); btn.className = "colorButton"; btn.style.backgroundColor = color; btn.addEventListener("click", () => { drawingColor = color; Array.from(pencilColorContainer.children).forEach(b => b.style.borderColor = "#ccc"); btn.style.borderColor = "#000"; }); pencilColorContainer.appendChild(btn); }); pencilSizeContainer.innerHTML = ""; const pencilSizes = [2, 4, 6, 8]; pencilSizes.forEach(size => { const btn = document.createElement("div"); btn.className = "sizeButton"; btn.setAttribute("data-size", size); btn.style.backgroundColor = "#777"; btn.addEventListener("click", () => { brushSizeInput.value = size; if (selectedElement && selectedElement.type === "drawing") { selectedElement.brushSize = size; drawCustomCanvas(); } Array.from(pencilSizeContainer.children).forEach(b => b.style.borderColor = "#ccc"); btn.style.borderColor = "#000"; }); pencilSizeContainer.appendChild(btn); }); // Toggle lápiz: ahora alterna entre activar y desactivar el modo dibujo toggleDrawingButton.addEventListener("click", () => { if (isDrawingMode) { isDrawingMode = false; pencilOptionsPanel.style.display = "none"; toggleDrawingButton.style.backgroundColor = "#005c4b"; } else { isDrawingMode = true; pencilOptionsPanel.style.display = "block"; toggleDrawingButton.style.backgroundColor = "#ddd"; // Si se activa el lápiz, desactivar modo formas shapeOptionsPanel.style.display = "none"; selectedElement = null; } }); // ============================= // Configurar menú emergente para formas // ============================= toggleShapesButton.addEventListener("click", () => { pencilOptionsPanel.style.display = "none"; shapeOptionsPanel.style.display = (shapeOptionsPanel.style.display === "none" || shapeOptionsPanel.style.display === "") ? "block" : "none"; toggleShapesButton.style.backgroundColor = shapeOptionsPanel.style.display === "block" ? "#ddd" : "#005c4b"; }); shapeSquareButton.addEventListener("click", () => { const element = { type: "shape", shape: "square", x: 50, y: 50, width: 100, height: 100, color: "#000000", rotation: 0 }; customElements.push(element); saveHistory(); drawCustomCanvas(); }); shapeCircleButton.addEventListener("click", () => { const element = { type: "shape", shape: "circle", x: 50, y: 50, width: 100, height: 100, color: "#000000", rotation: 0 }; customElements.push(element); saveHistory(); drawCustomCanvas(); }); // ============================= // Configurar botón multi-select // ============================= toggleMultiSelectButton.addEventListener("click", () => { isMultiSelectMode = !isMultiSelectMode; toggleMultiSelectButton.style.backgroundColor = isMultiSelectMode ? "#ddd" : "#005c4b"; if (!isMultiSelectMode) { multiSelectedElements = []; drawCustomCanvas(); } }); // ============================= // Configurar edición de texto (botones de color) // ============================= const textColors = colorsText; function populateColorButtons(container, callback) { container.innerHTML = ""; textColors.forEach(color => { const btn = document.createElement("div"); btn.className = "colorButton"; btn.style.backgroundColor = color; btn.addEventListener("click", () => { callback(color); }); container.appendChild(btn); }); } populateColorButtons(textColorButtons, (color) => { if (selectedElement && selectedElement.type === "text") { selectedElement.color = color; drawCustomCanvas(); } }); populateColorButtons(textBgButtons, (color) => { if (selectedElement && selectedElement.type === "text") { selectedElement.bgColor = color; drawCustomCanvas(); } }); addTextButton.addEventListener("click", () => { const text = prompt("Enter the text:"); if (text) { const element = { type: "text", text: text, x: 50, y: 50, fontSize: 30, width: 0, height: 0, color: "#000000", bgColor: "", rotation: 0 }; customElements.push(element); saveHistory(); drawCustomCanvas(); } }); // ============================= // Resto de controles: bringForward, sendBackward, etc. // ============================= bringForwardButton.addEventListener("click", () => { if (selectedElement) { const idx = customElements.indexOf(selectedElement); if (idx !== -1 && idx < customElements.length - 1) { customElements.splice(idx, 1); customElements.push(selectedElement); saveHistory(); drawCustomCanvas(); } } }); sendBackwardButton.addEventListener("click", () => { if (selectedElement) { const idx = customElements.indexOf(selectedElement); if (idx > 0) { customElements.splice(idx, 1); customElements.unshift(selectedElement); saveHistory(); drawCustomCanvas(); } } }); deleteElementButton.addEventListener("click", () => { if (selectedElement) { const idx = customElements.indexOf(selectedElement); if (idx !== -1) { customElements.splice(idx, 1); selectedElement = null; saveHistory(); drawCustomCanvas(); } } }); clearCanvasButton.addEventListener("click", () => { customElements = []; selectedElement = null; saveHistory(); drawCustomCanvas(); }); downloadButton.addEventListener("click", () => { const dataURL = customCanvas.toDataURL("image/png"); const a = document.createElement("a"); a.href = dataURL; a.download = "sticker.png"; a.click(); }); undoButton.addEventListener("click", () => { if (historyIndex > 0) { historyIndex--; customElements = cloneCustomElements(history[historyIndex]); drawCustomCanvas(); } }); redoButton.addEventListener("click", () => { if (historyIndex < history.length - 1) { historyIndex++; customElements = cloneCustomElements(history[historyIndex]); drawCustomCanvas(); } }); // ============================= // Panel de edición de texto: actualización en tiempo real // ============================= function updateTextEditorPanel() { if (selectedElement && selectedElement.type === "text") { textEditorPanel.style.display = "block"; textContentInput.value = selectedElement.text; textFontSelect.value = selectedElement.fontFamily || ""; textFontSizeInput.value = selectedElement.fontSize; } else { textEditorPanel.style.display = "none"; } } textContentInput.addEventListener("input", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.text = textContentInput.value; drawCustomCanvas(); } }); textFontSelect.addEventListener("change", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.fontFamily = textFontSelect.value; drawCustomCanvas(); } }); textFontSizeInput.addEventListener("input", () => { if (selectedElement && selectedElement.type === "text") { selectedElement.fontSize = parseInt(textFontSizeInput.value); drawCustomCanvas(); } }); // ============================= // Animación: Si hay algún GIF, redibujar continuamente // ============================= function animateCanvas() { if (customElements.some(el => el.type === "image" && el.isGif)) { drawCustomCanvas(); } requestAnimationFrame(animateCanvas); } animateCanvas(); // ============================= // Crear sticker personalizado // ============================= createCustomStickerButton.addEventListener("click", () => { if (customCreateClicked) return; customCreateClicked = true; createCustomStickerButton.disabled = true; customStatus.textContent = "Generating custom sticker..."; selectedElement = null; hoveredElement = null; multiSelectedElements = []; drawCustomCanvas(); const gifElements = customElements.filter(el => el.type === "image" && el.isGif); if (gifElements.length === 1 && customElements.length === 1) { simulateWhatsAppFileUpload(gifElements[0].originalBlob, customStatus); customElements = []; selectedElement = null; drawCustomCanvas(); createCustomStickerButton.disabled = false; customCreateClicked = false; } else { customCanvas.toBlob(function (blob) { if (!blob) { customStatus.textContent = "Error generating sticker."; return; } const stickerFile = new File([blob], "sticker_personalizado.webp", { type: "image/webp" }); simulateWhatsAppFileUpload(stickerFile, customStatus); customElements = []; selectedElement = null; drawCustomCanvas(); createCustomStickerButton.disabled = false; customCreateClicked = false; }, "image/webp"); } }); } // ============================= // Simulación de subida a WhatsApp // ============================= function simulateWhatsAppFileUpload(file, statusElement) { openAttachmentMenu(); setTimeout(() => { const waFileInput = document.querySelector("input[type='file'][accept*='image']"); if (!waFileInput) { statusElement.textContent = "WhatsApp file input not found"; return; } const dt = new DataTransfer(); dt.items.add(file); waFileInput.files = dt.files; const event = new Event("change", { bubbles: true }); waFileInput.dispatchEvent(event); statusElement.textContent = "Sticker loaded. Sending..."; setTimeout(() => { const sendButton = document.querySelector("span[data-icon='send']"); if (sendButton) { sendButton.click(); statusElement.textContent = "Sticker send"; } else { statusElement.textContent = "Submit button not found"; } }, 1000); }, 500); } // ============================= // Abrir menú de adjuntos de WhatsApp // ============================= function openAttachmentMenu() { const attachmentButton = document.querySelector("div[title='Adjuntar']") || document.querySelector("span[data-icon='clip']"); if (attachmentButton) { attachmentButton.click(); } } // Función auxiliar: distancia entre dos puntos function distance(p1, p2) { return Math.sqrt((p1.x - p2.x) ** 2 + (p1.y - p2.y) ** 2); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址