大数跨境
0
0

会 “感知时间” 的 3D 光影时钟

会 “感知时间” 的 3D 光影时钟 码途钥匙
2025-07-15
0
· 点击蓝字,关注我们

































效果图

图片

01
一、HTML
要让这个会旋转、会变色的 3D 时钟在屏幕上呈现,首先需要一个 “表演舞台”—— 这正是 HTML 的使命。它用极简的标签,为这场光影秀奠定了基础:
  • 一个<canvas>标签是核心,就像一块黑色的数字画布,所有的 3D 时钟元素都将在这里绘制。它没有预设的图案,却能承载 WebGL 渲染的复杂图形,是连接代码与视觉的 “桥梁”;

  • 两个隐藏的<script>标签分别存放 “顶点着色器” 和 “片段着色器” 代码 —— 这两个特殊脚本是 WebGL 的 “画笔”,负责计算图形的形状和颜色,而 HTML 的作用就是安全地 “保管” 它们,等待 JavaScript 调用。

没有多余的元素,HTML 只用最基础的结构定义了 “哪里可以绘图” 和 “绘图规则存放在哪里”,就像搭建了一个空旷的剧场,只等后续的 “灯光” 和 “演员” 登场。

02
二、CSS
CSS 虽然不直接参与 3D 图形的计算,却用简单的样式为时钟营造了恰到好处的视觉氛围,让技术感与美感平衡:

  • 它将页面背景设为深灰色(background: #333),像一块低调的幕布,让时钟的光影效果更突出 —— 就像电影院的黑暗环境能让银幕更明亮;

  • 让画布(<canvas>)占满整个屏幕(width: 100%; height: 100%),确保无论在手机还是电脑上,时钟都能完整展示,没有边框限制,增强沉浸感;

  • 隐藏页面滚动条(overflow: hidden),避免多余的元素干扰视线,让注意力完全集中在旋转的时钟上。


这些看似简单的样式,实则是在精心控制 “视觉焦点”:用深色背景衬托时钟的亮色,用全屏布局强化 3D 效果的纵深感,让技术生成的图形更具观赏性。

03
三、JavaScript

这个 3D 时钟最神奇的地方 —— 会随时间转动、会随鼠标旋转、会按时段变色 —— 全靠 JavaScript 这个 “导演” 在幕后指挥。它的工作可以分为三个核心部分:

1. 让时钟 “走” 起来:时间的精准计算

JavaScript 会实时获取当前时间(小时、分钟、秒),并将这些数字转化为 3D 时钟的 “运动指令”:

  • 秒针每 1 秒转动一个刻度,分针每 60 秒转动一格,时针每小时缓慢移动 —— 就像机械钟的齿轮传动,只不过这里用代码计算角度

  • 为了让转动更自然,它还会添加 “动画过渡”:比如秒针不是突然跳格,而是有微小的加速和减速,模拟真实时钟的惯性。

2. 用 WebGL 绘制 3D 效果:着色器的 “魔法”

JavaScript 会调用 HTML 中存放的着色器代码,让它们在画布上绘制出立体的时钟:

  • 顶点着色器负责计算每个 “刻度” 的 3D 位置,让时钟看起来有厚度和弧度,不是扁平的图案;

  • 片段着色器则计算每个像素的颜色:白天时用暖黄色调,夜晚时切换为蓝紫色,凌晨和傍晚又有不同的过渡色,就像时钟在 “感知” 时间的流逝;

  • 还会添加光影效果:模拟光线照射在时钟上的明暗变化,让立体效果更真实,比如刻度的边缘会有高光,背面则略显阴暗。

3. 响应互动:让时钟 “跟着鼠标转”

当你移动鼠标时,JavaScript 会捕捉鼠标位置,实时调整时钟的视角:

  • 鼠标上下移动,时钟会前后倾斜;左右移动,时钟会左右旋转,就像你在亲手转动一个实物时钟;

  • 为了让旋转更顺滑,它会给视角变化添加 “缓冲”,不会因为鼠标突然移动而显得生硬 —— 这种细节让互动感更自然。

此外,JavaScript 还负责 “舞台维护”:比如窗口大小变化时,自动调整画布尺寸;确保着色器代码正确运行,避免图形出错。就像一个全能导演,既管演员(时钟)的动作,又管舞台(画布)的适配。

04
完整代码
<!DOCTYPE html><html lang="en">  <head>    <meta charset="UTF-8" />    <title>基于 WebGL 的着色器时钟</title>    <style>      html {        height100%;      }      body {        background#333;        overflow: hidden;        padding0;        margin0;        width100%;        height100%;        display: flex;        align-items: center;      }      canvas {        height100%;        width100%;      }    </style>  </head>  <body>    <canvas id="canvas"></canvas>    <!-- VertexShader code here -->    <script id="vertexShader" type="x-shader/x-vertex">      #version 300 es      precision highp float;        in vec4 position;        void main() {          gl_Position = vec4( position );        }    </script>    <!-- FragmentShader code here -->    <script id="fragmentShader" type="x-shader/x-fragment">      #version 300 es      #if __VERSION__ < 130      #define TEXTURE2D texture2D      #else      #define TEXTURE2D texture      #endif      precision highp float;      out vec4 fragColor;      uniform vec2 u_resolution;      uniform vec4 u_mouse;      uniform float u_time;      uniform float u_date;      #define R u_resolution      #define T u_time      #define M u_mouse      #define PI          3.14159265359      #define PI2         6.28318530718      #define MIN_DIST    1e-4      #define MAX_DIST    50.      /**          License: Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License          Dial Clock Concept          Was kicking this around in my brain to make a clock shader.          The math is a little messy and there's some trick logic          around the hours to make the dials work.      */        // numbers from SDF Shapes      float box( in vec2 p, in vec2 b ){          vec2 d = abs(p)-b;          return length(max(d,0.))+min(max(d.x,d.y),0.);      }      float rbox( in vec2 p, in vec2 b, in vec4 r ) {          r.xy = (p.x>0.)?r.xy : r.zw;          r.x  = (p.y>0.)?r.x  : r.y;          vec2 q = abs(p)-b+r.x;          return min(max(q.x,q.y),0.)+length(max(q,0.))-r.x;      }      const vec4 bz = vec4(.075,.055,.035,.0);      const vec4 bo = vec4(.175,.15,.075,.025);      const vec2 oa = vec2(.15,.1);      // number      float get0(vec2 p) {          float bt = max(              rbox(p,vec2(.15,.175),bz.xxxx),              -rbox(p,vec2(.1,.125),bz.zzzz)          );          return bt;      }      float get1(vec2 p) {          float bt = box(p,vec2(.025,.175));          return bt;      }      float get2(vec2 p) {          float bt = max(              rbox(p-vec2(.0,.075),oa,bz.xxww),              -rbox(p-vec2(-.075,.075),vec2(.175,.05),bz.zzww)          );          bt = max(bt,-box(p-vec2(-.085,.045),vec2(.08,.08)));          float bb = max(              rbox(p+vec2(.0,.075),oa,bz.wwxx),              -rbox(p+vec2(-.075,.075),vec2(.175,.05),bz.wwzz)          );          bb = max(bb,-box(p+vec2(-.085,.045),vec2(.08,.08)));          return min(bt,bb);      }      float get3(vec2 p) {          vec2 of = vec2(0,.075);          float bt = max(          rbox(p-of,oa,bz.xxww),          -rbox(p-of+vec2(.05,0),vec2(.15,.05),bz.zzww)          );          float bb = max(          rbox(p+of,oa,bz.xxww),          -rbox(p+of+vec2(.05,0),vec2(.15,.05),bz.zzww)          );          return min(bt,bb);      }      float get4(vec2 p) {          float bt = min(box(vec2(p.x-.125,p.y),vec2(.025,.175)),              rbox(p-vec2(.0,.075),oa,bz.wwwx)          );          bt = max(              bt,              -rbox(p-vec2(0,.115),vec2(.1,.095),bz.wwwz)          );          return bt;      }      float get5(vec2 p) {          float bt = max(              rbox(p-vec2(.0,.075),oa,bz.wwwx),              -rbox(p-vec2(.075,.075),vec2(.175,.05),bz.wwwz)          );          bt = max(bt,-box(p-vec2(.085,.045),vec2(.08,.08)));          float bb = max(              rbox(p+vec2(.0,.075),oa,bz.xxww),              -rbox(p+vec2(.075,.075),vec2(.175,.05),bz.zzww)          );          bb = max(bb,-box(p+vec2(.085,.045),vec2(.08,.08)));          return min(bt,bb);      }      float get6(vec2 p) {          float bt = max(              rbox(p-vec2(.0,.075),oa,bz.wwxw),              -rbox(p-vec2(.075,.075),vec2(.175,.05),bz.wwzw)          );          bt = max(bt,-box(p-vec2(.085,.045),vec2(.08,.08)));          float bb = max(              rbox(p+vec2(.0,.075),oa,bz.xxwx),              -rbox(p+vec2(.0,.075),vec2(.1,.05),bz.zzwz)          );          return min(bt,bb);      }      float get7(vec2 p) {          float bt = max(              rbox(p+vec2(0,.0),vec2(.15,.175),bz.xwww),              -rbox(p+vec2(.05,.045),vec2(.15,.175),bz.zwww)          );          return bt;      }      float get8(vec2 p) {          float bt = max(              rbox(p-vec2(.0,.075),oa,bz.xxxx),              -rbox(p-vec2(.0,.075),vec2(.1,.05),bz.zzzz)          );          float bb = max(              rbox(p+vec2(.0,.075),oa,bz.xxxx),              -rbox(p+vec2(.0,.075),vec2(.1,.05),bz.zzzz)          );          return min(bt,bb);      }      float get9(vec2 p) {          float bt = max(              rbox(p-vec2(.0,.075),oa,bz.xwxx),              -rbox(p-vec2(.0,.075),vec2(.1,.05),bz.zwzz)          );          float bb = max(              rbox(p+vec2(.0,.075),oa,bz.wxxw),              -rbox(p+vec2(.075,-.02),vec2(.175,.15),bz.zzwz)          );          return min(bt,bb);      }      // the shader code below      float hash21(vec2 p) {return fract(sin(dot(p, vec2(27.6932.53)))*437.53);}      mat2 rot(float a){ return mat2(cos(a),sin(a),-sin(a),cos(a)); }      // timing functions      float lerp (float b, float e, float t) { return clamp((t - b) / (e - b), 0.1.); }      float eic (float t) { return  t*t*t; }      float opx(in float d, in float z, in float h){          vec2 w = vec2( d, abs(z) - h );          return min(max(w.x, w.y), 0.) + length(max(w, 0.));      }      // globals      float h1,h2,m1,m2,s1,s2,tmod;      vec3 hp,hit;      vec2 map(vec3 p) {          vec2 res = vec2(1e5,0);          vec3 q = p + vec3(2,0,0);          float ofs = 2.8;          float mn = -.62831853071;          float sz = 1.3;          vec2 q1 = q.yz;          vec2 q2 = q.yz;          q1*=rot(ofs+(h1*mn));          q2*=rot(ofs+(h2*mn));          float d = opx(abs(length(q1)-sz)-.01,q.x,.25)-.025;          if(d<res.x)  {              res = vec2(d,1);              hit = vec3(q.x,q1.y,-q1.x);          }          float e = opx(abs(length(q2)-sz)-.01,q.x-.75,.25)-.025;          if(e<res.x)  {              res = vec2(e,2);              hit = vec3(q.x,q2.y,-q2.x);          }          q = p + vec3(0,0,0);          q1 = p.yz;          q2 = p.yz;          q1*=rot(ofs+(m1*mn));          q2*=rot(ofs+(m2*mn));          float g = opx(abs(length(q1)-sz)-.01,q.x,.25)-.025;          if(g<res.x)  {              res = vec2(g,3);              hit = vec3(q.x,q1.y,-q1.x);          }          float h = opx(abs(length(q2)-sz)-.01,q.x-.75,.25)-.025;          if(h<res.x)  {              res = vec2(h,4);              hit = vec3(q.x,q2.y,-q2.x);          }          q = p - vec3(2,0,0);          q1 = p.yz;          q2 = p.yz;          q1*=rot(ofs+(s1*mn));          q2*=rot(ofs+(s2*mn));          float j = opx(abs(length(q1)-sz)-.01,q.x,.25)-.025;          if(j<res.x)  {              res = vec2(j,5);              hit = vec3(q.x,q1.y,-q1.x);          }          float k = opx(abs(length(q2)-sz)-.01,q.x-.75,.25)-.025;          if(k<res.x)  {              res = vec2(k,6);              hit = vec3(q.x,q2.y,-q2.x);          }          float fl = length(p.zy)-.75;          if(fl<res.x)  {              res = vec2(fl,10);              hit = p;          }          return res;      }      vec3 normal(vec3 p, float t) {          t*=MIN_DIST;          float d = map(p).x;          vec2 e = vec2(t,0);          vec3 n = d - vec3(              map(p-e.xyy).x,              map(p-e.yxy).x,              map(p-e.yyx).x          );          return normalize(n);      }      vec2 marcher(vec3 ro, vec3 rd, inout vec3 p, int steps) {          float d=0.,m=0.;          for(int i=0;i<steps;i++){              vec2 t = map(p);              d += t.x;              m  = t.y;              p = ro + rd * d;              if(abs(t.x)<d*MIN_DIST||d>75.break;          }          return vec2(d,m);      }      float getDigits(vec2 nv,int dec) {          float d = 1e5;          if(dec == 0) d = get0(nv);          if(dec == 1) d = get1(nv);          if(dec == 2) d = get2(nv);          if(dec == 3) d = get3(nv);          if(dec == 4) d = get4(nv);          if(dec == 5) d = get5(nv);          if(dec == 6) d = get6(nv);          if(dec == 7) d = get7(nv);          if(dec == 8) d = get8(nv);          if(dec == 9) d = get9(nv);          return d;      }      void main( )      {          vec2 F = gl_FragCoord.xy;          // time precal          float idate = u_date;//debug var//T+3598.;//          int sec = int(mod(idate,60.));          int minute = int(mod(idate/60.,60.));          int hour = int(mod(idate/3600.,12.));          int ampm = int(mod(idate/3600.,24.));          // global digits          float num = float(hour);if(num == 0.) num = 12.;          h1 = floor(mod(num / pow(10.0,1.),10.0));          h2 = floor(mod(num / pow(10.0,0.),10.0));          num = float(minute);          m1 = floor(mod(num / pow(10.0,1.),10.0));          m2 = floor(mod(num / pow(10.0,0.),10.0));          num = float(sec);          s1 = floor(mod(num / pow(10.0,1.),10.0));          s2 = floor(mod(num / pow(10.0,0.),10.0));          // second dials          float t2 = lerp(0.,1.,mod(idate,1.));          s2 = s2+eic(t2);          float t1 = lerp(9.,10.,mod(idate,10.));          s1 = s1+eic(t1);          // minute dials          float t4 = lerp(59.,60.,mod(idate,60.));          m2 = m2+eic(t4);          float t3 = lerp(599.,600.,mod(idate,600.));          m1 = m1+eic(t3);          // hour dials          float t6 = lerp(3599.,3600.,mod(idate,3600.));          h2 = h2+eic(t6);          float t5 = lerp(3599.,3600.,mod(idate,3600.));          if(hour == 0) hour = 12;          if(hour == 9||hour>11) {              h1 = h1+eic(t5);          }          //          // uv ro + rd          vec2 uv = (2.* F.xy-R.xy)/max(R.x,R.y);          vec3 ro = vec3(.4,0,R.y<400.?6.:4.75);          vec3 rd = normalize(vec3(uv, -1.0));          // mouse //          float x = M.xy==vec2(0) ? 0. : (M.y/R.y*.2-.1)*PI;          float y = M.xy==vec2(0) ? 0. : (M.x/R.x*.2-.1)*PI;          mat2 rx = rot(x+.2*cos(T*.325)), ry = rot(y+.15*sin(T*.25));          ro.zy*=rx, ro.xz*=ry;          rd.zy*=rx, rd.xz*=ry;          vec3 C = vec3(0), p = ro;          vec2 ray = marcher(ro,rd,p,100);          float d = ray.x, m = ray.y;          hp = hit;          if(d<MAX_DIST) {              vec3 n = normal(p,d);              vec3 lpos = vec3(5,11,12);              vec3 l = normalize(lpos-p);              float diff = clamp(dot(n,l),.1,1.);              float shdw = 1.;              for( float t=.01;t<12.; ) {                  float h = map(p + l*t).x;                  if( h<MIN_DIST ) { shdw = 0.break; }                  shdw = min(shdw, 32.*h/t);                  t += h * .95;                  if( shdw<MIN_DIST || t>42. ) break;              }              diff = mix(diff,diff*shdw,.75);              float spec = .75 * pow(max(dot(normalize(p-ro),reflect(normalize(lpos),n)),0.),24.);              // base color change depending on time of day              vec3 h = ampm<12?vec3(1,.635,0):ampm>19?vec3(.337,.082,.718):vec3(.8);              if(m>0.) {                  vec2 uv = vec2(atan(hp.z,hp.y)/PI2,p.x);                  vec2 id = vec2(floor(uv.x*10.),1.);                  id.x = mod(abs(id.x+5.),10.);                  uv.x = fract(uv.x*10.)-.5;                  if(m==1.) {uv.y+=2.;id.x=mod(abs(id.x+6.),2.);}                  if(m==2.) uv.y+=1.25;                  if(m==3.) id.x=mod(abs(id.x+6.),6.);                  if(m==4.) uv.y-=.75;                  if(m==5.){uv.y-=2.;id.x=mod(abs(id.x+5.)+1.,6.);}                  if(m==6.) uv.y-=2.75;                  float px = 2./R.x;                  vec2 nv = vec2(uv.y,-uv.x)*1.25;                  float d = getDigits(nv,int(id.x));                  float c = length(uv)-.235;                  c=max(c,-d);                  // dial color change depending on time of day                  vec3 clr = ampm<12?vec3(1.,.35,0):ampm>19?vec3(.68,.14,.98):vec3(.1,.9,.3);                  vec3 clx = ampm<12?vec3(1.):ampm>19?vec3(0.6):vec3(.3);                  vec3 cld = ampm<12?vec3(.7,.3,.0):ampm>19?vec3(0.1):vec3(.6);                  h = mix(h,clx,smoothstep(px,-px,c));                  h = mix(h,clr,smoothstep(px,-px,d));                  c = length(vec2(abs(uv.x)-.5,uv.y))-.175;                  h = mix(h,cld,smoothstep(px,-px,c));              }              if(m==10.) {                  // pattern color change depending on time of day                  vec3 clr = ampm<12?vec3(.973,.89,.05):ampm>19?vec3(.235,.165,.75):vec3(.72,.93,.52);                  vec3 cld = ampm<12?vec3(.7,.3,.0):ampm>19?vec3(.001):vec3(.6);                  vec2 uv = vec2(atan(hp.z,hp.y)/PI2,p.x);                  uv.xy += T*vec2(-.02,1);                  vec2 sc = vec2(14.,3), id = floor(uv*sc);                  uv = fract(uv*sc)-.5;                  float px = 2./R.x, rnd = hash21(id);                  if(rnd>.5) uv.x*=-1.;                  float chk = mod(id.y + id.x,2.) * 2. - 1.;                  vec2 gx = length(uv-.5)<length(uv+.5)? vec2(uv-.5) : vec2(uv+.5);                  float tr = length(gx)-.5;                  if(ampm>19){                      tr= smoothstep(-px,px,abs(abs(tr)-.1)-.05);                  }else{                      tr= (chk>.5 ^^ rnd<.5) ? smoothstep(-px,px,tr) : smoothstep(px,-px,tr);                  }                  h = mix(clr, cld,tr);              }              C = h * diff;          }          // fog overlay - color change depending on time of day          vec3 clr = ampm<12?vec3(0.306,0.118,0.016):ampm>19?vec3(0.039,0.020,0.078):vec3(.25);          C = mix(C,clr, 1.-exp(-.00325*d*d*d));          // line fading          if((int(F.x)%6 == int(F.y)%6) && R.x>800. ) C = clr;          // static effect          C = mix(C,clamp(C*.8,vec3(0),vec3(1)),hash21(floor(uv.xy*30.)+uv));          C = pow(C, vec3(.4545));          fragColor = vec4(C,1);      }    </script>    <script src="https://fecoder-pic-1302080640.cos.ap-nanjing.myqcloud.com/twgl.min.js"></script>    <script>      function _defineProperty(obj, key, value) {        if (key in obj) {          Object.defineProperty(obj, key, {            value: value,            enumerabletrue,            configurabletrue,            writabletrue,          });        } else {          obj[key] = value;        }        return obj;      } // Mouse Class for movments and attaching to dom //      class Mouse {        constructor(element) {          _defineProperty(            this,            "reset",            () => {              this.x =                ~~(document.documentElement.clientWidth,                window.innerWidth || 0) / 2;              this.y =                ~~(document.documentElement.clientHeight,                window.innerHeight || 0) / 2;            }          );          this.element = element || window;          this.drag = false;          this.x =            ~~(document.documentElement.clientWidthwindow.innerWidth || 0) /            2;          this.y =            ~~(document.documentElement.clientHeightwindow.innerHeight || 0) /            2;          this.getCoordinates = this.getCoordinates.bind(this);          this.events = ["mouseenter""mousemove"];          this.events.forEach((eventName) => {            this.element.addEventListener(eventName, this.getCoordinates);          });          this.element.addEventListener("mousedown"() => {            this.drag = true;          });          this.element.addEventListener("mouseup"() => {            this.drag = false;          });          window.addEventListener("resize"this.reset);        }        getCoordinates(event) {          event.preventDefault();          if (this.drag) {            this.x = event.pageX;            this.y = event.pageY;          }        }      }      // WEBGL BOOTSTRAP TWGL.js      const glcanvas = document.getElementById("canvas");      const gl = glcanvas.getContext("webgl2");      // Fractal code in HTML window - Fragment Shader //      const programInfo = twgl.createProgramInfo(gl, [        "vertexShader",        "fragmentShader",      ]);      const arrays = {        position: [-1, -101, -10, -110, -1101, -10110],      };      const bufferInfo = twgl.createBufferInfoFromArrays(gl, arrays);      const mouse = new Mouse(glcanvas);      let umouse = [gl.canvas.width / 2, gl.canvas.height / 200];      let tmouse = umouse;      let uniforms;      const ct = new Date();      const timenow =        ct.getHours() * 60.0 * 60 +        ct.getMinutes() * 60 +        ct.getSeconds() +        ct.getMilliseconds() / 1000.0;      // RENDER LOOP      const render = (time) => {        twgl.resizeCanvasToDisplaySize(gl.canvas1.0);        gl.viewport(00, gl.canvas.width, gl.canvas.height);        const factor = 0.15;        umouse = [mouse.x, mouse.y0];        tmouse[0] = tmouse[0] - (tmouse[0] - umouse[0]) * factor;        tmouse[1] = tmouse[1] - (tmouse[1] - umouse[1]) * factor;        tmouse[2] = mouse.drag ? 1 : -1;        const mytime = time / 1000;        uniforms = {          u_time: mytime,          u_mouse: tmouse,          u_date: mytime + timenow,          u_resolution: [gl.canvas.width, gl.canvas.height],        };        gl.useProgram(programInfo.program);        twgl.setBuffersAndAttributes(gl, programInfo, bufferInfo);        twgl.setUniforms(programInfo, uniforms);        twgl.drawBufferInfo(gl, bufferInfo);        requestAnimationFrame(render);      };      // DOM READY      window.addEventListener("DOMContentLoaded"(event) => {        requestAnimationFrame(render);      });    </script>  </body></html>


此外,JavaScript 还负责 “舞台维护”:比如窗口大小变化时,自动调整画布尺寸;确保着色器代码正确运行,避免图形出错。就像一个全能导演,既管演员(时钟)的动作,又管舞台(画布)的适配。

从 HTML 搭建的 “舞台”,到 CSS 营造的 “氛围”,再到 JavaScript 驱动的 “动态与互动”,三个技术分工协作,把一串代码变成了一个会呼吸、会互动、有时间感的 3D 时钟。当你看着它随时间缓缓转动,随鼠标轻轻倾斜,甚至能通过颜色变化感知昼夜时,很难不感叹:技术与艺术的结合,原来可以这么迷人。

这或许就是 WebGL 的魅力 —— 用代码在二维的屏幕上,创造出仿佛能触摸的三维世界,而网页三巨头,就是实现这场魔术的关键道具。





点分享
点收藏
点在看
点点赞

【声明】内容源于网络
0
0
码途钥匙
欢迎来到 Python 学习乐园!这里充满活力,分享前沿实用知识技术。新手或开发者,都能找到价值。一起在这个平台,以 Python 为引,开启成长之旅,探索代码世界,共同进步。携手 Python,共赴精彩未来,快来加入我们吧!
内容 992
粉丝 0
码途钥匙 欢迎来到 Python 学习乐园!这里充满活力,分享前沿实用知识技术。新手或开发者,都能找到价值。一起在这个平台,以 Python 为引,开启成长之旅,探索代码世界,共同进步。携手 Python,共赴精彩未来,快来加入我们吧!
总阅读285
粉丝0
内容992