Skip to content

Instantly share code, notes, and snippets.

@GMartigny
Last active September 27, 2018 14:37
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save GMartigny/793dbfd924704c7b1fa360066939769d to your computer and use it in GitHub Desktop.
Save GMartigny/793dbfd924704c7b1fa360066939769d to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name Upgrade dev.to offline drawing app
// @namespace http://tampermonkey.net/
// @version 1.2
// @description Slightly improuve the dev.to offline page.
// @author GMartigny
// @match https://dev.to/*
// @grant none
// ==/UserScript==
(function() {
// Loosely check if it's on the offline page
if (canvas && draw) {
function setStyle (node, styles) {
Object.keys(styles).forEach(key => node.style[key] = styles[key]);
}
function getCoordinates(event) {
// check to see if mobile or desktop
if (event.touches) {
// touch coordinates
return [event.touches[0].pageX - canvas.offsetLeft, event.touches[0].pageY - canvas.offsetTop];
}
else {
// click events
return [event.pageX - canvas.offsetLeft, event.pageY - canvas.offsetTop];
}
}
// Add some colors
const context = canvas.getContext("2d");
const addedColors = ["#ffffff", "#666"];
colors.push(...addedColors);
const colorDiv = document.querySelector(".colors");
addedColors.forEach(color => {
const button = document.createElement("button")
button.classList.add("color")
button.style.backgroundColor = color
colorDiv.appendChild(button)
button.addEventListener("click", () => {
context.strokeStyle = color;
});
});
// Change color pickers styles
const buttons = document.querySelectorAll(".color");
const scale = 1.2;
const scaleStr = `scale3d(${scale}, ${scale}, 1)`;
let selectedButton = 0;
function focus (index) {
buttons[selectedButton].style.transform = "";
buttons[index].style.transform = scaleStr;
selectedButton = index;
}
buttons.forEach((button, index) => {
setStyle(button, {
border: "2px solid #666",
cursor: "pointer",
transition: "transform ease-out .1s",
});
button.addEventListener("click", () => {
focus(index);
});
});
// Draw a circle on click
const TAU = Math.PI * 2;
canvas.addEventListener("click", (event) => {
context.fillStyle = context.strokeStyle;
context.beginPath();
const [x, y] = getCoordinates(event);
context.arc(x, y, brushSize / 2, 0, TAU);
context.fill();
});
// Using the mouse wheel change brush size
const sizeViewer = document.createElement("div");
document.body.appendChild(sizeViewer);
setStyle(sizeViewer, {
display: "none",
position: "absolute",
top: 0,
left: 0,
zIndex: "10",
backgroundColor: "#333",
borderRadius: "50%",
});
let brushSize = 5;
const minBrush = 2;
const maxBrush = 50;
let hideTimeout = null;
canvas.addEventListener("mousewheel", (event) => {
brushSize = Math.max(minBrush, Math.min(brushSize * (event.deltaY > 0 ? 1.2 : 1 / 1.2), maxBrush));
context.lineWidth = brushSize;
const [x, y] = getCoordinates(event);
setStyle(sizeViewer, {
display: "",
transform: `translate3d(${x + 5}px, ${y - 5 - brushSize}px, 0)`,
width: `${brushSize}px`,
height: `${brushSize}px`,
});
clearTimeout(hideTimeout);
hideTimeout = setTimeout(() => sizeViewer.style.display = "none", 500);
});
// Save and restore the canvas state
const saved = localStorage.getItem("save");
if (saved) {
document.body.style.cursor = "wait";
const img = new Image();
img.src = saved;
img.onload = () => {
context.drawImage(img, 0, 0);
document.body.style.cursor = "";
};
}
// Hitting the "Tab" key toggle the color picker
setStyle(colorDiv, {
transition: "transform ease-out .2s",
transformOrigin: "50% 100%",
});
window.addEventListener("keydown", (event) => {
if (event.key === "Tab") {
colorDiv.style.transform = colorDiv.style.transform ? "" : "rotate3d(1, 0, 0, -90deg)";
}
else if (event.key === "s" && event.ctrlKey) {
event.preventDefault();
const data = canvas.toDataURL();
localStorage.setItem("save", data);
// feedback ?
}
else if (event.key === "Escape" && confirm("You're sure you want to erase everything ?")) {
localStorage.removeItem("save");
context.clearRect(0, 0, canvas.width, canvas.height);
drawSVG(svg, img);
}
});
// Create an image from the logo svg
const svg = document.querySelector(".rainbow-logo");
const img = (function (svg) {
const data = (new XMLSerializer()).serializeToString(svg);
const svgBlob = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
const url = window.URL.createObjectURL(svgBlob);
const img = new Image();
img.src = url;
img.onload = () => {
svg.style.visibility = "hidden";
drawSVG(svg, img);
};
return img;
})(svg);
// Redraw the logo when canvas is erased
function setSizeFix () {
buttons[selectedButton].click();
context.lineWidth = brushSize;
if (img.width) {
drawSVG(svg, img);
}
}
window.addEventListener("resize", setSizeFix)
setSizeFix();
// Hide the text when drawing start
const content = document.querySelector(".content");
setStyle(content, {
transition: "opacity linear .2s",
zIndex: "-1",
});
canvas.addEventListener('mousedown', () => content.style.opacity = "0");
canvas.addEventListener('touchstart', () => content.style.opacity = "0");
// Add a screenshot button
const screenshotButton = document.createElement("a");
screenshotButton.textContent = "Download";
screenshotButton.download = "my_art";
setStyle(screenshotButton, {
position: "fixed",
bottom: "5px",
cursor: "pointer",
backgroundColor: "#666",
color: "#fff",
padding: ".5em 1em",
});
document.body.appendChild(screenshotButton);
screenshotButton.addEventListener("click", () => {
// Create another canvas to add the background
const offscreen = document.createElement("canvas");
offscreen.width = canvas.width;
offscreen.height = canvas.height;
const ctx = offscreen.getContext("2d");
ctx.fillStyle = "#fff";
ctx.fillRect(0, 0, offscreen.width, offscreen.height);
ctx.drawImage(canvas, 0, 0);
screenshotButton.href = offscreen.toDataURL();
});
function drawSVG (svg, img) {
context.save();
// Compute border-radius cliping
const bounds = svg.getBoundingClientRect();
context.beginPath();
const margin = bounds.width * 0.1;
context.moveTo(bounds.left + margin, bounds.top); // top-left
context.arcTo(bounds.right, bounds.top, bounds.right, bounds.top + margin, margin); // top-right
context.arcTo(bounds.right, bounds.bottom, bounds.right - margin, bounds.bottom, margin); // bottom-right
context.arcTo(bounds.left, bounds.bottom, bounds.left, bounds.bottom - margin, margin); // bottom-left
context.arcTo(bounds.left, bounds.top, bounds.left + margin, bounds.top, margin); // top-left
context.clip();
context.drawImage(img, bounds.x, bounds.y, bounds.width, bounds.height);
context.restore();
}
}
})();
@GMartigny
Copy link
Author

In order to run the script you can either:

  • Install the TamperMonkey extension in your browser then click the top-right Raw button
  • Or copy the whole code and run it in your browser-console (need to be done each time)

https://dev.to/offline.html

@GMartigny
Copy link
Author

Features v1.0:

  • Add white and black color
  • Focus effect on selected color
  • Add export button
  • Hide page texts when draw start

@GMartigny
Copy link
Author

Features v1.1:

  • Hitting the "Tab" key toggle the color picker
  • Mouse wheel to change line width

@GMartigny
Copy link
Author

Features v1.2:

  • Ctrl + S permanently save the drawing
  • Escape erase everything (and wipe the save)
  • Simple clicks draw a circle

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment