文章目录

序号目录
1HTML满屏跳动的爱心(可写字)
2HTML五彩缤纷的爱心
3HTML满屏漂浮爱心
4HTML情人节快乐
5HTML蓝色爱心射线
6HTML跳动的爱心(简易版)
7HTML粒子爱心
8HTML蓝色动态爱心
9HTML跳动的爱心(双心版)
10HTML橙色动态粒子爱心
11HTML旋转爱心
12HTML爱情树
13HTML3D相册
14HTML旋转相册
15HTML基础烟花秀
16HTML炫酷烟花秀
17HTML粉色烟花秀
18HTML新春烟花
19HTML龙年大吉
20HTML圣诞树
21HTML大雪纷飞
22HTML想见你
23HTML元素周期表
24HTML飞舞的花瓣
25HTML星空特效
26HTML黑客帝国字母雨
27HTML哆啦A梦
28HTML流星雨
29HTML沙漏爱心
30HTML爱心字母雨
31HTML爱心流星雨
32HTML生日蛋糕
33HTML流光爱心

写在前面

本期博主给大家推荐一个由HTML代码实现的、红蓝色线条组成的流光爱心,一起来看看吧。

完整代码

<!DOCTYPE html>
<!--<div class="menu__subheader"><br>微信公众号:雁卿雁卿呀-->
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <title>流光爱心</title>
  <meta name="author" content="Logical Digit">
  </head>
      <!-- css部分 -->
 <style>
  body {
    background-color: #000;
    margin: 0;
    overflow: hidden;
    background-repeat: no-repeat;
}
</style>
  <body>
    <!-- 绘画爱心 -->
    <canvas id="canvas" width="1400" height="600"></canvas>
    <!-- js部分 -->
  </body>
  <script>
var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
// Initialize the GL context
var gl = canvas.getContext('webgl');
if (!gl) {
    console.error("Unable to initialize WebGL.");
}
//Time step
var dt = 0.015;
//Time
var time = 0.0;
//************** Shader sources **************
var vertexSource = `
attribute vec2 position;
void main() {
  gl_Position = vec4(position, 0.0, 1.0);
}
`;
 
var fragmentSource = `
precision highp float;
uniform float width;
uniform float height;
vec2 resolution = vec2(width, height);
uniform float time;
#define POINT_COUNT 8
vec2 points[POINT_COUNT];
const float speed = -0.5;
const float len = 0.25;
float intensity = 0.9;
float radius = 0.015;
//https://www.shadertoy.com/view/MlKcDD
//Signed distance to a quadratic bezier
float sdBezier(vec2 pos, vec2 A, vec2 B, vec2 C){    
  vec2 a = B - A;
  vec2 b = A - 2.0*B + C;
  vec2 c = a * 2.0;
  vec2 d = A - pos;
  float kk = 1.0 / dot(b,b);
  float kx = kk * dot(a,b);
  float ky = kk * (2.0*dot(a,a)+dot(d,b)) / 3.0;
  float kz = kk * dot(d,a);      
  float res = 0.0;
  float p = ky - kx*kx;
  float p3 = p*p*p;
  float q = kx*(2.0*kx*kx - 3.0*ky) + kz;
  float h = q*q + 4.0*p3;
  if(h >= 0.0){ 
    h = sqrt(h);
    vec2 x = (vec2(h, -h) - q) / 2.0;
    vec2 uv = sign(x)*pow(abs(x), vec2(1.0/3.0));
    float t = uv.x + uv.y - kx;
    t = clamp( t, 0.0, 1.0 );
    // 1 root
    vec2 qos = d + (c + b*t)*t;
    res = length(qos);
  }else{
    float z = sqrt(-p);
    float v = acos( q/(p*z*2.0) ) / 3.0;
    float m = cos(v);
    float n = sin(v)*1.732050808;
    vec3 t = vec3(m + m, -n - m, n - m) * z - kx;
    t = clamp( t, 0.0, 1.0 );
    // 3 roots
    vec2 qos = d + (c + b*t.x)*t.x;
    float dis = dot(qos,qos);
        
    res = dis;
    qos = d + (c + b*t.y)*t.y;
    dis = dot(qos,qos);
    res = min(res,dis);
    
    qos = d + (c + b*t.z)*t.z;
    dis = dot(qos,qos);
    res = min(res,dis);
    res = sqrt( res );
  }
    
  return res;
}
//http://mathworld.wolfram.com/HeartCurve.html
vec2 getHeartPosition(float t){
  return vec2(16.0 * sin(t) * sin(t) * sin(t),
              -(13.0 * cos(t) - 5.0 * cos(2.0*t)
              - 2.0 * cos(3.0*t) - cos(4.0*t)));
}
//https://www.shadertoy.com/view/3s3GDn
float getGlow(float dist, float radius, float intensity){
  return pow(radius/dist, intensity);
}
float getSegment(float t, vec2 pos, float offset, float scale){
  for(int i = 0; i < POINT_COUNT; i++){
    points[i] = getHeartPosition(offset + float(i)*len + fract(speed * t) * 6.28);
  }
    
  vec2 c = (points[0] + points[1]) / 2.0;
  vec2 c_prev;
  float dist = 10000.0;
    
  for(int i = 0; i < POINT_COUNT-1; i++){
    //https://tinyurl.com/y2htbwkm
    c_prev = c;
    c = (points[i] + points[i+1]) / 2.0;
    dist = min(dist, sdBezier(pos, scale * c_prev, scale * points[i], scale * c));
  }
  return max(0.0, dist);
}
void main(){
  vec2 uv = gl_FragCoord.xy/resolution.xy;
  float widthHeightRatio = resolution.x/resolution.y;
  vec2 centre = vec2(0.5, 0.5);
  vec2 pos = centre - uv;
  pos.y /= widthHeightRatio;
  //Shift upwards to centre heart
  pos.y += 0.02;
  float scale = 0.000015 * height;
  
  float t = time;
    
  //Get first segment
  float dist = getSegment(t, pos, 0.0, scale);
  float glow = getGlow(dist, radius, intensity);
    
  vec3 col = vec3(0.0);
    
  //White core
  col += 10.0*vec3(smoothstep(0.003, 0.001, dist));
  //Pink glow
  col += glow * vec3(0.94,0.14,0.4);
    
  //Get second segment
  dist = getSegment(t, pos, 3.4, scale);
  glow = getGlow(dist, radius, intensity);
    
  //White core
  col += 10.0*vec3(smoothstep(0.003, 0.001, dist));
  //Blue glow
  col += glow * vec3(0.2,0.6,1.0);
        
  //Tone mapping
  col = 1.0 - exp(-col);
  //Output to screen
   gl_FragColor = vec4(col,1.0);
}
`;
 
//************** Utility functions **************
 
window.addEventListener('resize', onWindowResize, false);
 
function onWindowResize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.uniform1f(widthHandle, window.innerWidth);
    gl.uniform1f(heightHandle, window.innerHeight);
}
 
 
//Compile shader and combine with source
function compileShader(shaderSource, shaderType) {
    var shader = gl.createShader(shaderType);
    gl.shaderSource(shader, shaderSource);
    gl.compileShader(shader);
    if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
        throw "Shader compile failed with: " + gl.getShaderInfoLog(shader);
    }
    return shader;
}
 
//From https://codepen.io/jlfwong/pen/GqmroZ
//Utility to complain loudly if we fail to find the attribute/uniform
function getAttribLocation(program, name) {
    var attributeLocation = gl.getAttribLocation(program, name);
    if (attributeLocation === -1) {
        throw 'Cannot find attribute ' + name + '.';
    }
    return attributeLocation;
}
 
function getUniformLocation(program, name) {
    var attributeLocation = gl.getUniformLocation(program, name);
    if (attributeLocation === -1) {
        throw 'Cannot find uniform ' + name + '.';
    }
    return attributeLocation;
}
 
//************** Create shaders **************
 
//Create vertex and fragment shaders
var vertexShader = compileShader(vertexSource, gl.VERTEX_SHADER);
var fragmentShader = compileShader(fragmentSource, gl.FRAGMENT_SHADER);
 
//Create shader programs
var program = gl.createProgram();
gl.attachShader(program, vertexShader);
gl.attachShader(program, fragmentShader);
gl.linkProgram(program);
 
gl.useProgram(program);
 
//Set up rectangle covering entire canvas 
var vertexData = new Float32Array([-1.0, 1.0, // top left
    -1.0, -1.0, // bottom left
    1.0, 1.0, // top right
    1.0, -1.0, // bottom right
]);
 
//Create vertex buffer
var vertexDataBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, vertexDataBuffer);
gl.bufferData(gl.ARRAY_BUFFER, vertexData, gl.STATIC_DRAW);
 
// Layout of our data in the vertex buffer
var positionHandle = getAttribLocation(program, 'position');
 
gl.enableVertexAttribArray(positionHandle);
gl.vertexAttribPointer(positionHandle,
    2, // position is a vec2 (2 values per component)
    gl.FLOAT, // each component is a float
    false, // don't normalize values
    2 * 4, // two 4 byte float components per vertex (32 bit float is 4 bytes)
    0 // how many bytes inside the buffer to start from
);
 
//Set uniform handle
var timeHandle = getUniformLocation(program, 'time');
var widthHandle = getUniformLocation(program, 'width');
var heightHandle = getUniformLocation(program, 'height');
 
gl.uniform1f(widthHandle, window.innerWidth);
gl.uniform1f(heightHandle, window.innerHeight);
 
function draw() {
    //Update time
    time += dt;
 
    //Send uniforms to program
    gl.uniform1f(timeHandle, time);
    //Draw a triangle strip connecting vertices 0-4
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
 
    requestAnimationFrame(draw);
}
 
draw();
</script>
</html>

代码分析

这段代码通过HTML、CSS和WebGL结合实现了一个“流光爱心”效果,以下是对其的详细分析。

1. HTML结构

在HTML部分,主要是一个canvas元素,它是用于绘制心形动画的区域。canvas元素通过其ID属性canvas在JavaScript中被引用,用于初始化WebGL上下文。

<canvas id="canvas" width="1400" height="600"></canvas>

在这里,宽度和高度只是初始设置,代码通过JavaScript调整canvas的大小以适应浏览器窗口。

2. CSS部分

CSS的作用是设置整个网页的背景颜色为黑色,并使画布占据整个窗口,同时禁用滚动条以确保效果全屏展示。

body {

    background-color: #000;
    margin: 0;
    overflow: hidden;
    background-repeat: no-repeat;
}

3. WebGL上下文初始化

JavaScript部分通过getContext('webgl')获取WebGL绘图上下文,这是一种用于在网页中绘制2D和3D图形的API。

var canvas = document.getElementById("canvas");
canvas.width = window.innerWidth;
canvas.height = window.innerHeight;
var gl = canvas.getContext('webgl');
if (!gl) {
    console.error("Unable to initialize WebGL.");
}

这段代码确保WebGL能够正常运行,如果无法初始化WebGL,会在控制台输出错误信息。

4. 着色器程序

代码中定义了顶点着色器和片段着色器,用于处理图形的顶点和颜色计算。顶点着色器vertexSource负责定义绘制区域,而片段着色器fragmentSource则负责生成心形动画的光效。

  • 顶点着色器:这个简单的顶点着色器只定义了矩形区域,覆盖整个canvas

attribute vec2 position;
void main() {
  gl_Position = vec4(position, 0.0, 1.0);
}
  • 片段着色器:这是核心部分,计算并绘制了心形的流光效果。着色器使用了多种图形技术,如二次贝塞尔曲线距离函数来生成心形轮廓,getGlow函数实现光晕效果,getHeartPosition函数计算心形轨迹。

vec2 getHeartPosition(float t){
  return vec2(16.0 * sin(t) * sin(t) * sin(t),
              -(13.0 * cos(t) - 5.0 * cos(2.0*t)
              - 2.0 * cos(3.0*t) - cos(4.0*t)));
}

这个公式是数学中的经典“心形曲线公式”,通过调整时间参数t,可以得到心形轨迹上的点。

5. 动态渲染

draw函数中,程序使用requestAnimationFrame(draw)不断调用自己来实现帧动画,逐帧更新time变量,使得心形的流光效果随着时间变化而持续运行。

function draw() {
    time += dt;
    gl.uniform1f(timeHandle, time);
    gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
    requestAnimationFrame(draw);
}

每一帧,WebGL都会根据当前的时间time重新计算心形上每个点的位置,进而生成流光效果。

6. 响应窗口变化

代码监听了窗口大小的变化,使用onWindowResize函数动态调整画布的大小,确保动画始终适应窗口尺寸,并且心形保持居中显示。

window.addEventListener('resize', onWindowResize, false);
function onWindowResize() {
    canvas.width = window.innerWidth;
    canvas.height = window.innerHeight;
    gl.viewport(0, 0, canvas.width, canvas.height);
    gl.uniform1f(widthHandle, window.innerWidth);
    gl.uniform1f(heightHandle, window.innerHeight);
}

总结

这段代码通过WebGL技术生成了一个心形流光效果。核心部分在于数学公式计算出的心形轨迹和光晕效果,通过着色器来实现。WebGL的强大之处在于它能直接操控GPU,生成高效的动画效果。

写在后面

我是一只有趣的兔子,感谢你的喜欢!

点赞(0) 打赏

评论列表 共有 0 条评论

暂无评论

微信公众账号

微信扫一扫加关注

发表
评论
返回
顶部