您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Animation tools for Sketchful.io
当前为
// ==UserScript== // @name Animation // @description Animation tools for Sketchful.io // @namespace https://greasyfork.org/users/281093 // @match https://sketchful.io/ // @grant none // @version 0.7.3 // @author Bell // @license MIT // @copyright 2020, Bell (https://openuserjs.org/users/Bell) // @require https://cdnjs.cloudflare.com/ajax/libs/gifshot/0.3.2/gifshot.min.js // @require https://cdn.jsdelivr.net/npm/sortablejs@latest/Sortable.min.js // @require https://cdn.jsdelivr.net/npm/[email protected]/libgif.min.js // ==/UserScript== /* jshint esversion: 6 */ const containerStyle = `white-space: nowrap; overflow: auto; justify-content: center; margin-top: 10px; max-width: 70%; height: 124px; background: rgb(0 0 0 / 30%); padding: 12px; overflow-y: hidden; border-radius: 10px; margin-bottom: 5px; width: 100%; user-select: none;`; const canvasLayerStyle = `width: 100%; position: absolute; pointer-events: none; image-rendering: pixelated;`; const styleRules = [ '#layerContainer::-webkit-scrollbar { width: 5px; height: 5px; overflow: hidden}', '#layerContainer::-webkit-scrollbar-track { background: none }', '#layerContainer::-webkit-scrollbar-thumb { background: #F5BC09; border-radius: 5px }', `#layerContainer { ${containerStyle} }`, `.layer { ${canvasLayerStyle} }`, '#layerContainer img { width: 133px; cursor: pointer; margin-right: 5px }', '#buttonContainer { max-width: 260px; min-width: 260px; }', '#buttonContainer div { height: fit-content; margin-top: 10px; margin-left: 10px; }', '#buttonContainer { width: 15%; padding-top: 5px }', '#gifPreview { position: absolute; z-index: 1; width: 100%; image-rendering: pixelated; }', '.hidden { display: none }', '#activeLayer { margin-top: -1px; border: 3px solid red }', '#buttonContainer input { width: 50px; border: none; height: 30px; text-align: center; border-radius: 5px}', '#buttonContainer input::-webkit-input-placeholder { text-align: center; }' ]; const sheet = window.document.styleSheets[window.document.styleSheets.length - 1]; const outerContainer = document.createElement('div'); const onionContainer = document.createElement('div'); const canvas = document.querySelector('#canvas'); const ctx = canvas.getContext('2d'); const layerContainer = addLayerContainer(); const onionLayers = createOnionLayers(); (() => { canvas.parentElement.insertBefore(onionContainer, canvas); addButtons(); styleRules.forEach((rule) => sheet.insertRule(rule)); const gameModeObserver = new MutationObserver(checkRoomType); gameModeObserver.observe(document.querySelector('.game'), { attributes: true }); gameModeObserver.observe(canvas, { attributes: true }); layerContainer.addEventListener('dragenter', highlight, false); layerContainer.addEventListener('dragleave', unhighlight, false); layerContainer.addEventListener('drop', handleDrop, false); layerContainer.addEventListener('dragover', function(event) { event.preventDefault(); }, false); document.addEventListener('keydown', copyPaste); })(); let copied = null; function copyPaste(e) { if (!e.ctrlKey || document.activeElement.tagName === 'INPUT') return; const selectedLayer = document.querySelector('#activeLayer'); if (e.code === 'KeyC') { if (!selectedLayer) return; copied = selectedLayer.cloneNode(); e.stopImmediatePropagation(); } else if (e.code === 'KeyV' && copied) { const copy = copied.cloneNode(); if (selectedLayer) {insertAfter(copy, selectedLayer);} else {layerContainer.append(copy);} resetActiveLayer(); setActiveLayer({ target: copy }); copy.scrollIntoView(); } } function checkRoomType() { outerContainer.style.display = isFreeDraw() ? 'flex' : 'none'; onionContainer.style.display = isFreeDraw() ? '' : 'none'; } function addLayer() { const imgData = ctx.getImageData(0, 0, canvas.width, canvas.height); saveLayer(canvas); onionLayers.previous.putImageData(imgData, 0, 0); makeTransparent(onionLayers.previous, 30, 0); } function createOnionLayers() { return { previous: createLayer().getContext('2d'), next: createLayer().getContext('2d'), hide: () => { onionContainer.classList.add('hidden'); }, show: () => { onionContainer.classList.remove('hidden'); } }; } function saveGif() { const container = document.querySelector('#layerContainer'); if (!container.childElementCount) return; const layers = Array.from(container.children).map(image => image.src); const interval = getInterval(); gifshot.createGIF({ gifWidth: canvas.width, gifHeight: canvas.height, interval: interval / 1000, images: layers }, downloadGif); } function extractFrames(img) { const gifLoaderTemp = document.createElement('div'); gifLoaderTemp.style.display = 'none'; gifLoaderTemp.append(img); document.body.append(gifLoaderTemp); img.setAttribute ('rel:auto_play', 0); const gif = new SuperGif({ gif: img }); gif.load(()=> { const gifCanvas = gif.get_canvas(); if (gifCanvas.width !== canvas.width || gifCanvas.height !== canvas.height) { alert('Not a sketchful gif'); return; } const numFrames = gif.get_length(); for (let i = 0; i < numFrames; i++) { gif.move_to(i); saveLayer(gifCanvas); } }); } function handleDrop(e) { e.preventDefault(); layerContainer.style.filter = ''; const dt = e.dataTransfer; const files = dt.files; if (files.length && files !== null) { handleFiles(files); } } function handleFiles(files) { files = [...files]; files.forEach(previewFile); } function previewFile(file) { const reader = new FileReader(); reader.readAsDataURL(file); reader.onloadend = function() { const gif = document.createElement('img'); gif.src = reader.result; extractFrames(gif); }; } function highlight(e) { e.preventDefault(); layerContainer.style.filter = 'drop-shadow(0px 0px 6px green)'; } function unhighlight(e) { e.preventDefault(); layerContainer.style.filter = ''; } function saveLayer(canv) { const activeLayer = document.querySelector('#activeLayer'); const container = document.querySelector('#layerContainer'); const img = document.createElement('img'); img.src = canv.toDataURL(); if (activeLayer) { insertAfter(img, activeLayer); setActiveLayer({ target: img }); } else { container.append(img); } img.scrollIntoView(); } function insertAfter(newNode, referenceNode) { referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling); } function setActiveLayer(e) { const img = e.target; if (img.tagName !== 'IMG') { resetActiveLayer(); return; } resetActiveLayer(); img.id = 'activeLayer'; if (!e.shiftKey) { ctx.drawImage(img, 0, 0); canvas.save(); } const previousImg = img.previousSibling; const nextImg = img.nextSibling; if (previousImg) { onionLayers.previous.drawImage(previousImg, 0, 0); makeTransparent(onionLayers.previous, 30, 0); } else { onionLayers.previous.clearRect(0, 0, canvas.width, canvas.height); } if (nextImg) { onionLayers.next.drawImage(nextImg, 0, 0); makeTransparent(onionLayers.next, 0, 30); } else { onionLayers.next.clearRect(0, 0, canvas.width, canvas.height); } } function resetActiveLayer() { const layer = document.querySelector('#activeLayer'); if (!layer) return; layer.id = ''; layer.style.border = ''; } function createLayer() { const canvasLayer = document.createElement('canvas'); canvasLayer.classList.add('layer'); canvasLayer.width = canvas.width; canvasLayer.height = canvas.height; onionContainer.appendChild(canvasLayer); return canvasLayer; } function downloadGif(obj) { const name = 'sketchful-gif-' + Date.now(); const a = document.createElement('a'); a.download = name + '.gif'; a.href = obj.image; a.click(); } function addButton(text, clickFunction, element, type) { const button = document.createElement('div'); button.setAttribute('class', `btn btn-sm btn-${type}`); button.textContent = text; button.onpointerup = clickFunction; element.append(button); return button; } function clamp(num, min, max) { return num <= min ? min : num >= max ? max : num; } function getInterval() { const input = document.querySelector('#gifIntervalInput'); let fps = parseInt(input.value); if (isNaN(fps)) fps = 10; fps = clamp(fps, 1, 50); input.value = fps; return 1000 / fps; } function removeLayer() { const activeLayer = document.querySelector('#activeLayer'); if (!activeLayer) return; activeLayer.remove(); } function overwriteLayer() { const activeLayer = document.querySelector('#activeLayer'); if (!activeLayer) return; activeLayer.src = canvas.toDataURL(); } let ahead = false; function toggleAhead() { ahead = !ahead; onionLayers.next.canvas.style.display = ahead ? 'none' : ''; this.classList.toggle('btn-danger'); this.classList.toggle('btn-info'); } function addButtons() { const buttonContainer = document.createElement('div'); buttonContainer.id = 'buttonContainer'; outerContainer.append(buttonContainer); addButton('Play', playAnimation, buttonContainer, 'success'); const downloadBtn = addButton('Download', saveGif, buttonContainer, 'primary'); addButton('Save Layer', addLayer, buttonContainer, 'info'); addButton('Delete', removeLayer, buttonContainer, 'danger'); addButton('Overwrite', overwriteLayer, buttonContainer, 'warning'); addButton('Onion', toggleOnion, buttonContainer, 'success'); addButton('Ahead', toggleAhead, buttonContainer, 'info'); const textDiv = document.createElement('div'); const textInput = document.createElement('input'); textDiv.classList.add('btn'); textDiv.style.padding = '0px'; textInput.placeholder = 'FPS'; textInput.id = 'gifIntervalInput'; setInputFilter(textInput, (v) => {return /^\d*\.?\d*$/.test(v);}); textDiv.append(textInput); buttonContainer.insertBefore(textDiv, downloadBtn); } function addLayerContainer() { const game = document.querySelector('body > div.game'); const container = document.createElement('div'); outerContainer.style.display = 'flex'; outerContainer.style.flexDirection = 'row'; outerContainer.style.justifyContent = 'center'; container.addEventListener('wheel', (e) => { if (e.deltaY > 0) container.scrollLeft += 100; else container.scrollLeft -= 100; e.preventDefault(); }); container.addEventListener('pointerdown', (e) => { if (e.button !== 0) { resetActiveLayer(); return; } setActiveLayer(e); }, true); container.addEventListener('contextmenu', (e) => { e.preventDefault(); }, true); container.id = 'layerContainer'; new Sortable(container, { animation: 150 }); outerContainer.append(container); game.append(outerContainer); return container; } let onion = true; function toggleOnion() { onion = !onion; this.textContent = onion ? 'Onion' : 'Onioff'; if (onion) { onionLayers.show(); } else { onionLayers.hide(); } this.classList.toggle('btn-success'); this.classList.toggle('btn-danger'); } let animating = false; function playAnimation() { let preview = document.querySelector('#gifPreview'); if (animating) { this.classList.toggle('btn-success'); this.classList.toggle('btn-danger'); this.textContent = 'Play'; while (preview) { preview.remove(); preview = document.querySelector('#gifPreview'); } animating = false; return; } const canvasCover = document.querySelector('#canvasCover'); const img = document.createElement('img'); img.id = 'gifPreview'; img.draggable = false; canvasCover.parentElement.insertBefore(img, canvasCover); let frame = layerContainer.firstChild; if (!frame) return; const interval = getInterval(); this.classList.toggle('btn-success'); this.classList.toggle('btn-danger'); this.textContent = 'Stop'; animating = true; (function playFrame() { if (!animating) return; img.src = frame.src; frame = frame.nextSibling || layerContainer.firstChild; setTimeout(playFrame, interval); })(); } function isFreeDraw() { return ( document.querySelector('#canvas').style.display !== 'none' && document.querySelector('#gameClock').style.display === 'none' && document.querySelector('#gameSettings').style.display === 'none' ); } function setInputFilter(textbox, inputFilter) { ['input', 'keydown', 'keyup', 'mousedown', 'mouseup', 'select', 'contextmenu', 'drop'].forEach(function(event) { textbox.addEventListener(event, function() { if (inputFilter(this.value)) { this.oldValue = this.value; this.oldSelectionStart = this.selectionStart; this.oldSelectionEnd = this.selectionEnd; } else if (this.hasOwnProperty('oldValue')) { this.value = this.oldValue; this.setSelectionRange(this.oldSelectionStart, this.oldSelectionEnd); } else { this.value = ''; } }); }); } function makeTransparent(context, red, green) { const imgData = context.getImageData(0, 0, canvas.width, canvas.height); const data = imgData.data; for(let i = 0; i < data.length; i += 4) { const [r, g, b] = data.slice(i, i + 3); if (r >= 200 && g >= 200 && b >= 200) { data[i + 3] = 0; } else { data[i] += (data[i] + red) <= 255 ? red : 0; data[i + 1] += (data[i + 1] + green) <= 255 ? green : 0; data[i + 3] = 130; } } context.putImageData(imgData, 0, 0); } canvas.save = () => { canvas.dispatchEvent(new MouseEvent('pointerup', { bubbles: true, clientX: 0, clientY: 0, button: 0 })); };