PageSourceSearch

https://emojicombos.com/emoji-art-editor

html emojicombos.com collected 2026-10-01 14:43:10 UTC 19,112 bytes, 162 lines download raw bytes

1<!DOCTYPE html>
2<html>
3  <head>
4    <meta charset="utf-8">
5    <meta name="viewport" content="width=device-width">
6    <link rel="canonical" href="https://emojicombos.com/emoji-art-editor">
7    <title>Make Emoji Art - Online Emoji Art Maker Tool</title>
8    
8<script src="https://cdn.jsdelivr.net/npm/[email protected]/index.min.js"></script>
8
9    
9<script>window.graphemeSplitter=new GraphemeSplitter</script>
9
10  </head>
11  <body>
12    <link href="https://fonts.googleapis.com/css?family=Roboto:400,700&amp;display=swap" rel="stylesheet">
13
14    <style>
15      body {
16        background: #eee;
17        font-family: 'Roboto', sans-serif;
18      }
19      body * {
20        box-sizing: border-box;
21      }
22      .step-module {
23        width: 95vw;
24        max-width: 900px;
25        margin: 0 auto;
26        margin-bottom: 1rem;
27        padding: 0.25rem;
28        border-radius: 3px;
29        box-shadow: 0px 1px 1px #b4b4b4;
30        background: white;
31      }
32      .step-module .step-header {
33        border-bottom: 1px solid lightgrey;
34        margin:0.5rem;
35        margin-top:0rem;
36      }
37      .step-module .step-body {
38        margin:0.5rem;
39      }
40      .step-body .step-body-section {
41        display:flex;
42        margin: 0.5rem 0;
43        align-items: center;
44      }
45      .b {
46        font-size: inherit;
47        line-height: inherit;
48      }
49      .tool-button[data-active='1'] {
50        font-weight: bold;
51      }
52
53      button {
54        cursor: pointer;
55      }
56
57      .tool-button {
58        margin: 0.25rem;
59        padding: 0.25rem;
60      }
61    </style>
62
63    <h1 style="text-align:center; font-size:1.5rem;">Make Emoji Art</h1>
64    <h2 style="overflow:hidden; height:0px; margin:0;">Edit and <b>draw emoji art</b> using this simple <b>emoji art editor</b>.</h2>
65
66    <p style="text-align:center; font-size:0.8rem; color:grey; max-width:400px; margin:0 auto;margin-bottom: 1rem;">Also try our <a style="" href="/dot-art-editor" target="_blank">dot art editor</a> and our <a style="" href="/dot-art-generator" target="_blank">image to dot art generator</a>.</p>
67
68    <!--<div class="step-module">
69      <h3 class="step-header">Choose grid size:</h3>
70      <div class="step-body">
71        <div class="step-body-section">
72          <input id="titleEl" style="width:100%; height:min-content;" placeholder="My Cool Thing" oninput="urlPreviewEl.innerHTML=`emojicombos.com/${normalizeKeyphrase(this.value).replace(/-/g, '--').replace(/ /g, '-')}`;">
73        </div>
74      </div>
75    </div>-->
76
77    <div style="text-align:center; margin-bottom:0.5rem;">
78      <button onclick="openEmojiPicker()" class="tool-button">Emoji: <span id="currentEmojiDisplayEl"></span></button>
79      <button onclick="undo()" class="tool-button">↶ undo</button>
80      <button onclick="redo()" class="tool-button">↷ redo</button>
81      <button onclick="clearButtonClickHandler()" class="tool-button">clear/new</button>
82      <button onclick="cropButtonClickHandler()" class="tool-button">crop to fit</button>
83      <button onclick="addPaddingClickHandler()" class="tool-button">add padding</button>
84      <button onclick="importButtonClickHandler()" class="tool-button">import</button>
85      <button onclick="zoomOut()" class="tool-button">🔎-</button> <!-- gridEl.style.lineHeight=(Number(gridEl.style.lineHeight.slice(0,-2))/1.5)+'px'; -->
86      <button onclick="zoomIn()" class="tool-button">🔎+</button>
87    </div>
88
89    <div id="currentPaletteListEl" style="text-align: center; margin-bottom: 0.5rem;"></div>
90
91    <div id="gridEl" style="border:1px solid grey; white-space:pre; font-size:15px; margin:0 auto; width:min-content; user-select:none; overscroll-behavior-y:none; cursor:default;"></div>
92    
93    <div style="text-align:center; margin-top:0.5rem; font-size:1.5rem;">
94      <button style="font-size:inherit;" onclick='copyEmojiArtClickHandler(gridEl.textContent),this.innerHTML="✅ copied!",this.disabled=!0,setTimeout(()=>{this.innerHTML="📋 copy",this.disabled=!1},1500)'>📋 copy</button>
95      <button style="font-size:inherit;" onclick="openSaveTextWindow(gridEl.textContent)">💾 save/submit</button>
96    </div>
97
98    <div id="emojiSearchModal" style="margin-top: 0.25rem; display: none; position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: rgb(0,0,0,0.5);">
99      <div id="emojiSearchModalInner" style="margin: auto; width: 90vw; max-width: 700px; background: white; padding: 0.25rem; border-radius: 2px;">
100        <div class="search-header"><input id="emojiSearchInputEl" style="flex-grow:1; font-size:1.3rem;" placeholder="Search for emojis..."></div>
101        <div id="emojiSearchResultsCtn"></div>
102      </div>
103    </div>
104    <style>
105      #emojiSearchModal .search-header {
106        margin: 0.25rem;
107        background: #e3e3e3;
108        width: auto;
109        padding: 0.25rem;
110        border-radius: 2px;
111        display: flex;
112        align-items: center;
113      }
114      #emojiSearchResultsCtn {
115        display: flex;
116        flex-wrap: wrap;
117        max-height: 30vh;
118        overflow-y: auto;
119        justify-content: space-between;
120      }
121      #emojiSearchResultsCtn > div {
122        padding: 0.4rem;
123        margin:0.25rem;
124        border: 1px solid #c4c4c4;
125        border-radius: 2px;
126        cursor:pointer;
127        font-size: 130%;
128        user-select: none;
129      }
130      #emojiSearchResultsCtn > div:hover {
131        background: #f6f6f6;
132      }
133    </style>
134    
134<script>let emojiSearchDebounceTimeout;function emojiSearchResultClickHandler(e){updateCurrentEmoji(this.textContent),updateEmojiPalette(),closeEmojiPicker()}emojiSearchInputEl.addEventListener("input",function(){emojiSearchResultsCtn.innerHTML='<svg style="margin:1rem auto;" width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><path d="M12,1A11,11,0,1,0,23,12,11,11,0,0,0,12,1Zm0,19a8,8,0,1,1,8-8A8,8,0,0,1,12,20Z" opacity=".25"/><path d="M12,4a8,8,0,0,1,7.89,6.7A1.53,1.53,0,0,0,21.38,12h0a1.5,1.5,0,0,0,1.48-1.75,11,11,0,0,0-21.72,0A1.5,1.5,0,0,0,2.62,12h0a1.53,1.53,0,0,0,1.49-1.3A8,8,0,0,1,12,4Z"><animateTransform attributeName="transform" type="rotate" dur="0.75s" values="0 12 12;360 12 12" repeatCount="indefinite"/></path></svg>',clearTimeout(emojiSearchDebounceTimeout),emojiSearchCache[emojiSearchInputEl.value]?updateEmojiSearchResults(emojiSearchInputEl.value):emojiSearchDebounceTimeout=setTimeout(()=>{updateEmojiSearchResults(emojiSearchInputEl.value)},400)});let emojiSearchCache={};async function updateEmojiSearchResults(e){let t;emojiSearchCache[e]?t=emojiSearchCache[e]:(t=await fetch(`https://emojicombos.com/api/keywordsToEmojis?keywords=${encodeURIComponent(e)}&key=${localStorage.loginKey||""}`).then(e=>e.json()).catch(e=>(console.error(e),[])),emojiSearchCache[e]=t);t=t.map(e=>e.trim()).filter(e=>/(\p{Emoji}\uFE0F|\p{Emoji_Presentation})/u.test(e)&&1===graphemeSplitter.splitGraphemes(e).length);let a=t.filter(e=>e.trim()).map(e=>`<div>${e.replace(/</g,"&lt;").replace(/>/g,"&gt;")}</div>`);emojiSearchResultsCtn.innerHTML=a.join("")+(a.length>0?'<div class="end-padding" style="flex-grow:1000000;pointer-events: none;opacity: 0;"></div>':""),emojiSearchResultsCtn.querySelectorAll("div:not(.end-padding)").forEach(e=>{e.addEventListener("click",emojiSearchResultClickHandler)})}emojiSearchModal.addEventListener("click",function(e){e.composedPath().includes(emojiSearchModalInner)||closeEmojiPicker()})</script>
134
135
136    <div id="tipsAndTricksEl" style="display:none; font-size:80%; max-width: 95vw;width: 600px;margin: 0 auto;margin-top: 1rem;background: white;border-radius: 3px;padding: 0.75rem;box-shadow: 0px 1px 1px #b4b4b4;">
137      <div style="font-size: 0.9rem; text-align: center;font-weight: bold;">Tips:</div>
138      <ul style="margin-top: 0.25rem;padding-left: 2rem;margin-bottom: 0;">
139        <li>Hold <b>Ctrl</b> and then click anywhere on the canvas to quickly switch your brush to the clicked emoji.</li>
140        <li style="margin-top: 0.25rem;">You might also like to check out our <a href="/dot-art-generator" target="_blank">image to dot art generator</a> and our <a href="/dot-art-editor" target="_blank">dot art editor</a>.</li>
141      </ul>
142      <div style="text-align:center; margin-top:0.5rem;">
143        <button onclick='localStorage.alreadySeenEmojiArtEditorTips="v1",tipsAndTricksEl.style.display="none"'>Close</button>
144      </div>
145    </div>
146    
146<script>"v1"!==localStorage.alreadySeenEmojiArtEditorTips&&(tipsAndTricksEl.style.display="")</script>
146
147
148    <p style="text-align:center; font-size:0.8rem; color:grey; max-width:500px; margin:0 auto; margin-top:1rem;">If you've found a bug or have feature ideas, you can <a style="color:inherit;" href="https://docs.google.com/forms/d/e/1FAIpQLSe4Sx-ol2SMbWO4isLP9xyweLXGte4-bvoUnyABk5dqO1CNSg/viewform?usp=sf_link" target="_blank">provide feedback here</a>.</p>
149    
150    
150<script>setInterval(()=>{!document.hidden&&window.userHasEditedArt&&(localStorage.emojiArtEditorText=gridEl.textContent)},1e4)</script>
150
151
152    
152<script>window.currentEmoji="⬛",currentEmojiDisplayEl.innerHTML=window.currentEmoji;let hashData={};try{hashData=JSON.parse(decodeURIComponent(location.hash.slice(1))||"{}")}catch(t){console.error(t)}function handleMouseOrTouchMove(t){let e;if(t.touches){if(1!==t.touches.length)return;e=t.touches[0]}else{if(1!==t.buttons)return;e=t}let r=e.clientX,i=e.clientY,o=document.elementFromPoint(r,i);o.classList.contains("b")&&(t.preventDefault(),e.ctrlKey?updateCurrentEmoji(o.textContent):(o.textContent=window.currentEmoji,window.userHasEditedArt=!0))}function generateDefaultEmojiArtGrid(t,e){text="";for(let r=0;r<e;r++)text+="⬜".repeat(t)+(r===e-1?"":"\n");return text}function openEmojiPicker(){emojiSearchModal.style.display="flex",emojiSearchInputEl.focus()}function closeEmojiPicker(){emojiSearchModal.style.display="none"}function updateEmojiPalette(){currentPaletteListEl.innerHTML='<div style="display:inline-flex; align-items:center; justify-content:center; height:fill-available; padding:0.25rem;">Palette:</div>';let t=[...new Set(graphemeSplitter.splitGraphemes(gridEl.textContent.replace(/\s+/g,"")))];t.includes(window.currentEmoji)||t.unshift(window.currentEmoji);for(let e of t)currentPaletteListEl.innerHTML+=`<div style="margin-left:0.25rem; display:inline-flex; align-items:center; justify-content:center; padding:0.25rem;"><button onclick="updateCurrentEmoji(this.dataset.emoji);" data-emoji="${e}" style="padding: 0.25rem;">${e}</button></div>`}function updateCurrentEmoji(t){window.currentEmoji=t,currentEmojiDisplayEl.innerHTML=t}document.addEventListener("DOMContentLoaded",async function(){gridEl.innerHTML=stringToHTMLGrid(generateDefaultEmojiArtGrid(3,3)),await new Promise(t=>setTimeout(t,3));let t,e=gridEl.querySelector(".b").offsetHeight;gridEl.style.lineHeight=e+"px",hashData.text?localStorage.emojiArtEditorText?confirm("You have an existing drawing in your browser cache. Loading this drawing will erase it. Click 'OK' to continue, or 'Cancel' to load the drawing in your cache.")?(t=hashData.text,localStorage.emojiArtEditorText=hashData.text):t=localStorage.emojiArtEditorText:t=hashData.text:localStorage.emojiArtEditorText&&(t=localStorage.emojiArtEditorText),t&&!validateEmojiArtGrid(t)&&(alert("Attempted to initialize imported or cached emoji art, but it was corrupted (e.g. non-emoji characters). Will now try to uncorrupt, but the result may look distorted."),t=uncorruptEmojiArt(t),validateEmojiArtGrid(t)||(t=void 0));let r=!1;t||(t=generateDefaultEmojiArtGrid(15,15),r=!0),location.hash="",gridEl.innerHTML=stringToHTMLGrid(t),r||updateEmojiPalette(),recordHistoryStateIfChanged()}),window.addEventListener("keydown",function(t){(t.ctrlKey||t.metaKey)&&"KeyZ"===t.code&&(t.shiftKey?redo():undo()),(t.ctrlKey||t.metaKey)&&"KeyY"===t.code&&redo()}),window.addEventListener("mouseup",recordHistoryStateIfChanged),window.addEventListener("touchend",recordHistoryStateIfChanged),window.addEventListener("touchstart",handleMouseOrTouchMove,{passive:!1}),window.addEventListener("touchmove",handleMouseOrTouchMove,{passive:!1}),window.addEventListener("mousemove",handleMouseOrTouchMove,{passive:!1}),window.addEventListener("mousedown",handleMouseOrTouchMove,{passive:!1}),window.addEventListener("keydown",function(t){"Control"==t.key&&(gridEl.style.cursor="crosshair")}),window.addEventListener("keyup",function(t){"Control"==t.key&&(gridEl.style.cursor="default")});let stateHistoryArray=[],stateHistoryIndex=0;function recordHistoryStateIfChanged(){gridEl.textContent!==stateHistoryArray[stateHistoryIndex]&&(0!==stateHistoryIndex&&(stateHistoryArray=stateHistoryArray.slice(stateHistoryIndex),stateHistoryIndex=0),stateHistoryArray.unshift(gridEl.textContent),stateHistoryArray.length>100&&stateHistoryArray.pop())}function undo(){0===stateHistoryIndex&&recordHistoryStateIfChanged(),stateHistoryIndex<stateHistoryArray.length-1&&(stateHistoryIndex++,gridEl.innerHTML=stringToHTMLGrid(stateHistoryArray[stateHistoryIndex]))}function redo(){stateHistoryIndex>0&&(stateHistoryIndex--,gridEl.innerHTML=stringToHTMLGrid(stateHistoryArray[stateHistoryIndex]))}function importButtonClickHandler(){let t=prompt("Paste the emoji art that you wish to import into the text box below. (Caution: The existing emoji art on the canvas will be overwritten)");t&&(t&&!validateEmojiArtGrid(t)&&(alert("Attempted to initialize imported emoji art, but it was corrupted (e.g. non-emoji characters). Will now try to uncorrupt, but the result may look distorted."),t=uncorruptEmojiArt(t),validateEmojiArtGrid(t)||(t=void 0)),t||(t=generateDefaultEmojiArtGrid(15,15)),gridEl.innerHTML=stringToHTMLGrid(t),recordHistoryStateIfChanged())}function addPaddingClickHandler(){let t=prompt("The currently-selected emoji will be used for padding. Indicate how much padding to add to the top,right,bottom,left (i.e. clockwise from top) like so:","3,3,3,3");
152if(null===t)return;let e=t.split(",").map(t=>Number(t.trim())).filter(t=>!isNaN(t)&&t>=0);if(4!==e.length)return alert("You entered invalid padding numbers. You should enter numbers in the following format: top,right,bottom,left");let[r,i,o,n]=e,a=window.currentEmoji,l=gridEl.textContent.trim().split("\n"),s=graphemeSplitter.splitGraphemes(l[0]).length;for(let t=0;t<o;t++)l.push(a.repeat(s));for(let t=0;t<r;t++)l.unshift(a.repeat(s));for(let t=0;t<l.length;t++)l[t]=a.repeat(n)+l[t];for(let t=0;t<l.length;t++)l[t]=l[t]+a.repeat(i);gridEl.innerHTML=stringToHTMLGrid(l.join("\n")),recordHistoryStateIfChanged()}function cropButtonClickHandler(){let t=gridEl.textContent.trim();if(""===t.replace(/[⠀\n]+/g,"").trim())return alert("Cannot crop an empty grid.");gridEl.innerHTML=stringToHTMLGrid(cropEmojiArt(t)),t.length===gridEl.textContent.length&&alert("Tried to crop whitespace from top and sides, but there was none."),recordHistoryStateIfChanged()}function clearButtonClickHandler(){let t=gridEl.textContent.split("\n"),e=prompt("How big should the new grid be? Use the format 'width,height'. Caution: This will delete your existing drawing.",Math.max(graphemeSplitter.splitGraphemes(t[0]).length,3)+","+Math.max(t.length,3));if(!e)return;let[r,i]=e.split(",").map(t=>Number(t));if(!r||!i)return alert("Invalid width or height.");gridEl.innerHTML=stringToHTMLGrid(generateDefaultEmojiArtGrid(r,i)),recordHistoryStateIfChanged()}function cropEmojiArt(t){let e=t.split("\n"),r=graphemeSplitter.splitGraphemes(e[0].trim()),i=graphemeSplitter.splitGraphemes(e[e.length-1].trim()),o=[r[0],r[r.length-1],i[0],i[i.length-1]].reduce((t,e)=>(t[e]=(t[e]||0)+1,t),{}),n=Object.entries(o).sort((t,e)=>e[1]-t[1])[0][0],a=1/0;for(let t of e){let e=graphemeSplitter.splitGraphemes(t),r=0;for(;e[r]===n;)r++;r<a&&(a=r)}let l=1/0;for(let t of e){let e=graphemeSplitter.splitGraphemes(t),r=0;for(;e[e.length-1-r]===n;)r++;r<l&&(l=r)}for(e=e.map(t=>graphemeSplitter.splitGraphemes(t)).map(t=>t.slice(a,l?-l:t.length).join(""));0===graphemeSplitter.splitGraphemes(e[0]).filter(t=>t!==n).length;)e=e.slice(1);for(;0===graphemeSplitter.splitGraphemes(e[e.length-1]).filter(t=>t!==n).length;)e=e.slice(0,-1);return e.join("\n")}function zoomIn(){gridEl.style.fontSize=1.5*Number(gridEl.style.fontSize.slice(0,-2))+"px",gridEl.style.lineHeight=1.5*Number(gridEl.style.lineHeight.slice(0,-2))+"px"}function zoomOut(){gridEl.style.fontSize=Number(gridEl.style.fontSize.slice(0,-2))/1.5+"px",gridEl.style.lineHeight=Number(gridEl.style.lineHeight.slice(0,-2))/1.5+"px"}function stringToHTMLGrid(t){return graphemeSplitter.splitGraphemes(t).map(t=>`<span class="b">${t}</span>`).join("")}function openSaveTextWindow(t){cropEmojiArt(t).length<.7*t.length&&"yes"===prompt("Your art contains a lot of padding - would you like to crop away the padding before saving? (yes/no)","yes")&&(t=cropEmojiArt(t));let e={text:t};window.open(`https://emojicombos.com/submit#${encodeURIComponent(JSON.stringify(e))}`,"_blank");try{gtag("event","save_emoji_art_from_editor_click",{})}catch(t){console.error(t)}}function validateEmojiArtGrid(t){let e=t.trim().split("\n");if(0===e.length)return!1;let r=graphemeSplitter.splitGraphemes(e[0]).length;return!(e.filter(t=>graphemeSplitter.splitGraphemes(t).length!==r).length>0)}function uncorruptEmojiArt(t){let e=(t=(t=t.replace(/ {3,5}/g,"⬜")).replace(/ +/g,"")).split("\n").map(t=>graphemeSplitter.splitGraphemes(t));e=e.map(t=>t.map(t=>t.match(/(\p{Emoji}\uFE0F|\p{Emoji_Presentation})/gu)?t:"⬜"));let r=e.map(t=>t.length),i=r.sort()[Math.round(r.length/2)];return e=e.map(t=>t.length>i?t.slice(0,i):t.length<i?t+"⠀".repeat(i-t.length):t),e.map(t=>t.join("")).join("\n").trim()}async function copyEmojiArtClickHandler(t){await copyTextToClipboard(t);try{gtag("event","copy_emoji_art_from_editor_click",{from_imported:void 0!==hashData.text})}catch(t){console.error(t)}}function fallbackCopyTextToClipboard(t){var e=document.createElement("textarea");e.value=t,e.style.top="0",e.style.left="0",e.style.position="fixed",document.body.appendChild(e),e.focus(),e.select();try{var r=document.execCommand("copy")?"successful":"unsuccessful";console.log("Copying text command was "+r)}catch(t){console.error("Fallback: Oops, unable to copy",t)}document.body.removeChild(e)}function copyTextToClipboard(t){navigator.clipboard?navigator.clipboard.writeText(t).then(function(){console.log("Async: Copying to clipboard was successful!")},function(t){console.error("Async: Could not copy text: ",t)}):fallbackCopyTextToClipboard(t)}function sleep(t){return new Promise(e=>setTimeout(e,t))}</script>
152
153
154
155    <!-- Global site tag (gtag.js) - Google Analytics -->
vendor: 68 bytes, lines 155-156
155
156    <script async src="https://www.googletagmanager.com/gtag/js?id=
156G-WF5V0J3NPY
vendor: 16 bytes, lines 156-157
156"></script>
157    
157<script>function gtag(){dataLayer.push(arguments)}window.dataLayer=window.dataLayer||[],gtag("js",new Date),gtag("config","G-WF5V0J3NPY")</script>
157
158
159  
159<script type="module" src="https://static.cloudflareinsights.com/beacon.min.js/v31edd6df95cf4e85bb4c19e7a9bdbcba1788362987495" integrity="sha512-iIg7k2xntmwu6/uSb5tpc/hySgZc4eoL31yB29W6tJFo2akwjPWcEqnCEdJvGexCL0KEQwVYv5BlowfhVz26hg==" data-cf-beacon='{"version":"2024.11.0","token":"b2a24a8737c948ef83556434bedb6c30","r":1,"spa":2}' crossorigin="anonymous"></script>
159
160</body>
161</html>
162

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.