前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站。
先看效果:
上源码:
javascript
import GUI from "https://cdn.jsdelivr.net/npm/lil-gui@0.18.2/+esm"
const canvasEl = document.querySelector("#ghost");
const mouseThreshold = .1;
const devicePixelRatio = Math.min(window.devicePixelRatio, 2);
const mouse = {
x: .3 * window.innerWidth,
y: .3 * window.innerHeight,
tX: .25 * window.innerWidth,
tY: .45 * window.innerHeight,
moving: false,
controlsPadding: 0
}
const params = {
size: .1,
tail: {
dotsNumber: 25,
spring: 1.4,
friction: .3,
maxGravity: 50,
gravity: 25,
},
smile: 1,
mainColor: [.98, .96, .96],
borderColor: [.2, .5, .7],
isFlatColor: false,
};
const textureEl = document.createElement("canvas");
const textureCtx = textureEl.getContext("2d");
const pointerTrail = new Array(params.tail.dotsNumber);
let dotSize = (i) => params.size * window.innerHeight * (1. - .2 * Math.pow(3. * i / params.tail.dotsNumber - 1., 2.));
for (let i = 0; i < params.tail.dotsNumber; i++) {
pointerTrail[i] = {
x: mouse.x,
y: mouse.y,
vx: 0,
vy: 0,
opacity: .04 + .3 * Math.pow(1 - i / params.tail.dotsNumber, 4),
bordered: .6 * Math.pow(1 - i / pointerTrail.length, 1),
r: dotSize(i)
}
}
let uniforms;
const gl = initShader();
createControls();
window.addEventListener("resize", resizeCanvas);
resizeCanvas();
render();
window.addEventListener("mousemove", e => {
updateMousePosition(e.clientX, e.clientY);
});
window.addEventListener("touchmove", e => {
updateMousePosition(e.targetTouches[0].clientX, e.targetTouches[0].clientY);
});
window.addEventListener("click", e => {
updateMousePosition(e.clientX, e.clientY);
});
let movingTimer = setTimeout(() => mouse.moving = false, 300);
function updateMousePosition(eX, eY) {
mouse.moving = true;
if (mouse.controlsPadding < 0) {
mouse.moving = false;
}
clearTimeout(movingTimer);
movingTimer = setTimeout(() => {
mouse.moving = false;
}, 300);
mouse.tX = eX;
const size = params.size * window.innerHeight;
eY -= .6 * size;
mouse.tY = eY > size ? eY : size;
mouse.tY -= mouse.controlsPadding;
}
function initShader() {
const vsSource = document.getElementById("vertShader").innerHTML;
const fsSource = document.getElementById("fragShader").innerHTML;
const gl = canvasEl.getContext("webgl") || canvasEl.getContext("experimental-webgl");
if (!gl) {
alert("WebGL is not supported by your browser.");
}
function createShader(gl, sourceCode, type) {
const shader = gl.createShader(type);
gl.shaderSource(shader, sourceCode);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error("An error occurred compiling the shaders: " + gl.getShaderInfoLog(shader));
gl.deleteShader(shader);
return null;
}
return shader;
}
const vertexShader = createShader(gl, vsSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(gl, fsSource, gl.FRAGMENT_SHADER);
function createShaderProgram(gl, vertexShader, fragmentShader) {
const program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
console.error("Unable to initialize the shader program: " + gl.getProgramInfoLog(program));
return null;
}
return program;
}
const shaderProgram = createShaderProgram(gl, vertexShader, fragmentShader);
uniforms = getUniforms(shaderProgram);
function getUniforms(program) {
let uniforms = [];
let uniformCount = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
for (let i = 0; i < uniformCount; i++) {
let uniformName = gl.getActiveUniform(program, i).name;
uniforms[uniformName] = gl.getUniformLocation(program, uniformName);
}
return uniforms;
}
const vertices = new Float32Array([-1., -1., 1., -1., -1., 1., 1., 1.]);
const vertexBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
gl.useProgram(shaderProgram);
const positionLocation = gl.getAttribLocation(shaderProgram, "a_position");
gl.enableVertexAttribArray(positionLocation);
gl.bindBuffer(gl.ARRAY_BUFFER, vertexBuffer);
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
const canvasTexture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, canvasTexture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureEl);
gl.uniform1i(uniforms.u_texture, 0);
gl.uniform1f(uniforms.u_size, params.size);
gl.uniform3f(uniforms.u_main_color, params.mainColor[0], params.mainColor[1], params.mainColor[2]);
gl.uniform3f(uniforms.u_border_color, params.borderColor[0], params.borderColor[1], params.borderColor[2]);
return gl;
}
function updateTexture() {
textureCtx.fillStyle = 'black';
textureCtx.fillRect(0, 0, textureEl.width, textureEl.height);
pointerTrail.forEach((p, pIdx) => {
if (pIdx === 0) {
p.x = mouse.x;
p.y = mouse.y;
} else {
p.vx += (pointerTrail[pIdx - 1].x - p.x) * params.tail.spring;
p.vx *= params.tail.friction;
p.vy += (pointerTrail[pIdx - 1].y - p.y) * params.tail.spring;
p.vy *= params.tail.friction;
p.vy += params.tail.gravity;
p.x += p.vx;
p.y += p.vy;
}
const grd = textureCtx.createRadialGradient(p.x, p.y, p.r * p.bordered, p.x, p.y, p.r);
grd.addColorStop(0, 'rgba(255, 255, 255, ' + p.opacity + ')');
grd.addColorStop(1, 'rgba(255, 255, 255, 0)');
textureCtx.beginPath();
textureCtx.fillStyle = grd;
textureCtx.arc(p.x, p.y, p.r, 0, Math.PI * 2);
textureCtx.fill();
});
}
function render() {
const currentTime = performance.now();
gl.uniform1f(uniforms.u_time, currentTime);
gl.clearColor(0.0, 0.0, 0.0, 1.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
if (mouse.moving) {
params.smile -= .05;
params.smile = Math.max(params.smile, -.1);
params.tail.gravity -= 10 * params.size;
params.tail.gravity = Math.max(params.tail.gravity, 0);
} else {
params.smile += .01;
params.smile = Math.min(params.smile, 1);
if (params.tail.gravity > 30 * params.size) {
params.tail.gravity = (30 + 9 * (1 + Math.sin(.002 * currentTime))) * params.size;
} else {
params.tail.gravity += params.size;
}
}
mouse.x += (mouse.tX - mouse.x) * mouseThreshold;
mouse.y += (mouse.tY - mouse.y) * mouseThreshold;
gl.uniform1f(uniforms.u_smile, params.smile);
gl.uniform2f(uniforms.u_pointer, mouse.x / window.innerWidth, 1. - mouse.y / window.innerHeight);
gl.uniform2f(uniforms.u_target_pointer, mouse.tX / window.innerWidth, 1. - mouse.tY / window.innerHeight);
updateTexture();
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, textureEl);
requestAnimationFrame(render);
}
function resizeCanvas() {
canvasEl.width = window.innerWidth * devicePixelRatio;
canvasEl.height = window.innerHeight * devicePixelRatio;
textureEl.width = window.innerWidth;
textureEl.height = window.innerHeight;
gl.viewport(0, 0, canvasEl.width, canvasEl.height);
gl.uniform1f(uniforms.u_ratio, canvasEl.width / canvasEl.height);
for (let i = 0; i < params.tail.dotsNumber; i++) {
pointerTrail[i].r = dotSize(i);
}
}
function createControls() {
const gui = new GUI();
gui.add(params, "size", .02, .3, .01)
.onChange(v => {
for (let i = 0; i < params.tail.dotsNumber; i++) {
pointerTrail[i].r = dotSize(i);
}
gl.uniform1f(uniforms.u_size, params.size);
});
gui.addColor(params, "mainColor").onChange(v => {
gl.uniform3f(uniforms.u_main_color, v[0], v[1], v[2]);
});
const borderColorControl = gui.addColor(params, "borderColor").onChange(v => {
gl.uniform3f(uniforms.u_border_color, v[0], v[1], v[2]);
});
gui.add(params, "isFlatColor")
.onFinishChange(v => {
borderColorControl.disable(v);
gl.uniform1f(uniforms.u_flat_color, v ? 1 : 0);
});
const controlsEl = document.querySelector(".lil-gui");
controlsEl.addEventListener("mouseenter", () => {
mouse.controlsPadding = -controlsEl.getBoundingClientRect().height;
});
controlsEl.addEventListener("mouseleave", () => {
mouse.controlsPadding = 0;
});
}
html
<div class="page">
WebGL Ghost Cursor
</div>
<canvas id="ghost"></canvas>
<script type="x-shader/x-fragment" id="vertShader">
precision mediump float;
varying vec2 vUv;
attribute vec2 a_position;
void main() {
vUv = .5 * (a_position + 1.);
gl_Position = vec4(a_position, 0.0, 1.0);
}
</script>
<script type="x-shader/x-fragment" id="fragShader">
precision mediump float;
varying vec2 vUv;
uniform float u_time;
uniform float u_ratio;
uniform float u_size;
uniform vec2 u_pointer;
uniform float u_smile;
uniform vec2 u_target_pointer;
uniform vec3 u_main_color;
uniform vec3 u_border_color;
uniform float u_flat_color;
uniform sampler2D u_texture;
#define TWO_PI 6.28318530718
#define PI 3.14159265358979323846
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec2 mod289(vec2 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
vec3 permute(vec3 x) { return mod289(((x*34.0)+1.0)*x); }
float snoise(vec2 v) {
const vec4 C = vec4(0.211324865405187, 0.366025403784439, -0.577350269189626, 0.024390243902439);
vec2 i = floor(v + dot(v, C.yy));
vec2 x0 = v - i + dot(i, C.xx);
vec2 i1;
i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0);
vec4 x12 = x0.xyxy + C.xxzz;
x12.xy -= i1;
i = mod289(i);
vec3 p = permute(permute(i.y + vec3(0.0, i1.y, 1.0)) + i.x + vec3(0.0, i1.x, 1.0));
vec3 m = max(0.5 - vec3(dot(x0, x0), dot(x12.xy, x12.xy), dot(x12.zw, x12.zw)), 0.0);
m = m*m;
m = m*m;
vec3 x = 2.0 * fract(p * C.www) - 1.0;
vec3 h = abs(x) - 0.5;
vec3 ox = floor(x + 0.5);
vec3 a0 = x - ox;
m *= 1.79284291400159 - 0.85373472095314 * (a0*a0 + h*h);
vec3 g;
g.x = a0.x * x0.x + h.x * x0.y;
g.yz = a0.yz * x12.xz + h.yz * x12.yw;
return 130.0 * dot(m, g);
}
vec2 rotate(vec2 v, float angle) {
float r_sin = sin(angle);
float r_cos = cos(angle);
return vec2(v.x * r_cos - v.y * r_sin, v.x * r_sin + v.y * r_cos);
}
float eyes(vec2 uv) {
uv.y -= .5;
uv.x *= 1.;
uv.y *= .8;
uv.x = abs(uv.x);
uv.y += u_smile * .3 * pow(uv.x, 1.3);
uv.x -= (.6 + .2 * u_smile);
float d = clamp(length(uv), 0., 1.);
return 1. - pow(d, .08);
}
float mouth(vec2 uv) {
uv.y += 1.5;
uv.x *= (.5 + .5 * abs(1. - u_smile));
uv.y *= (3. - 2. * abs(1. - u_smile));
uv.y -= u_smile * 4. * pow(uv.x, 2.);
float d = clamp(length(uv), 0., 1.);
return 1. - pow(d, .07);
}
float face(vec2 uv, float rotation) {
uv = rotate(uv, rotation);
uv /= (.27 * u_size);
float eyes_shape = 10. * eyes(uv);
float mouth_shape = 20. * mouth(uv);
float col = 0.;
col = mix(col, 1., eyes_shape);
col = mix(col, 1., mouth_shape);
return col;
}
void main() {
vec2 point = u_pointer;
point.x *= u_ratio;
vec2 uv = vUv;
uv.x *= u_ratio;
uv -= point;
float texture = texture2D(u_texture, vec2(vUv.x, 1. - vUv.y)).r;
float shape = texture;
float noise = snoise(uv * vec2(.7 / u_size, .6 / u_size) + vec2(0., .0015 * u_time));
noise += 1.2;
noise *= 2.1;
noise += smoothstep(-.8, -.2, (uv.y) / u_size);
float face = face(uv, 5. * (u_target_pointer.x - u_pointer.x));
shape -= face;
shape *= noise;
vec3 border = (1. - u_border_color);
border.g += .2 * sin(.005 * u_time);
border *= .5;
vec3 color = u_main_color;
color -= (1. - u_flat_color) * border * smoothstep(.0, .01, shape);
shape = u_flat_color * smoothstep(.8, 1., shape) + (1. - u_flat_color) * shape;
color *= shape;
gl_FragColor = vec4(color, shape);
}
</script>
css
body, html {
margin: 0;
padding: 0;
overflow: hidden;
background-color: #2C3E50;
}
canvas#ghost {
position: fixed;
top: 0;
left: 0;
display: block;
width: 100%;
z-index: 10000;
pointer-events: none;
}
.page {
min-height: 100vh;
display: flex;
align-items: center;
justify-content: center;
color: white;
text-align: center;
font-size: 4vw;
text-shadow: 0 0 5px #000000;
}
.lil-gui {
--width: 300px;
max-width: 90%;
--widget-height: 20px;
font-size: 15px;
--input-font-size: 15px;
--padding: 10px;
--spacing: 10px;
--slider-knob-width: 5px;
--background-color: rgba(5, 0, 15, .8);
--widget-color: rgba(255, 255, 255, .3);
--focus-color: rgba(255, 255, 255, .4);
--hover-color: rgba(255, 255, 255, .5);
--font-family: monospace;
z-index: 1;
}