Golfed join
const str = ['one','-','two'].join``;
console.log(str);
// outputs "one-two"
With tagged templates you can pass a template string to a function without parens.
const str = ['one','-','two'].join``;
console.log(str);
// outputs "one-two"
With tagged templates you can pass a template string to a function without parens.
Object.getOwnPropertyNames(Math).forEach(i => window[i] = Math[i]);
// or with map, just to be shorter
Object.getOwnPropertyNames(Math).map(i => window[i] = Math[i]);
// if this points to window
Object.getOwnPropertyNames(Math).map(i => this[i] = Math[i]);
// or using the deprecated "with" statement
with (Math) {
console.log(PI, E, SQRT2, cos(1));
}
While not very useful, I sometimes like to make the entire Math
object global on the window – just when speed coding and playing around.
const hasTouch =
navigator.maxTouchPoints != null && navigator.maxTouchPoints > 0;
const el = document.body.appendChild(document.createElement('div'));
el.innerHTML = `
<svg id="mainSvg" width="100%" height="100%" viewBox="0 0 800 800">
<g transform="translate(100, 100) scale(0.8, 0.8) rotate(25, 400, 400)">
<rect x="0" y="0" width="800" height="800" fill="#ccc" stroke="none"/>
<text x="10" y="30" font-size="30px">click/tap the box</text>
</g>
</svg>
<style>
svg, div, body, html {
height: 100%;
width: 100%;
margin: 0; padding: 0;
}
svg {
overflow: hidden;
}
</style>
`;
function createSvg(type) {
return document.createElementNS('http://www.w3.org/2000/svg', type);
}
// mouse or touch location is always relative to the svg
// any css transformations on the svg are only accounted for in
// Chrome unfortunately
function svgTouch(e, svgEl) {
const touches = e.touches;
let locX, locY;
if (touches != null && touches.length > 0) {
locX = touches[0].clientX;
locY = touches[0].clientY;
} else {
locX = e.clientX;
locY = e.clientY;
}
const pt = svgEl.createSVGPoint();
pt.x = locX;
pt.y = locY;
const newPnt = pt.matrixTransform(svgEl.getScreenCTM().inverse());
return { locX: newPnt.x, locY: newPnt.y };
}
document.addEventListener(hasTouch ? 'touchstart' : 'mousedown', e => {
// global id `mainSvg` :P
const { locX, locY } = svgTouch(e, mainSvg);
const circle = createSvg('circle');
circle.cx.baseVal.value = locX;
circle.cy.baseVal.value = locY;
circle.r.baseVal.value = 20;
circle.style.fill = 'blue';
mainSvg.appendChild(circle);
});
This snippet shows how to get relative mouse/touch coordinates within an SVG element. The main trick here is on line 50… pt.matrixTransform(svgEl.getScreenCTM().inverse());
– we get the inverse transformation matrix of the “mainSvg” node and transform the mouse/touch coordinates by it.
Beware, when this was posted, as noted in the comments – CSS transforms on the SVG element or any of its parents won’t be accounted for in the `getScreenCTM` call in any browsers other than Chrome. There is an open Firefox issue for this I believe…
// no scrolling on mobile
document.addEventListener('touchmove', e => e.preventDefault(), {
passive: false
});
const hasTouch =
navigator.maxTouchPoints != null && navigator.maxTouchPoints > 0;
// looking forward to `navigator?.maxTouchPoints > 0`
// being better supported
function touchify(e) {
const touch = [];
touch.x = touch.y = 0;
if (e.touches != null && e.touches.length > 0) {
touch.x = e.touches[0].clientX;
touch.y = e.touches[0].clientY;
for (let i = 0; i < e.touches.length; i++) {
touch[i] = e.touches[i];
}
} else {
touch.x = e.clientX;
touch.y = e.clientY;
touch[0] = { clientX: e.clientX, clientY: e.clientY };
}
return touch;
}
let moveOrTouchMove;
if (hasTouch) {
moveOrTouchMove = 'touchmove';
} else {
moveOrTouchMove = 'mousemove';
}
function dot(x, y, radius, color) {
const el = document.createElement('div');
const size = `${radius * 2}px`;
Object.assign(el.style, {
position: 'absolute',
left: `${x}px`,
top: `${y}px`,
width: size,
height: size,
transform: `translate(${-radius}px, ${-radius}px)`,
borderRadius: '50%',
background: color
});
el.classList.add('dot');
document.body.appendChild(el);
return el;
}
let dotX = 100,
dotY = 100,
touchX = 200,
touchY = 150,
damp = 12,
dotEl = dot(dotX, dotY, 20, 'red');
document.addEventListener(moveOrTouchMove, e => {
const { x, y } = touchify(e);
touchX = x;
touchY = y;
});
function loop() {
dotX += (touchX - dotX) / damp;
dotY += (touchY - dotY) / damp;
Object.assign(dotEl.style, {
left: `${dotX}px`,
top: `${dotY}px`
});
window.requestAnimationFrame(loop);
}
loop();
Move your mouse on desktop or your finger on mobile – watch the red dot follow…
This is a simpler version of some of the things used in yesterdays post – just a quick way to normalize touch events – just one of many ways to do this – for simple stuff this is my goto
.
If you want to try it out on its own page – take a look here (specifically good for trying it on mobile).
You’ll probably want a to use a meta tag like this – for the full effect.
<meta name="viewport" content="width=device-width, initial-scale=1.0">
// no scrolling on mobile
document.addEventListener("touchmove", e => e.preventDefault(), {
passive: false
});
// has more than one touch point
const hasTouch =
navigator.maxTouchPoints != null && navigator.maxTouchPoints > 0;
function touchify(e) {
const touch = [];
touch.x = touch.y = 0;
if (e.touches != null && e.touches.length > 0) {
touch.x = e.touches[0].clientX;
touch.y = e.touches[0].clientY;
for (let i = 0; i < e.touches.length; i++) {
touch[i] = e.touches[i];
}
} else {
touch.x = e.clientX;
touch.y = e.clientY;
touch[0] = { clientX: e.clientX, clientY: e.clientY };
}
return touch;
}
let clickOrTouchEnd,
downOrTouchStart,
enterOrTouchStart,
moveOrTouchMove,
upOrTouchEnd,
leaveOrTouchEnd;
if (hasTouch) {
clickOrTouchEnd = "touchend";
downOrTouchStart = "touchstart";
enterOrTouchStart = "touchstart";
moveOrTouchMove = "touchmove";
upOrTouchEnd = "touchend";
leaveOrTouchEnd = "touchend";
} else {
clickOrTouchEnd = "click";
downOrTouchStart = "mousedown";
enterOrTouchStart = "mouseenter";
moveOrTouchMove = "mousemove";
upOrTouchEnd = "mouseup";
leaveOrTouchEnd = "mouseleave";
}
function dot(x, y, radius, color) {
const el = document.createElement("div");
const size = `${radius * 2}px`;
Object.assign(el.style, {
position: "absolute",
left: `${x}px`,
top: `${y}px`,
width: size,
height: size,
transform: `translate(${-radius}px, ${-radius}px)`,
borderRadius: "50%",
background: color
});
el.classList.add("dot");
document.body.appendChild(el);
return el;
}
let down = false;
let lastTouch;
document.addEventListener(downOrTouchStart, e => {
const { x, y } = touchify(e);
dot(x, y, 30, `rgba(255, 0, 0, 0.2)`);
down = true;
});
document.addEventListener(moveOrTouchMove, e => {
if (down) {
const touches = touchify(e);
// draw more than one touch
for (let i = 0; i < touches.length; i++) {
const touch = touches[i];
dot(touch.clientX, touch.clientY, 10, `rgba(0, 0, 155, 0.2)`);
}
lastTouch = touches[0];
}
});
document.addEventListener(upOrTouchEnd, e => {
if (down) {
dot(lastTouch.clientX, lastTouch.clientY, 20, `rgba(0, 155, 0, 0.2)`);
down = false;
}
});
Draw with your mouse by clicking, holding down and dragging…
I often use some variation of this depending on the project… This snippet normalizes touch events in a simple way, prevents scrolling and gives an example of how to handle multi-touch.
You’ll probably want a to use a meta tag like this – for the full effect.
<meta name="viewport" content="width=device-width, initial-scale=1.0">