积累成相(Flocked)

积累成相Flocked

更多有趣示例 尽在小红砖社区

示例

在这里插入图片描述

HTML

<div class="controls">
	<div class="line selection"><label class="customWrapper">custom<input type="file" id="custom"></label></div>
	<label class="line">
		<span>Interval:</span><input type="range" id="interval" min="20" max="1000" value="300" step="10">
	</label>
	<label class="line">
		<span>Speed:</span><input type="range" id="speed" min="20" max="500" value="100" step="5">
	</label>
	<label class="line">
		<span>Speed factor:</span><input type="range" id="sf" min="1" max="50" value="15">
	</label>
</div>
<canvas id="c" width="400" height="400"></canvas>

CSS

html, body { height: 100%; }
body {
	overflow: hidden;
	background: #333;
	display: flex;
	align-items: center;
	justify-content: center;
	font-family: Calibri,Candara,Segoe,"Segoe UI",Optima,Arial,sans-serif;
	font-size: 14px;
}

.controls {
	position: fixed;
	top: 4px;
	left: 8px;
	color: #fff;
}

.line {
	display: block;
	padding: 6px 0;
	span {
		display: inline-block;
		width: 100px;
		vertical-align: middle;
	}
}

@mixin thumb {
	appearance: none;
	width: 8px;
	height: 8px;
	background: #333;
	border: 2px solid currentColor;
	border-radius: 50%;
}
@mixin track {
	width: 100%;
	height: 1px;
	background: #fff;
	border: none;
}
input[type="range"] {
	appearance: none;
	width: 150px;
	background: transparent;
	height: 12px;
	vertical-align: middle;
	color: #fff;
	&::-webkit-slider-thumb { @include thumb; transform: translate(-50%, -50%); }
	&::-moz-range-thumb { @include thumb; }
	&::-ms-thumb { @include thumb; transform: none;  }
	&::-webkit-slider-runnable-track { @include track; }
	&::-moz-range-track { @include track; }
	&::-ms-track { @include track; }
	&:focus {
		outline: none;
		color: #9df;
	}
}

.selection { margin-left: -2px; }
.selection button, .selection .customWrapper {
	background: none;
	// border: 1px solid #fff;
	border: none;
	background: rgba(255, 255, 255, .15);
	color: currentColor;
	font: inherit;
	border-radius: 999px;
	margin: 0 2px;
	padding: 4px 12px;
	cursor: pointer;
	&:focus {
		background: rgba(255, 255, 255, .3);
		outline: none;
	}
}
input[type="file"] { display: none; }

#c {
	box-shadow: 0 2px 10px -2px rgba(0, 0, 0, .5);
}

JS

console.clear();

let sources = [
	{ name: 'pearl', url: 'https://upload.wikimedia.org/wikipedia/commons/thumb/d/d7/Meisje_met_de_parel.jpg/300px-Meisje_met_de_parel.jpg' },
	{ name: 'phil', url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/15664/phil_dark_bg.jpg' },
	{ name: 'joy+roy', url: 'https://s3-us-west-2.amazonaws.com/s.cdpn.io/15664/joyroy_dark.jpg' },
];

let c = document.getElementById('c');
let ctx = c.getContext('2d');
let size = { x: c.width, y: c.height };
let definition = 100;
let partSize = size.y / definition;
let picData = [];
let points = [];

async function load(data) {
	if (data instanceof File) {
		return new Promise((res, rej) => {
			let reader = new FileReader();
			reader.onload = () => res(load(reader.result));
			reader.onerror = rej;
			reader.readAsDataURL(data);
		});
	}
	return new Promise((res, rej) => {
		let img = new Image();
		img.onload = () => res(img);
		img.onerror = rej;
		img.crossOrigin = 'anonymous';
		img.src = data;
	});
}
function getLightData(image, size) {
	if (!size) {
		size = { x: img.width, y: img.height };
	}
	let c = document.createElement('canvas');
	let ctx = c.getContext('2d');
	c.width = size.x;
	c.height = size.y;
	ctx.fillRect(0, 0, c.width, c.height);
	let w = size.x;
	let h = size.y;
	let imgRatio = image.width / image.height;
	let finalRatio = w / h;
	if (imgRatio > finalRatio) {
		w = h * imgRatio;
	} else {
		h = w / imgRatio;
	}
	ctx.drawImage(image, (size.x - w) * .5, (size.y - h) * .5, w, h);
	let data = ctx.getImageData(0, 0, size.x, size.y).data;
	let ret = [];
	for (let i = 0, n = data.length; i < n; i += 4) {
		ret.push((.2126 * data[i] + .7152 * data[i + 1] + .0722 * data[i + 2]) / 255);
	}
	return ret;
}
let currentImage = null;
async function prepare(source) {
	if (source === currentImage) {
		return;
	}
	currentImage = source;
	picData = [];
	let image = await load(source);
	if (currentImage !== source) {
		return;
	}
	picData = getLightData(image, size);
}

function at(x, y) {
	return picData[~~x + ~~y * size.x] || 0;
}
function addLine() {
	points.push(...[...Array(definition)].map((e, i) => ({ x:0, y: i * partSize })));
}

let spawner = (() => {
	let last = 0;
	// interval in seconds
	let interval = .3;
	return {
		setInterval: v => interval = v || 1,
		tick: dt => {
			last += dt || 0;
			if (last >= interval) {
				addLine();
				last = last % interval;
			}
		}
	};
})();

let speed = 100;
let minSpeed = .15;

// Under 30fps, prefer lagging than time accuracy
let maxDT = 1 / 30;
let previous;
function loop(timestamp) {
	if (!previous) {
		previous = timestamp;
	}
	let dt = (timestamp - previous) * .001;
	if (dt > maxDT) {
		dt = maxDT;
	}
	previous = timestamp;
	spawner.tick(dt);
	ctx.fillStyle = '#34495e';
	ctx.fillRect(0, 0, size.x, size.y);
	ctx.fillStyle = '#fff';
	for (let i = points.length; i--;) {
		let point = points[i];
		let val = 1 - at(point.x, point.y);
		let factor = minSpeed + (1 - minSpeed) * (val * val);
		point.x += speed * dt * factor;
		if (point.x >= size.x || point.x < 0 || point.y >= size.y || point.y < 0) {
			points.splice(i, 1);
			continue;
		}
		ctx.fillRect(point.x, point.y, 1, partSize);
	}
	requestAnimationFrame(loop);
}

document.getElementById('interval').addEventListener('input', e => {
	spawner.setInterval(e.target.value / 1000);
});
document.getElementById('speed').addEventListener('input', e => {
	speed = +e.target.value;
});
document.getElementById('sf').addEventListener('input', e => {
	minSpeed = e.target.value / 100;
});

function checkSize() {
	let size = Math.min(window.innerWidth, window.innerHeight, c.width);
	c.style.width = size + 'px';
	c.style.height = size + 'px';
}
window.addEventListener('resize', checkSize);

c.addEventListener('click', () => {
	c.style.zIndex = c.style.zIndex ? '' : 5;
});

let $selection = document.querySelector('.selection');
sources.forEach(source => {
	let button = document.createElement('button');
	button.textContent = source.name;
	button.addEventListener('click', () => prepare(source.url));
	$selection.insertBefore(button, $selection.lastElementChild);
});
document.getElementById('custom').addEventListener('change', e => {
	let files = e.target.files;
	let file = files && files[0];
	if (!file) {
		return;
	}
	prepare(file);
});
document.body.addEventListener('dragover', e => e.preventDefault());
document.body.addEventListener('drop', e => {
	e.preventDefault();
	let dt = e.dataTransfer;
	if (dt.items) {
		let file = [...dt.items].find(e => e.kind === 'file');
		if (file) {
			prepare(file.getAsFile());
		}
	} else {
		let file = dt.files[0];
		if (file) {
			prepare(file);
		}
	}
});

checkSize();
prepare(sources[0].url).then(() => requestAnimationFrame(loop));

猜你喜欢

转载自blog.csdn.net/weixin_45544796/article/details/107484257