来源:one-html-page-challengehttps://github.com/iveseenthedark/one-html-page-challenge
?只用一个HTML文件就实现了显示相机图像画面到浏览器,但看不懂
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="author" content="iveseenthedark" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#333333" />
<title>A S C I I C A M</title>
<style>
#ascii-cam pre,
body,
html {
width: 100vw;
height: 100vh;
margin: 0;
}
* {
box-sizing: border-box;
}
body {
overflow: hidden;
background-color: #000;
color: #eee;
font-family: monospace;
}
#logo,
#enable-cam-msg {
transform: translate(-50%, -50%);
position: absolute;
left: 50%;
top: 50%;
}
.fade {
transition: opacity 0.25s ease-in-out;
}
#logo {
line-height: 1.5;
}
#ascii-cam {
opacity: 0;
}
#ascii-cam pre {
position: absolute;
top: 0;
left: 0;
overflow: hidden;
line-height: 0.6;
user-select: none;
mix-blend-mode: screen;
font-size: 1.2em;
font-size: 2vh;
}
@media (max-height: 50em) {
#ascii-cam pre {
font-size: 1em;
}
}
@media (min-height: 60em) {
#ascii-cam pre {
font-size: 1.2em;
}
}
#ascii-cam #r-output {
color: red;
}
#ascii-cam #g-output {
color: #0f0;
}
#ascii-cam #b-output {
color: #00f;
}
#enable-cam-msg {
opacity: 0;
font-size: 3rem;
background-color: white;
color: black;
margin: 0;
max-width: 50vw;
white-space: pre-line;
text-align: center;
font-weight: 900;
}
#canvas,
#video {
display: none;
}
</style>
</head>
<body>
<pre id="logo" class="fade">
_
_n_|_|_,_
|===.-.===|
| ((_)) |
'==='-'==='
A S C I I C A M
</pre>
<pre id="enable-cam-msg" class="fade">Allow access to camera to use</pre>
<div id="ascii-cam" class="fade">
<div class="layers">
<pre id="r-output"></pre>
<pre id="g-output"></pre>
<pre id="b-output"></pre>
</div>
</div>
<video id="video" autoplay="true"></video>
<canvas id="canvas" width="640" height="480"></canvas>
<script type="application/javascript">
(() => {
'use strict';
let interval;
let r_layer, g_layer, b_layer;
const palette = [' ', '.', '-', ':', '+', '*', '=', '%', '@', '#'];
const vanity = 'B Y I V E S E E N T H E D A R K';
/**
* @param {*} stream
*/
const handle_video = stream => {
const canvas = document.getElementById('canvas');
const ctx = canvas.getContext('2d');
const video = document.getElementById('video');
// Older browsers may not have srcObject
if ('srcObject' in video) {
video.srcObject = stream;
} else {
// Avoid using this in new browsers, as it is going away.
video.src = window.URL.createObjectURL(stream);
}
video.addEventListener('loadedmetadata', () => {
window.addEventListener('resize', () => set_canvas_dimensions(video, canvas, ctx));
set_canvas_dimensions(video, canvas, ctx);
// Hide Logo
play_video(video, canvas, ctx);
setTimeout(() => {
document.getElementById('logo').style.opacity = 0;
document.getElementById('ascii-cam').style.opacity = 1;
}, 1000);
});
};
/**
*/
const handle_no_video = () => {
setTimeout(() => {
document.getElementById('logo').style.opacity = 0;
document.getElementById('ascii-cam').style.opacity = 1;
document.getElementById('enable-cam-msg').style.opacity = 1;
play_random();
}, 1000);
};
/**
* @param {*} e
*/
const handle_error = e => {
console.error('Error', e);
handle_no_video();
};
/**
* The greyscale value is calculated GREY = 0.299 * RED + 0.587 * GREEN + 0.114 * BLUE
* For more information see http://en.wikipedia.org/wiki/Grayscale
* @param {Array} image_data
*/
const fade_to_grey = pixels => pixels.map(pixel => pixel[0] * 0.299 + pixel[1] * 0.587 + pixel[2] * 0.114);
/**
*/
const play_video = (video, canvas, ctx) => {
clearInterval(interval);
// Buffer reverse
ctx.drawImage(video, 0, 0, canvas.width * -1, canvas.height);
// Grab view
const dx = canvas.dataset.hoffset || 0;
const dy = canvas.dataset.voffset || 0;
const image_width = canvas.width - dx * 2;
const image_data = ctx.getImageData(dx, dy, image_width, canvas.height - dy * 2).data;
// Chunk
const channels = 4;
const pixels = Array.from(Array(Math.ceil(image_data.length / channels)), (_, i) =>
image_data.slice(i * channels, i * channels + channels)
);
// Draw
let r_output = '';
let g_output = '';
let b_output = '';
pixels.forEach((pixel, i) => {
// Break for new line
if (i && i % image_width === 0) {
r_output += '\n';
g_output += '\n';
b_output += '\n';
}
const b_idx = Math.floor((pixel[2] / 255.0) * (palette.length - 1));
const r_idx = Math.floor((pixel[0] / 255.0) * (palette.length - 1));
const g_idx = Math.floor((pixel[1] / 255.0) * (palette.length - 1));
if (i && i >= image_width - vanity.length && i < image_width) {
// Input vanity message
r_output += vanity[vanity.length + i - image_width];
g_output += vanity[vanity.length + i - image_width];
b_output += palette[g_idx];
} else {
// Output camera data
r_output += palette[r_idx];
g_output += palette[g_idx];
b_output += palette[b_idx];
}
}, this);
r_layer.textContent = r_output;
g_layer.textContent = g_output;
b_layer.textContent = b_output;
interval = setInterval(() => play_video(video, canvas, ctx), 33);
};
/**
*/
const play_random = () => {
clearInterval(interval);
const char_dims = get_character_dimensions();
const screen_dims = get_screen_dimensions(char_dims);
const total_chars = Math.floor(screen_dims.char_width * screen_dims.char_height);
const mid_x = Math.floor(screen_dims.char_width / 2);
const mid_y = Math.floor(screen_dims.char_height / 2);
let radius_sq = mid_x * mid_x + mid_y + mid_y;
radius_sq = radius_sq < 2500 ? 2500 : radius_sq; // Stop the circle getting too small
let r_output = '',
g_output = '',
b_output = '';
for (let i = 0; i < total_chars; i++) {
// Break for new line
if (i && i % screen_dims.char_width === 0) {
r_output += '\n';
g_output += '\n';
b_output += '\n';
}
const char_pos_x = i % screen_dims.char_width;
const char_pos_y = i / screen_dims.char_width;
let dist = (char_pos_x - mid_x) * (char_pos_x - mid_x) + (char_pos_y - mid_y) * (char_pos_y - mid_y);
dist = dist > radius_sq ? radius_sq : dist; // Distance can be further than radius, clamp
const range = Math.floor(palette.length * (1 - dist / radius_sq));
const r_idx = Math.floor(Math.random() * range);
const g_idx = Math.floor(Math.random() * range);
const b_idx = Math.floor(Math.random() * range);
r_output += palette[r_idx];
g_output += palette[g_idx];
b_output += palette[b_idx];
}
r_layer.textContent = r_output;
g_layer.textContent = g_output;
b_layer.textContent = b_output;
interval = setInterval(play_random, 100);
};
/**
*/
const get_character_dimensions = () => {
const span = document.createElement('span');
span.textContent = 'X';
span.style.position = 'absolute';
span.style.left = '-100px';
document.querySelector('#ascii-cam pre').appendChild(span);
const dimensions = span.getBoundingClientRect();
span.remove();
return dimensions;
};
/**
*/
const get_screen_dimensions = char_dims => {
return {
width: window.innerWidth,
height: window.innerHeight,
char_width: Math.ceil(window.innerWidth / char_dims.width),
char_height: Math.ceil(window.innerHeight / char_dims.height)
};
};
/**
*/
const set_canvas_dimensions = (video, canvas, ctx) => {
console.log('setting canvas dimensions');
const char_dims = get_character_dimensions();
const screen_dims = get_screen_dimensions(char_dims);
let width = Math.floor(screen_dims.width / char_dims.width);
let height = Math.max(width * 0.75, screen_dims.char_height);
if (height > screen_dims.char_height) {
canvas.dataset.voffset = Math.floor((height - screen_dims.char_height) / 2);
canvas.dataset.hoffset = 0;
} else {
width = (height / 3) * 4;
canvas.dataset.hoffset = Math.floor((width - screen_dims.char_width) / 2);
canvas.dataset.voffset = 0;
}
video.width = canvas.width = width;
video.height = canvas.height = height;
// Reset scale if context exists
ctx && ctx.scale(-1, 1);
};
// Onload
window.addEventListener('load', () => {
if (navigator && navigator.mediaDevices) {
// Grab layers
r_layer = document.getElementById('r-output');
g_layer = document.getElementById('g-output');
b_layer = document.getElementById('b-output');
const constraints = { audio: false, video: {} };
navigator.mediaDevices
.getUserMedia(constraints)
.then(handle_video)
.catch(handle_error);
} else {
handle_no_video();
}
});
})();
</script>
</body>
</html>
|