Coding soon

Coding soon

Multitouch

MultiTouch HTML
-
<div id="mtpzr"></div>
MultiTouch CSS
-
#mtpzr {
	position: relative;
	width: 100%;
	height: 66vh;
	touch-action: none;
	-webkit-text-size-adjust: 100%;
	-webkit-tap-highlight-color: transparent;
	overflow: hidden;
	font-family: 'Courier New', Courier, monospace;

}

#mtpzr > * {

	max-width: 33vmax;
	max-height: 33vmax;
	-webkit-user-drag: none;

}

img, 
video {
	
	object-fit: contain;
}

.txt {

	box-sizing: border-box;
	padding: .6em;
	background-color: #525252;
	color: #FFFFFF;

}
MultiTouch Main
+
const container = document.querySelector("#mtpzr");

const ww = container.offsetWidth;
const wh = container.offsetHeight;
const maxV = Math.max(ww, wh) * 0.33;
const rdm = () => Math.random();
const rdmC = siz => Math.floor(rdm() * siz);
const rdmX = () => rdmC(ww - maxV);
const rdmY = () => rdmC(wh - maxV);

const rdmSet = el => {

	const ds = el.dataset;

	ds.tx = rdmX();
	ds.ty = rdmY();
	ds.ts = 1;
	ds.tr = (Math.round(rdm()) * 2 - 1) * rdm() * 30;

};

for(let i = 0; i < 3; i++) {

	const pic = document.createElement("img");
	pic.classList.add("elem");
	pic.src = "media/multitouch/" + i + ".jpg";
	container.appendChild(pic);
	rdmSet(pic);

}

const txt = document.createElement("div");
txt.classList.add("elem", "txt");
txt.innerHTML = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.";
container.appendChild(txt);
rdmSet(txt);

const touchIt = new MTPZR(container);
MultiTouch Lib
+
/**
 * @force
 * @export
 * @class MTPZR : Multi Touch Pan Zoom Rotate
 */
class MTPZR {

	/**
	 * @construct
	 * @param {Element} el : wrapper
	 */
	constructor(el) {

		this.el = el;
		this.els = [...el.children];
		this.act = new Map();
		this.ptr = new Map();
		this.soon = new Map();

		this.min = 0.1;
		this.max = 5;
		this.top = this.els.length + 1;

		this.el.style.touchAction = "none";

		this.els.map((el, i) => {

			Object.assign(el.style,
				{
					"position": "absolute",
					"userSelect": "none",
					"webkitUserSelect": "none",
					// prevent hold save image
					"webkitTouchCallout": "none", 
					"zIndex": i + 1
				});

			const t = el.dataset;

			t.tx ||= t["tx"] || 0;
			t.ty ||= t["ty"] || 0;
			t.ts ||= t["ts"] || 1;
			t.tr ||= t["tr"] || 0;

			this.tf(el,
				t);

			// kill touch hold magnify
			this.adevt(el,
				"touchstart",
				this.noevt);
		
		});

		this.adevt(this.el,
			"pointerdown",
			this.down);

		const win = window;

		this.adevt(win,
			"pointermove",
			this.move);

		["pointerup", "pointercancel"]
		.map(ev =>
			this.adevt(win,
				ev,
				this.up));

		this.loop();
	
	}

	adevt(el, ev, cb) {

		el.addEventListener(ev,
			cb.bind(this));
	
	}

	noevt(evt) {

		evt.preventDefault();
		
		return false;
	
	}

	loop() {

		if(this.soon.size) {

			this.soon.forEach((t, el) => {

				Object.assign(el.dataset,
					t);

				this.tf(el,
					t);
			
			});

			this.soon.clear();
		
		}

		requestAnimationFrame(() => 
			this.loop());
	
	}

	tf(el, t) {

		el.style.transform = `translate(${t.tx}px,${t.ty}px) scale(${t.ts}) rotate(${t.tr}deg)`;
	
	}

	pid(e) {

		return e.pointerId;
	
	}

	down(e) {

		const el = this.els.find(el => 
			el.contains(e.target));

		if(!el || this.act.get(el)?.pts?.size >= 2) 
			return;

		const [pid, px, py] = [this.pid(e), e.clientX, e.clientY];

		el.style.zIndex = ++this.top;
		el.setPointerCapture(pid);

		const s = this.act.get(el) || {
			pts: new Map(),
			frm: this.get(el),
			cur: this.get(el)
		};

		s.frm = { ...s.cur };

		if(s.pts.size === 1) {

			const [onlyPoint] = s.pts.values();

			onlyPoint.sx = onlyPoint.px;
			onlyPoint.sy = onlyPoint.py;
		
		}

		if(s.pts.size === 1) {

			const [[_, p]] = s.pts.entries();

			[p.sx, p.sy] = [p.px, p.py];
		
		}

		s.pts.set(pid,
			{ px, py, sx: px, sy: py });

		this.act.set(el,
			s);

		this.ptr.set(pid,
			el);
	
	}

	move(e) {

		const pid = this.pid(e);
		const el = this.ptr.get(pid);

		if(!el) 
			return;

		const s = this.act.get(el);
		const p = s.pts.get(pid);

		if(!p) 
			return;

		p.px = e.clientX;
		p.py = e.clientY;
		
		this.upd(el,
			s);
	
	}

	up(e) {

		const pid = this.pid(e);
		const el = this.ptr.get(pid);

		if(!el) 
			return;

		const s = this.act.get(el);

		if(!s) 
			return;

		s.pts.delete(pid);
		this.ptr.delete(pid);

		if(s.pts.size) {

			s.frm = { ...s.cur };
			s.pts.forEach(p => 
				([p.sx, p.sy] = [p.px, p.py]));
		
		}
		else {

			this.act.delete(el);
		
		}
	
	}

	get(el) {

		const d = el.dataset;

		return {
			tx: +d.tx || 0,
			ty: +d.ty || 0,
			ts: +d.ts || 1,
			tr: +d.tr || 0
		};
	
	}

	upd(el, s) {

		const pts = [...s.pts.values()];
		const { cur: c, frm: f } = s;

		if(pts.length === 1) {

			const [p] = pts;

			c.tx = f.tx + (p.px - p.sx);
			c.ty = f.ty + (p.py - p.sy);
		
		}
		else {

			const [p1, p2] = pts;
			const dx = p2.px - p1.px;
			const dy = p2.py - p1.py;
			const sx = p2.sx - p1.sx;
			const sy = p2.sy - p1.sy;

			const cx = (p1.px + p2.px) / 2;
			const cy = (p1.py + p2.py) / 2;
			const ix = (p1.sx + p2.sx) / 2;
			const iy = (p1.sy + p2.sy) / 2;

			c.ts = Math.max(
				this.min,
				Math.min(
					this.max,
					Math.sqrt((dx * dx + dy * dy) / (sx * sx + sy * sy)) * f.ts
				)
			);

			c.tr
				= f.tr
				+ ((Math.atan2(dy,
					dx) - Math.atan2(sy,
					sx)) * 180) / Math.PI;
			c.tx = f.tx + (cx - ix);
			c.ty = f.ty + (cy - iy);
		
		}

		this.soon.set(el,
			c);
	
	}

}