WebGL利用FBO完成立方体贴图效果完整实例(附demo源码下载)


Posted in Javascript onJanuary 26, 2016

本文实例讲述了WebGL利用FBO完成立方体贴图效果的方法。分享给大家供大家参考,具体如下:

这篇主要记录WebGL的一些基本要点,顺便也学习下如何使用FBO与环境贴图。先看下效果图(需要支持WebGL,Chrome,火狐,IE11)。

WebGL利用FBO完成立方体贴图效果完整实例(附demo源码下载)

主要实现过程如下,先用FBO输出当前环境在立方体纹理中,再画出当前立方体,最后画球,并且把FBO关联的纹理贴在这个球面上。

开始WebGL时,最好有些OpenGL基础,在前面讲Obj完善与MD2时,大家可能已经发现了,因为着色器的添加使用,原来一些Opengl大部分API已经没有使用。WebGL就和这差不多,大部分功能是着色器完成主要功能,记录下主要过程,大家可以比较下前面的,看看是不是很像,为了熟悉WebGL基本功能,本文没有利用比较完善的框架,只是用到一个帮助计算矩阵的框架(gl-matrix.js).

和使用OpenGL一样,我们要初始化使用环境,提取一些全局使用量。相关代码如下:

初始化:

var gl;//WebGLRenderingContext
var cubeVBO;//Cube buffer ID
var sphereVBO;//球体VBO
var sphereEBO;//球体EBO
var cubeTexID;//立方体纹理ID
var fboBuffer;//桢缓存对象
var glCubeProgram;//立方体着色器应用
var glSphereProgram;//球体着色器应用
var fboWidth = 512;//桢缓存绑定纹理宽度
var fboHeight = 512;//桢缓存绑定纹理高度
var targets;//立方体贴图六个方向
var pMatrix = mat4.create();//透视矩阵
var vMatrix = mat4.create();//视图矩阵
var eyePos = vec3.fromValues(0.0, 1.0, 0.0);//眼睛位置
var eyeLookat = vec3.fromValues(0.0, -0.0, 0.0);//眼睛方向
var spherePos = vec3.fromValues(0.0, -0.0, 0.0);//球体位置
var canvanName;
function webGLStart(cName) {
  canvanName = cName;
  InitWebGL();
  InitCubeShader();
  InitSphereShader();
  InitCubeBuffer();
  InitSphereBuffer();
  InitFBOCube();
  //RenderFBO();
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.enable(gl.DEPTH_TEST);
  tick();
}
function InitWebGL() {
  //var canvas = document.getElementById(canvanName);
  InitGL(canvanName);
}
function InitGL(canvas) {
  try {
    //WebGLRenderingContext 
    gl = canvas.getContext("experimental-webgl");
    gl.viewportWidth = canvas.width;
    gl.viewportHeight = canvas.height;
    targets = [gl.TEXTURE_CUBE_MAP_POSITIVE_X,
           gl.TEXTURE_CUBE_MAP_NEGATIVE_X,
           gl.TEXTURE_CUBE_MAP_POSITIVE_Y,
           gl.TEXTURE_CUBE_MAP_NEGATIVE_Y,
           gl.TEXTURE_CUBE_MAP_POSITIVE_Z,
           gl.TEXTURE_CUBE_MAP_NEGATIVE_Z];
  } catch (e) { }
  if (!gl) { alert("你的浏览器不支持WebGL"); }
}

在这里,我们初始化在网页中WebGL的上下方环境,并给出一系列初始化过程。下面先给出房间,也就是其中立方体的相关代码。

立方体:

function InitCubeShader() {
  //WebGLShader
  var shader_vertex = GetShader("cubeshader-vs");
  var shader_fragment = GetShader("cubeshader-fs");
  //WebglCubeProgram
  glCubeProgram = gl.createProgram();
  gl.attachShader(glCubeProgram, shader_vertex);
  gl.attachShader(glCubeProgram, shader_fragment);
  gl.linkProgram(glCubeProgram);
  if (!gl.getProgramParameter(glCubeProgram, gl.LINK_STATUS)) {
    alert("Shader hava error.");
  }
  gl.useProgram(glCubeProgram);
  glCubeProgram.positionAttribute = gl.getAttribLocation(glCubeProgram, "a_position");
  glCubeProgram.normalAttribute = gl.getAttribLocation(glCubeProgram, "a_normal");
  glCubeProgram.texCoordAttribute = gl.getAttribLocation(glCubeProgram, "a_texCoord");
  glCubeProgram.view = gl.getUniformLocation(glCubeProgram, "view");
  glCubeProgram.perspective = gl.getUniformLocation(glCubeProgram, "perspective");
}
function InitCubeBuffer() {
  var cubeData = [
      -10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 0.0,
      -10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 1.0,
      10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 1.0,
      10.0, 10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 1.0,
      10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 0.0, 0.0,
      -10.0, -10.0, -10.0, 0.0, 0.0, -10.0, 1.0, 0.0,
      -10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 0.0,
      10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 0.0,
      10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 1.0,
      10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 1.0, 1.0,
      -10.0, 10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 1.0,
      -10.0, -10.0, 10.0, 0.0, 0.0, 10.0, 0.0, 0.0,
      -10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 0.0, 0.0,
      10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 1.0, 0.0,
      10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 1.0, 1.0,
      10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 1.0, 1.0,
      -10.0, -10.0, 10.0, 0.0, -10.0, 0.0, 0.0, 1.0,
      -10.0, -10.0, -10.0, 0.0, -10.0, 0.0, 0.0, 0.0,
      10.0, -10.0, -10.0, 10.0, 0.0, 0.0, 0.0, 0.0,
      10.0, 10.0, -10.0, 10.0, 0.0, 0.0, 1.0, 0.0,
      10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 1.0, 1.0,
      10.0, 10.0, 10.0, 10.0, 0.0, 0.0, 1.0, 1.0,
      10.0, -10.0, 10.0, 10.0, 0.0, 0.0, 0.0, 1.0,
      10.0, -10.0, -10.0, 10.0, 0.0, 0.0, 0.0, 0.0,
      10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 0.0, 0.0,
      -10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 1.0, 0.0,
      -10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 1.0, 1.0,
      -10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 1.0, 1.0,
      10.0, 10.0, 10.0, 0.0, 10.0, 0.0, 0.0, 1.0,
      10.0, 10.0, -10.0, 0.0, 10.0, 0.0, 0.0, 0.0,
      -10.0, 10.0, -10.0, -10.0, 0.0, 0.0, 0.0, 0.0,
      -10.0, -10.0, -10.0, -10.0, 0.0, 0.0, 1.0, 0.0,
      -10.0, -10.0, 10.0, -10.0, 0.0, 0.0, 1.0, 1.0,
      -10.0, -10.0, 10.0, -10.0, 0.0, 0.0, 1.0, 1.0,
      -10.0, 10.0, 10.0, -10.0, 0.0, 0.0, 0.0, 1.0,
      -10.0, 10.0, -10.0, -10.0, 0.0, 0.0, 0.0, 0.0,
  ];
  cubeVBO = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, cubeVBO);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(cubeData), gl.STATIC_DRAW);
}
function RenderCube() {
  gl.useProgram(glCubeProgram);
  gl.bindBuffer(gl.ARRAY_BUFFER, cubeVBO);
  gl.vertexAttribPointer(glCubeProgram.positionAttribute, 3, gl.FLOAT, false, 32, 0);
  gl.enableVertexAttribArray(glCubeProgram.positionAttribute);
  gl.vertexAttribPointer(glCubeProgram.normalAttribute, 3, gl.FLOAT, false, 32, 12);
  gl.enableVertexAttribArray(glCubeProgram.normalAttribute);
  gl.vertexAttribPointer(glCubeProgram.texCoordAttribute, 2, gl.FLOAT, false, 32, 24);
  gl.enableVertexAttribArray(glCubeProgram.texCoordAttribute);
  gl.uniformMatrix4fv(glCubeProgram.view, false, vMatrix);
  gl.uniformMatrix4fv(glCubeProgram.perspective, false, pMatrix);
  gl.drawArrays(gl.TRIANGLES, 0, 36);
}

上面的代码主要分为初始化立方体的着色器对象,初始化相关缓存,然后绘制立方体,可以说在Opengl中,如果用着色器来画,过程也是差不多的,在Opengl里,已经没有固定管线的一些功能如InterleavedArrays来指定是顶点还是法线或是纹理了,统一用vertexAttribPointer来传递应用程序与着色器之间的数据。在前面 MD2桢动画实现里面后面的参数传递改进版也有相关应用。

相应着立方体着色器主要代码如下:

立方体着色器实现:

<script id="cubeshader-fs" type="x-shader/x-fragment">
    precision mediump float;
    varying vec3 normal;
    varying vec3 tex1;
    varying vec3 tex2;
    void main( void )
    {
    float x = tex1.x * 6.28 * 8.0; //2兀 * 8
    float y = tex1.y * 6.28 * 8.0; //2兀 * 8
    //cos(x)= 8个 (1 -1 1)
    gl_FragColor = vec4(tex2,1.0) * vec4(sign(cos(x)+cos(y))); //
    //gl_FragColor = vec4(normal*vec3(0.5)+vec3(0.5), 1);
    }
</script>
<script id="cubeshader-vs" type="x-shader/x-vertex">
    attribute vec3 a_position;
    attribute vec3 a_normal;
    attribute vec2 a_texCoord;
    uniform mat4 view;
    uniform mat4 perspective;
    varying vec3 normal;
    varying vec3 tex1;
    varying vec3 tex2;
    void main( void )
    {
    gl_Position = perspective * view * vec4(a_position,1.0);
    normal = a_normal;
    tex1 = vec3(a_texCoord,0.0);
    tex2 = normalize(a_position)*0.5+0.5;
    }
</script>

着色器中,已经没有ftransform()功能可供调用,要自己传递如模型,视图,透视矩阵,在这里,模型是以原点为中心来绘画,意思模型视图矩阵也就是视图矩阵,所以屏幕位置的计算只需要视图和透视矩阵。在片断着色器中,x,y是从顶点着色器中的纹理坐标传递过来,相应过程6.28*8.0,相当于8个360度,用于控制立方体上的方块显示,而tex2是着色器中的顶点映射[0,1]的值,分别给立方体的六面分别设置不同的意思,然后用二个矢量的乘积来混合这二种颜色显示,gl_FragColor = vec4(tex2,1.0) * vec4(sign(cos(x)+cos(y)))。

在显示球体之前,应该先生成当前环境的立方体绘图,在这里使用FBO,先生成桢缓存和立方体绘理,并关联,然后以原点为中心,分别向上下左右前右绘图,然后利用桢缓冲分别输出到立方体上的六个面,主要代码如下:

FBO与立方体纹理:

function InitFBOCube() {
  // WebGLFramebuffer
  fboBuffer = gl.createFramebuffer();
  gl.bindFramebuffer(gl.FRAMEBUFFER, fboBuffer);
  fboBuffer.width = 512;
  fboBuffer.height = 512;
  cubeTexID = gl.createTexture();
  gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTexID);
  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
  gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
  for (var i = 0; i < targets.length; i++) {
    gl.texImage2D(targets[i], 0, gl.RGBA, fboBuffer.width, fboBuffer.height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
  }
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
}
function RenderFBO() {
  gl.disable(gl.DEPTH_TEST);
  gl.viewport(0, 0, fboBuffer.width, fboBuffer.height);
  gl.clearColor(0.0, 0.0, 0.0, 1.0);
  gl.bindFramebuffer(gl.FRAMEBUFFER, fboBuffer);
  for (var i = 0; i < targets.length; i++) {
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, targets[i], cubeTexID, null);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  }
  mat4.perspective(pMatrix, 45, fboBuffer.width / fboBuffer.height, 0.1, 100.0);
  for (var i = 0; i < targets.length; i++) {
    gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, targets[i], cubeTexID, null);
    var lookat = vec3.create();
    var up = vec3.create();
    up[1] = 1.0;
    if (i == 0) {
      lookat[0] = -1.0;
    } else if (i == 1) {
      lookat[0] = 1.0;      
    } else if (i == 2) {
      lookat[1] = -1.0;
      up[0] = 1.0;
    } else if (i == 3) {
      lookat[1] = 1.0;
      up[0] = 1.0;
    } else if (i == 4) {
      lookat[2] == -1.0;      
    } else if (i == 5) {
      lookat[2] = 1.0;      
    } else {     
    }
    //vec3.fromValues(0.0, 0.0, 0.0)
    vMatrix = mat4.create();
    mat4.lookAt(vMatrix, vec3.fromValues(0.0, 0.0, 0.0), lookat, up);
    //mat4.scale(vMatrix, vMatrix, vec3.fromValues(-1.0, -1.0, -1.0));
    //mat4.translate(vMatrix, vMatrix, spherePos);    
    RenderCube();
  }
  gl.bindFramebuffer(gl.FRAMEBUFFER, null);
  gl.enable(gl.DEPTH_TEST);
}

在上面不知是gl-matrix提供的矩阵算法有问题,还是本来应该这样,在上下面的时候生成的纹理图不对,需要偏转摄像机的向上矢量。因为这是摄像机位置与目标的生成的Z轴和设定的UP轴平行了,这样导致不能正确计算X轴,然后对应的UP轴也计算不出来,相应视图矩阵出现错误。

最后是球体的绘画,代码主要和立方体的差不多,注意球体的顶点算法。

球体:

function InitSphereShader() {
  //WebGLShader
  var shader_vertex = GetShader("sphereshader-vs");
  var shader_fragment = GetShader("sphereshader-fs");
  //WebglCubeProgram
  glSphereProgram = gl.createProgram();
  gl.attachShader(glSphereProgram, shader_vertex);
  gl.attachShader(glSphereProgram, shader_fragment);
  gl.linkProgram(glSphereProgram);
  if (!gl.getProgramParameter(glSphereProgram, gl.LINK_STATUS)) {
    alert("Shader hava error.");
  }
  glSphereProgram.positionAttribute = gl.getAttribLocation(glSphereProgram, "a_position");
  glSphereProgram.normalAttribute = gl.getAttribLocation(glSphereProgram, "a_normal");
  glSphereProgram.eye = gl.getUniformLocation(glSphereProgram, "eye");
  glSphereProgram.mapCube = gl.getUniformLocation(glSphereProgram, "mapCube");
  glSphereProgram.model = gl.getUniformLocation(glSphereProgram, "model");
  glSphereProgram.view = gl.getUniformLocation(glSphereProgram, "view");
  glSphereProgram.perspective = gl.getUniformLocation(glSphereProgram, "perspective");
}
function InitSphereBuffer() {
  var radius = 1;
  var segments = 16;
  var rings = 16;
  var length = segments * rings * 6;
  var sphereData = new Array();
  var sphereIndex = new Array();
  for (var y = 0; y < rings; y++) {
    var phi = (y / (rings - 1)) * Math.PI;
    for (var x = 0; x < segments; x++) {
      var theta = (x / (segments - 1)) * 2 * Math.PI;
      sphereData.push(radius * Math.sin(phi) * Math.cos(theta));
      sphereData.push(radius * Math.cos(phi));
      sphereData.push(radius * Math.sin(phi) * Math.sin(theta));
      sphereData.push(Math.sin(phi) * Math.cos(theta));
      sphereData.push(radius * Math.cos(phi))
      sphereData.push(Math.sin(phi) * Math.sin(theta));
    }
  }
  for (var y = 0; y < rings - 1; y++) {
    for (var x = 0; x < segments - 1; x++) {
      sphereIndex.push((y + 0) * segments + x);
      sphereIndex.push((y + 1) * segments + x);
      sphereIndex.push((y + 1) * segments + x + 1);
      sphereIndex.push((y + 1) * segments + x + 1);
      sphereIndex.push((y + 0) * segments + x + 1)
      sphereIndex.push((y + 0) * segments + x);
    }
  }
  sphereVBO = gl.createBuffer();
  gl.bindBuffer(gl.ARRAY_BUFFER, sphereVBO);
  gl.bufferData(gl.ARRAY_BUFFER, new Float32Array(sphereData), gl.STATIC_DRAW);
  sphereVBO.numItems = segments * rings;
  sphereEBO = gl.createBuffer();
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereEBO);
  gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, new Uint16Array(sphereIndex), gl.STATIC_DRAW);
  sphereEBO.numItems = sphereIndex.length;
}
function RenderSphere() {
  gl.useProgram(glSphereProgram);
  gl.bindBuffer(gl.ARRAY_BUFFER, sphereVBO);
  gl.vertexAttribPointer(glSphereProgram.positionAttribute, 3, gl.FLOAT, false, 24, 0);
  gl.enableVertexAttribArray(glSphereProgram.positionAttribute);
  gl.vertexAttribPointer(glSphereProgram.normalAttribute, 3, gl.FLOAT, false, 24, 12);
  gl.enableVertexAttribArray(glSphereProgram.normalAttribute);
  var mMatrix = mat4.create();
  mat4.translate(mMatrix, mMatrix, spherePos);
  gl.uniform3f(glSphereProgram.eye, eyePos[0],eyePos[1],eyePos[2]);
  gl.uniformMatrix4fv(glSphereProgram.model, false, mMatrix);
  gl.uniformMatrix4fv(glSphereProgram.view, false, vMatrix);
  gl.uniformMatrix4fv(glSphereProgram.perspective, false, pMatrix);
  gl.activeTexture(gl.TEXTURE0);
  gl.bindTexture(gl.TEXTURE_CUBE_MAP, cubeTexID);
  //gl.uniformMatrix4fv(glSphereProgram.mapCube, 0);
  gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, sphereEBO);
  gl.drawElements(gl.TRIANGLES, sphereEBO.numItems, gl.UNSIGNED_SHORT, 0);
  gl.bindTexture(gl.TEXTURE_2D, null);
}

可以看到,也是和立方体一样的三步,初始化着色器,初始化顶点与法线,绘画。下面给出着色器代码:

球体着色器:

<script id="sphereshader-fs" type="x-shader/x-fragment">
    precision mediump float;
    varying vec3 normal;
    varying vec3 eyevec;
    uniform samplerCube mapCube;
    void main( void )
    {
    gl_FragColor = textureCube(mapCube, reflect(normalize(-eyevec), normalize(normal)));
    }
</script>
<script id="sphereshader-vs" type="x-shader/x-vertex">
    attribute vec3 a_position;
    attribute vec3 a_normal;
    uniform mat4 model;
    uniform mat4 view;
    uniform mat4 perspective;
    uniform vec3 eye;
    varying vec3 normal;
    varying vec3 eyevec;
    void main( void )
    {
    gl_Position = perspective * view * model * vec4(a_position,1.0);
    eyevec = -eye;// a_position.xyz;
    normal = a_normal;
    }
</script>

和前面立方体有点不同的是,球体有自己的模型矩阵,这也是一般正常的用法,然后传递眼睛对应球体顶点矢量与法线传递在片断着色器中,在片断着色器中,就有用到前面所生成的立方体纹理,我们根据眼睛经过顶点通过对应法向量反射到立体体纹理上的点来获取当前球体所对应的环境颜色,在这里,我们可以直接调用textureCube来完成上面所说的过程,不需要我们手动来计算。

其中GetShader函数的使用,参照了http://msdn.microsoft.com/zh-TW/library/ie/dn302360(v=vs.85) 这里的讲解。

可以说,上面主要的绘制函数已经完成,但是我们这个是能动的,所以需要模拟如客户端环境每隔多久绘制一次,主要代码如下:

动画:

function tick() {
  Update();
  OnDraw();
  setTimeout(function () { tick() }, 15);
}
function OnDraw() {
  //fbo rander CUBE_MAP
  RenderFBO();
  //element rander
  gl.viewport(0, 0, gl.viewportWidth, gl.viewportHeight);
  gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
  mat4.perspective(pMatrix, 45, gl.viewportWidth / gl.viewportHeight, 0.1, 200.0);
  mat4.lookAt(vMatrix, eyePos, eyeLookat, vec3.fromValues(0.0, 1.0, 0.0));
  RenderCube();
  RenderSphere();
}
var lastTime = new Date().getTime();
function Update() {
  var timeNow = new Date().getTime();
  if (lastTime != 0) {
    var elapsed = timeNow - lastTime;
    //3000控制人眼的旋转速度。8控制人眼的远近
    eyePos[0] = Math.cos(elapsed / 3000) * 8;
    eyePos[2] = Math.sin(elapsed / 2000) * 8;
    spherePos[0] = Math.cos(elapsed / 4000) * 3;
    spherePos[2] = Math.cos(elapsed / 4000) * 3;
  }
}

在上面,每隔15毫秒调用一次Update与Draw函数,其中Update用于更新眼睛与球体位置,Draw绘画。

完整实例代码点击此处本站下载。

希望本文所述对大家JavaScript程序设计有所帮助。

Javascript 相关文章推荐
firefox插件Firebug的使用教程
Jan 02 Javascript
jQuery学习7 操作JavaScript对象和集合的函数
Feb 07 Javascript
js 异步处理进度条
Apr 01 Javascript
firefox火狐浏览器与与ie兼容的2个问题总结
Jul 20 Javascript
jquery插件开发之实现jquery手风琴功能分享
Mar 10 Javascript
Javascript中关于Array.filter()的妙用详解
Dec 04 Javascript
Google 爬虫如何抓取 JavaScript 的内容
Apr 07 Javascript
前端主流框架vue学习笔记第二篇
Jul 26 Javascript
JavaScript实现树的遍历算法示例【广度优先与深度优先】
Oct 26 Javascript
微信小程序批量监听输入框对按钮样式进行控制的实现代码
Oct 12 Javascript
在vue中使用防抖和节流,防止重复点击或重复上拉加载实例
Nov 13 Javascript
Windows下安装 node 的版本控制工具 nvm
Feb 06 Javascript
JS组件中bootstrap multiselect两大组件较量
Jan 26 #Javascript
JS组件Form表单验证神器BootstrapValidator
Jan 26 #Javascript
jQuery实现的指纹扫描效果实例(附演示与demo源码下载)
Jan 26 #Javascript
Bootstrap树形组件jqTree的简单封装
Jan 25 #Javascript
javascript实现2016新年版日历
Jan 25 #Javascript
基于javascript实现图片左右切换效果
Jan 25 #Javascript
JavaScript实现获取某个元素相邻兄弟节点的prev与next方法
Jan 25 #Javascript
You might like
php获取百度收录、百度热词及百度快照的方法
2015/04/02 PHP
PHP实现的统计数据功能详解
2016/12/06 PHP
php+redis在实际项目中HTTP 500: Internal Server Error故障排除
2017/02/05 PHP
php正则判断是否为合法身份证号的方法
2017/03/16 PHP
PHP实现的数组和XML文件相互转换功能示例
2018/03/15 PHP
关于js拖拽上传 [一个拖拽上传修改头像的流程]
2011/07/13 Javascript
用原生JavaScript实现jQuery的$.getJSON的解决方法
2013/05/03 Javascript
使用 Node.js 做 Function Test实现方法
2013/10/25 Javascript
JavaScript中发布/订阅模式的简单实例
2014/11/05 Javascript
JS实现选中当前菜单后高亮显示的导航条效果
2015/10/15 Javascript
js制作支付倒计时页面
2016/10/21 Javascript
js基于myFocus实现轮播图效果
2017/02/14 Javascript
vue mint-ui学习笔记之picker的使用
2017/10/11 Javascript
JS实现为动态添加的元素增加事件功能示例【基于事件委托】
2018/03/21 Javascript
微信小程序实现下滑到底部自动翻页功能
2020/03/07 Javascript
在vue-cli3.0 中使用预处理器 (Sass/Less/Stylus) 配置全局变量操作
2020/08/10 Javascript
浅谈Python的Django框架中的缓存控制
2015/07/24 Python
使用Python制作自动推送微信消息提醒的备忘录功能
2018/09/06 Python
python plotly画柱状图代码实例
2019/12/13 Python
使用pickle存储数据dump 和 load实例讲解
2019/12/30 Python
Python单元测试模块doctest的具体使用
2020/02/10 Python
keras中的History对象用法
2020/06/19 Python
英国度假别墅预订:Sykes Cottages
2017/06/12 全球购物
英国网络托管和域名领导者:Web Hosting UK
2017/10/15 全球购物
英国手机壳购买网站:Case Hut
2019/04/11 全球购物
一个精品风格的世界:Atterley
2019/05/01 全球购物
Furla官网:意大利著名的皮革品牌
2019/08/06 全球购物
一些关于MySql加速和优化的面试题
2014/01/30 面试题
小型女装店的创业计划书
2014/01/09 职场文书
八一演出活动方案
2014/02/03 职场文书
给校长的建议书500字
2014/05/15 职场文书
希特勒的演讲稿
2014/05/23 职场文书
个人主要事迹材料
2014/08/26 职场文书
matplotlib如何设置坐标轴刻度的个数及标签的方法总结
2021/06/11 Python
MySQL8.0.18配置多主一从
2021/06/21 MySQL
Redis 中使用 list,streams,pub/sub 几种方式实现消息队列的问题
2022/03/16 Redis