前言
今天在codepen上遇到一个有趣的东西,使用WebGL和GLSL的3D图形渲染示例。它创建了一个HTML5画布,并在其上绘制了一个由立方体组成的场景
网址
http://js.yunduanjianzhan.cn/
js代码
const canvas = document.createElement("canvas")
const gl = canvas.getContext("webgl2")
document.body.innerHTML = ""
document.body.appendChild(canvas)
document.body.style = "margin:0;touch-action:none;overflow:hidden"
canvas.style.width = "100%"
canvas.style.height = "auto"
canvas.style.userSelect = "none"
const dpr = Math.max(1, .5*window.devicePixelRatio)
function resize() {
const {
innerWidth: width,
innerHeight: height
} = window
canvas.width = width * dpr
canvas.height = height * dpr
gl.viewport(0, 0, width * dpr, height * dpr)
}
window.onresize = resize
const vertexSource = `#version 300 es
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
in vec4 position;
void main(void) {
gl_Position = position;
}
`
const fragmentSource = `#version 300 es
/*********
* made by Matthias Hurrle (@atzedent)
*/
#ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
#else
precision mediump float;
#endif
out vec4 fragColor;
uniform vec2 resolution;
uniform float time;
uniform vec2 touch;
uniform int pointerCount;
#define mouse (touch/resolution)
#define P pointerCount
#define T mod(time+40.,180.)
#define TICK (fract(T*.025))
mat2 rot(float a) {
float c=cos(a), s=sin(a);
return mat2(c,-s,s,c);
}
float rnd(vec2 p) {
return fract(sin(dot(p,p.yx+vec2(234.78,543.12)))*345678.);
}
float box(vec3 p, vec3 s, float r) {
p = abs(p)-s;
return length(max(p,.0))+
min(.0,max(max(p.x,p.y),p.z))-r;
}
float map(vec3 p) {
vec3 q = p;
const float n = 3.;
p.xz = (fract(p.xz/n)-.5)*n;
vec2 id = (floor(q.xz/n)-.5)*n;
float h = max(.2,rnd(id)*2.5),
w = max(.5,1.-rnd(id));
q = mod(q*10.,1.125);
float bld = box((p-vec3(0,h,0))+.05,vec3(w,h,w),.0125),
wnd = box(q,vec3(1),.005);
return max(bld, -(TICK<.5?max(wnd,-(bld+.05)):min(wnd,bld-.025)));
}
void cam(inout vec3 p) {
if (P>0) {
p.yz *= rot(-mouse.y*3.1415+1.5707);
p.xz *= rot(3.1415-mouse.x*6.2832);
} else {
if (TICK>.5) {
p.yz *= rot(-.45);
p.xz *= rot(2.7+sin(T*.05));
} else {
p.yz *= rot(-.25);
p.xz *= rot(3.1415+cos(T*.1)*.75);
}
}
}
void main(void) {
vec2 uv = (
gl_FragCoord.xy-.5*resolution
)/min(resolution.x,resolution.y);
vec3 col = vec3(0),
p = vec3(0),
rd = normalize(vec3(uv,1.8));
cam(p);
cam(rd);
p.z += T*3.;
const float steps = 60., maxd = 200.;
float diffuse = mix(.5,1.,rnd(uv*10.+vec2(T,1)));
for (float i=.0; i<steps; i++) {
float d = map(p)*.5*diffuse;
if (d < 1e-2) {
col += (steps-i)/steps;
col *= vec3(3,2,1);
break;
}
if (d > maxd) break;
p += rd*d;
}
fragColor = vec4(col, 1);
}
`
function compile(shader, source) {
gl.shaderSource(shader, source)
gl.compileShader(shader)
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error(gl.getShaderInfoLog(shader))
}
}
let program
function setup() {
const vs = gl.createShader(gl.VERTEX_SHADER)
const fs = gl.createShader(gl.FRAGMENT_SHADER)
compile(vs, vertexSource)
compile(fs, fragmentSource)
program = gl.createProgram()
gl.attachShader(program, vs)
gl.attachShader(program, fs)
gl.linkProgram(program)
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error(gl.getProgramInfoLog(program))
}
}
let vertices, buffer;
function init() {
vertices = [
-1,-1, 1,
-1,-1, 1,
-1, 1, 1,
-1, 1, 1
]
buffer = gl.createBuffer()
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(vertices), gl.STATIC_DRAW)
const position = gl.getAttribLocation(program, "position")
gl.enableVertexAttribArray(position)
gl.vertexAttribPointer(position, 2, gl.FLOAT, false, 0, 0)
program.resolution = gl.getUniformLocation(program, "resolution")
program.time = gl.getUniformLocation(program, "time")
program.touch = gl.getUniformLocation(program, "touch")
program.pointerCount = gl.getUniformLocation(program, "pointerCount")
}
const mouse = {
x: 0,
y: 0,
touches: new Set(),
update: function(x, y, pointerId) {
this.x = x * dpr
this.y = (innerHeight - y) * dpr
this.touches.add(pointerId)
},
remove: function(pointerId) { this.touches.delete(pointerId) }
}
function loop(now) {
gl.clearColor(0, 0, 0, 1)
gl.clear(gl.COLOR_BUFFER_BIT)
gl.useProgram(program)
gl.bindBuffer(gl.ARRAY_BUFFER, buffer)
gl.uniform2f(program.resolution, canvas.width, canvas.height)
gl.uniform1f(program.time, now * 1e-3)
gl.uniform2f(program.touch, mouse.x, mouse.y)
gl.uniform1i(program.pointerCount, mouse.touches.size)
gl.drawArrays(gl.TRIANGLES, 0, vertices.length * .5)
requestAnimationFrame(loop)
}
setup()
init()
resize()
loop(0)
window.addEventListener("pointerdown", e => mouse.update(e.clientX, e.clientY, e.pointerId))
window.addEventListener("pointerup", e => mouse.remove(e.pointerId))
window.addEventListener("pointermove", e => {
if (mouse.touches.has(e.pointerId))
mouse.update(e.clientX, e.clientY, e.pointerId)
})
案例
(一)时间
显示实时时间:
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1; // 月份从0开始,所以需要加1
const day = now.getDate();
const hours = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
console.log(`当前时间:${year}-${month}-${day} ${hours}:${minutes}:${seconds}`);
const timeElement = document.getElementById('time');
function updateTime() {
const now = new Date();
const year = now.getFullYear();
const month = now.getMonth() + 1;
const day = now.getDate();
const hours = now.getHours();
const minutes = now.getMinutes();
const seconds = now.getSeconds();
timeElement.textContent = `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
}
setInterval(updateTime, 1000); // 每秒更新一次时间
根据时间提示问候
const greetingElement = document.getElementById('greeting');
function updateGreeting() {
const now = new Date();
const hours = now.getHours();
let greeting;
if (hours >= 5 && hours < 7) {
greeting = '早上好!一天开始了,祝你有个美好的一天!';
} else if (hours >= 7 && hours < 10) {
greeting = '上午好!工作顺利,精神抖擞!';
} else if (hours >= 10 && hours < 12) {
greeting = '中午好!休息一下,吃顿美味的午餐吧!';
} else if (hours >= 12 && hours < 14) {
greeting = '下午好!休息好了,继续努力工作吧!';
} else if (hours >= 14 && hours < 18) {
greeting = '傍晚好!累了一天,愿你晚上能好好放松!';
} else if (hours >= 18 && hours < 20) {
greeting = '晚上好!享受美食,放松心情,准备迎接新的一天吧!';
} else if (hours >= 20 && hours < 22) {
greeting = '晚安!今天过得怎么样?希望你有个美好的夜晚!';
} else {
greeting = '夜深了,愿你拥有一个美好的梦乡!';
}
greetingElement.textContent = greeting;
}
updateGreeting();
网页栏显示实时时间
function updateTitle() {
const now = new Date();
const hours = String(now.getHours()).padStart(2, '0');
const minutes = String(now.getMinutes()).padStart(2, '0');
const seconds = String(now.getSeconds()).padStart(2, '0');
const timeString = `${hours}:${minutes}:${seconds}`;
document.title = timeString;
}
setInterval(updateTitle, 100); // 每秒更新一次标题
(二)css样式更换
动态调整元素尺寸
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>动态调整元素尺寸</title>
<style>
.box {
background-color: lightblue;
width: 100px;
height: 100px;
transition: all 0.5s;
}
</style>
</head>
<body>
<div class="box"></div>
<script>
function resizeBox() {
const box = document.querySelector('.box');
const newSize = Math.min(window.innerWidth, window.innerHeight) * 0.5;
box.style.width = newSize + 'px';
box.style.height = newSize + 'px';
}
window.addEventListener('resize', resizeBox);
resizeBox();
</script>
</body>
</html>
切换主题
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>切换主题</title>
<style>
body {
font-family: Arial, sans-serif;
transition: background-color 0.5s;
}
.dark-theme {
background-color: #333;
color: white;
}
</style>
</head>
<body>
<h1>切换主题示例</h1>
<button id="toggleTheme">切换主题</button>
<script>
const toggleThemeButton = document.getElementById('toggleTheme');
let darkThemeEnabled = false;
function toggleTheme() {
darkThemeEnabled = !darkThemeEnabled;
document.body.classList.toggle('dark-theme', darkThemeEnabled);
}
toggleThemeButton.addEventListener('click', toggleTheme);
</script>
</body>
</html>
(三)鼠标键盘事件
禁止F12和右键
// 禁止右键菜单
document.addEventListener('contextmenu', function (e) {
e.preventDefault();
alert('右键菜单已禁用');
});
// 禁止F12键
document.addEventListener('keydown', function (e) {
if (e.key === 'F12') {
e.preventDefault();
alert('F12已禁用');
}
});
文本或图片跟随鼠标
const followMouse = document.getElementById('followMouse');
document.addEventListener('mousemove', function (e) {
followMouse.style.left = e.clientX + 'px';
followMouse.style.top = e.clientY + 'px';
});
禁止鼠标
// 禁止鼠标点击
document.addEventListener('click', function (e) {
e.preventDefault();
alert('鼠标点击已禁用');
});
// 禁止鼠标移动
document.addEventListener('mousemove', function (e) {
e.preventDefault();
alert('鼠标移动已禁用');
});
(四)打字效果的文本展示
function typeWriterEffect(element, speed = 50) {
const text = element.innerText;
element.innerHTML = '';
let index = 0;
function type() {
if (index< text.length) {
element.innerHTML += text.charAt(index);
index++;
setTimeout(type, speed);
}
}
type();
}
// 选择页面中的typewriter元素并应用打字效果
const typewriterElement = document.querySelector('.typewriter');
typeWriterEffect(typewriterElement);
作者知识有限
也许与您还差距很远,写这篇文章也是为了巩固javascript的知识