当前位置: 首页 > news >正文

Vue3 + threeJs 定义六种banner轮播图切换动画效果:百叶窗、手风琴、拼图、渐变、菱形波次、圆形扩展

最近写了一个案例。用来丰富banner图的切换效果,css不是特别完美,所以采用的事threeJs的方式:
vue3 + threeJS
动画效果有:百叶窗、手风琴、拼图、渐变、菱形波次、圆形扩展动画:
以下是动画效果视频:

threeJS写banner切换效果(六个动画效果)

代码中已经做好了优化:
(1)窗口大小调整时更新渲染器和相机;
(2)浏览器切换到其他网页时,关闭定时器;浏览器切换回当前网页时,重新开始轮播。
(3)切换路由或组件卸载时调用,确保轮播定时器被清理。
(4)渲染采用ShaderMaterial材质:可以用于全屏显示图片,适用于轮播图、背景等场景,显示效果非常锐利、高清
(5)代码中注释清晰

  • 整体逻辑
    1. 初始化场景、相机、渲染器。
    1. 加载三张图片纹理,并设置纹理参数。
    1. 定义六种动画效果:百叶窗、手风琴、拼图、渐变、菱形波次、圆形扩展。
    1. 使用 setTimeout 控制每个效果的切换时间。
    1. 使用 effectIndex 来控制当前使用的特效。
    1. 使用 currentIndex 来控制当前显示的图片索引。
    1. 使用 isFirstLoad 来判断是否是第一次加载,如果是,则直接显示第一张图片。
    1. 每次切换效果时,先加载下一张图片的纹理,然后根据当前效果索引调用对应的动画函数。
    1. 每个效果动画完成后,更新 currentFullMesh,并在最后一个效果后延时8000毫秒再开始下一轮。
    1. 使用 requestAnimationFrame 实现渲染循环。
    1. 使用 THREE.js 的 PlaneGeometry 和 ShaderMaterial 实现全屏高清效果。
    1. 使用 THREE.js 的 TextureLoader 加载图片纹理,并设置纹理的编码、包裹方式和过滤方式。
    1. 使用 THREE.js 的 PerspectiveCamera 实现透视相机。
    1. 使用 THREE.js 的 WebGLRenderer 实现渲染器,并设置输出编码和色调映射。

以下是完整代码:

<template><div><div class="banner_box" ref="bannerBox"></div></div>
</template><script setup>
import { ref, onMounted, onUnmounted } from "vue";
import * as THREE from "three";
import bannerImage1 from "../src/assets/img/banner1.png";
import bannerImage2 from "../src/assets/img/banner2.png";
import bannerImage3 from "../src/assets/img/banner3.png";const bannerBox = ref(null);/*** 工具函数:全屏高清ShaderMaterial,支持透明度渐变:* * 创建一个全屏的ShaderMaterial,用于显示纹理。* * 该材质支持透明度渐变,可以通过 uniforms 控制纹理和透明度。* * 使用 THREE.js 的 PlaneGeometry 实现全屏效果。* * 使用顶点着色器和片段着色器实现纹理的显示和透明度控制。
ShaderMaterial 默认会用纹理的原始分辨率和采样方式,且不会自动做 mipmap 或滤波降级,显示效果非常锐利、高清。
相较于MeshBasicMaterial,ShaderMaterial 提供了更高的灵活性和控制力,
MeshBasicMaterial会出现模糊或锯齿现象,尤其是在全屏显示时。* * 该ShaderMaterial材质可以用于全屏显示图片,适用于轮播图、背景等场景。* */
function createFullScreenShaderMaterial(texture, opacity = 1.0) {return new THREE.ShaderMaterial({uniforms: {uTexture: { value: texture },uOpacity: { value: opacity },},vertexShader: `varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);}`,fragmentShader: `uniform sampler2D uTexture;uniform float uOpacity;varying vec2 vUv;void main() {vec4 color = texture2D(uTexture, vUv);color.a *= uOpacity;gl_FragColor = color;}`,transparent: true,});
}
// 定义轮播相关变量
let slideshowTimeout = null;
let animationFrameId = null;
onMounted(() => {/*** 整体逻辑:* 1. 初始化场景、相机、渲染器。* 2. 加载三张图片纹理,并设置纹理参数。* 3. 定义六种动画效果:百叶窗、手风琴、拼图、渐变、菱形波次、圆形扩展。* 4. 使用 setTimeout 控制每个效果的切换时间。* 5. 使用 effectIndex 来控制当前使用的特效。* 6. 使用 currentIndex 来控制当前显示的图片索引。* 7. 使用 isFirstLoad 来判断是否是第一次加载,如果是,则直接显示第一张图片。* 8. 每次切换效果时,先加载下一张图片的纹理,然后根据当前效果索引调用对应的动画函数。* 9. 每个效果动画完成后,更新 currentFullMesh,并在最后一个效果后延时8000毫秒再开始下一轮。* 10. 使用 requestAnimationFrame 实现渲染循环。* 11. 使用 THREE.js 的 PlaneGeometry 和 ShaderMaterial 实现全屏高清效果。* 12. 使用 THREE.js 的 TextureLoader 加载图片纹理,并设置纹理的编码、包裹方式和过滤方式。* 13. 使用 THREE.js 的 PerspectiveCamera 实现透视相机。* 14. 使用 THREE.js 的 WebGLRenderer 实现渲染器,并设置输出编码和色调映射。* */// 初始化场景、相机、渲染器const scene = new THREE.Scene();// 获取 bannerBox 的宽高const bannerWidth = bannerBox.value.clientWidth;const bannerHeight = bannerBox.value.clientHeight;// 初始化相机// 使用透视相机,视野角度为75度,宽高比为 bannerWidth / bannerHeight,近裁剪面为0.1,远裁剪面为1000const camera = new THREE.PerspectiveCamera(75,bannerWidth / bannerHeight,0.1,1000);// 设置相机位置,使其能够完整看到整个 banner// 相机位置设置为 bannerHeight / (2 * Math.tan((camera.fov * Math.PI) / 360))camera.position.z =bannerHeight / (2 * Math.tan((camera.fov * Math.PI) / 360));// 设置渲染器const renderer = new THREE.WebGLRenderer();renderer.setSize(bannerWidth, bannerHeight);renderer.outputEncoding = THREE.sRGBEncoding;renderer.toneMapping = THREE.ACESFilmicToneMapping;renderer.toneMappingExposure = 0.9;bannerBox.value.appendChild(renderer.domElement);// 加载图片纹理const textureLoader = new THREE.TextureLoader();const textures = [textureLoader.load(bannerImage1),textureLoader.load(bannerImage2),textureLoader.load(bannerImage3),];textures.forEach((texture) => {texture.encoding = THREE.sRGBEncoding;texture.wrapS = THREE.ClampToEdgeWrapping;texture.wrapT = THREE.ClampToEdgeWrapping;texture.minFilter = THREE.LinearFilter;});let currentFullMesh = null;/*** 百叶窗动画:* 效果:将整个纹理分割成 5 个水平的百叶窗片段,* 每个片段从上到下依次展开,形成百叶窗效果。* */function animateBlinds(nextTexture, onComplete) {const blinds = [];const blindsCount = 5;const blindHeight = bannerHeight / blindsCount;for (let i = 0; i < blindsCount; i++) {const geometry = new THREE.PlaneGeometry(bannerWidth, blindHeight);const material = createFullScreenShaderMaterial(textures[currentIndex],0);material.transparent = true;// 设置材质的 uniformsconst uvOffsetY = 1 - (i + 1) / blindsCount;const uvOffsetHeight = 1 / blindsCount;const uvArray = geometry.attributes.uv.array;uvArray[1] = uvOffsetY + uvOffsetHeight;uvArray[3] = uvOffsetY + uvOffsetHeight;uvArray[5] = uvOffsetY;uvArray[7] = uvOffsetY;geometry.attributes.uv.needsUpdate = true;// 创建材质const mesh = new THREE.Mesh(geometry, material);mesh.position.y = bannerHeight / 2 - blindHeight / 2 - i * blindHeight;scene.add(mesh);blinds.push(mesh);}// 设置每个百叶窗片段的纹理和透明度blinds.forEach((blind, index) => {setTimeout(() => {blind.material.uniforms.uTexture.value = nextTexture;blind.material.needsUpdate = true;let progress = 0;const duration = 500;function fadeIn() {progress += 16;const t = Math.min(progress / duration, 1);blind.material.uniforms.uOpacity.value = t;if (t < 1) {requestAnimationFrame(fadeIn);}}fadeIn();}, index * 300);});// 所有百叶窗片段动画完成后,移除它们并显示下一张图片setTimeout(() => {blinds.forEach((mesh) => scene.remove(mesh));if (currentFullMesh) scene.remove(currentFullMesh);const geometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const material = createFullScreenShaderMaterial(nextTexture, 1);const mesh = new THREE.Mesh(geometry, material);scene.add(mesh);currentFullMesh = mesh;}, blindsCount * 300 + 1000);}/*** 拼图碎片动画:* 效果:将整个纹理分割成 5x5 的拼图碎片,动画整体实现从左上角到右下角的拼图效果,* */function createPuzzleEffect(nextTexture) {const rows = 5;const cols = 5;const pieceWidth = bannerWidth / cols;const pieceHeight = bannerHeight / rows;const pieces = [];const tempGroup = new THREE.Group();scene.add(tempGroup);for (let col = 0; col < cols; col++) {for (let row = 0; row < rows; row++) {// 创建每个拼图碎片的几何体和材质// 使用 PlaneGeometry 创建平面几何体const geometry = new THREE.PlaneGeometry(pieceWidth, pieceHeight);const material = createFullScreenShaderMaterial(nextTexture, 0);// 设置材质的 uniformsconst uvOffsetX = col / cols;const uvOffsetY = 1 - (row + 1) / rows;const uvOffsetWidth = 1 / cols;const uvOffsetHeight = 1 / rows;const uvArray = geometry.attributes.uv.array;uvArray[0] = uvOffsetX;uvArray[1] = uvOffsetY + uvOffsetHeight;uvArray[2] = uvOffsetX + uvOffsetWidth;uvArray[3] = uvOffsetY + uvOffsetHeight;uvArray[4] = uvOffsetX;uvArray[5] = uvOffsetY;uvArray[6] = uvOffsetX + uvOffsetWidth;uvArray[7] = uvOffsetY;geometry.attributes.uv.needsUpdate = true;// 创建每个拼图碎片的网格const mesh = new THREE.Mesh(geometry, material);mesh.position.x = col * pieceWidth - bannerWidth / 2 + pieceWidth / 2;mesh.position.y =bannerHeight / 2 - row * pieceHeight - pieceHeight / 2;tempGroup.add(mesh);pieces.push({ mesh, col, row });}}// 计算最大对角线长度const maxDiagonal = rows + cols - 1;for (let d = 0; d < maxDiagonal; d++) {setTimeout(() => {pieces.forEach(({ mesh, col, row }) => {if (col + row === d) {let progress = 0;const duration = 500;function fadeIn() {progress += 16;const t = Math.min(progress / duration, 1);mesh.material.uniforms.uOpacity.value = t;if (t < 1) {requestAnimationFrame(fadeIn);}}fadeIn();}});}, d * 300);}// 所有拼图碎片动画完成后,移除它们并显示下一张图片setTimeout(() => {scene.remove(tempGroup);if (currentFullMesh) scene.remove(currentFullMesh);const geometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const material = createFullScreenShaderMaterial(nextTexture, 1);const mesh = new THREE.Mesh(geometry, material);scene.add(mesh);currentFullMesh = mesh;}, maxDiagonal * 300 + 1000);}/*** 手风琴动画:* 效果:动画整体实现从右到左的手风琴效果,,每一个手风琴片段从下到上展开。* */function createAccordionEffect(nextTexture) {const accordionCount = 5;const accordionWidth = bannerWidth / accordionCount;const accordionHeight = bannerHeight;const accordions = [];for (let i = 0; i < accordionCount; i++) {const geometry = new THREE.PlaneGeometry(accordionWidth, accordionHeight);const uStart = i / accordionCount;const uEnd = (i + 1) / accordionCount;const uvArray = geometry.attributes.uv.array;uvArray[0] = uStart;uvArray[1] = 1;uvArray[2] = uEnd;uvArray[3] = 1;uvArray[4] = uStart;uvArray[5] = 0;uvArray[6] = uEnd;uvArray[7] = 0;geometry.attributes.uv.needsUpdate = true;// 创建每个手风琴片段的材质const material = createFullScreenShaderMaterial(nextTexture, 1);const mesh = new THREE.Mesh(geometry, material);// 设置每个手风琴片段的位置mesh.position.x =i * accordionWidth - bannerWidth / 2 + accordionWidth / 2;mesh.position.y = 0;mesh.scale.y = 0;scene.add(mesh);accordions.push(mesh);}// 动画效果:每个手风琴片段从下到上展开accordions.forEach((accordion, index) => {setTimeout(() => {let progress = 0;const duration = 1200;// 使用 requestAnimationFrame 实现动画function animatePiece() {progress += 16;const t = Math.min(progress / duration, 1);accordion.scale.y = t;accordion.position.y = (t - 1) * (bannerHeight / 2);if (t < 1) {requestAnimationFrame(animatePiece);}}animatePiece();}, (accordionCount - 1 - index) * 300);});// 所有手风琴片段动画完成后,移除它们并显示下一张图片setTimeout(() => {accordions.forEach((mesh) => scene.remove(mesh));if (currentFullMesh) scene.remove(currentFullMesh);const geometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const material = createFullScreenShaderMaterial(nextTexture, 1);const mesh = new THREE.Mesh(geometry, material);scene.add(mesh);currentFullMesh = mesh;}, accordionCount * 300 + 1000);}/*** 渐变动画:* 效果:将整个纹理渐变显示,同时实现缩放动画,* */
function createGradientEffect(nextTexture) {const geometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const material = createFullScreenShaderMaterial(nextTexture, 0);const mesh = new THREE.Mesh(geometry, material);scene.add(mesh);currentFullMesh = mesh;let progress = 0;const duration = 2000;const startScale = 1.2; // 初始放大倍数const endScale = 1.0;   // 结束时为正常大小function animateGradient() {progress += 16;const t = Math.min(progress / duration, 1);mesh.material.uniforms.uOpacity.value = t;// 图片缩放动画:从 startScale 到 endScaleconst scale = startScale + (endScale - startScale) * t;mesh.scale.set(scale, scale, 1);if (t < 1) {requestAnimationFrame(animateGradient);} else {// 动画结束,重置为正常大小mesh.scale.set(1, 1, 1);if (currentFullMesh) scene.remove(currentFullMesh);const newGeometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const newMaterial = createFullScreenShaderMaterial(nextTexture, 1);const newMesh = new THREE.Mesh(newGeometry, newMaterial);scene.add(newMesh);currentFullMesh = newMesh;}}animateGradient();
}/*** 菱形波次效果:* 效果:动画整体实现从中心向外扩散的菱形波次效果,* 每个菱形片段从中心向外扩散,形成菱形波次效果。* */function createDiamondGradientEffect(nextTexture) {const rows = 7;const cols = 5;const pieceWidth = bannerWidth / cols;const pieceHeight = bannerHeight / rows;const pieces = [];const tempGroup = new THREE.Group();scene.add(tempGroup);const centerRow = Math.floor(rows / 2);const centerCol = Math.floor(cols / 2);for (let col = 0; col < cols; col++) {for (let row = 0; row < rows; row++) {const geometry = new THREE.PlaneGeometry(pieceWidth, pieceHeight);const material = createFullScreenShaderMaterial(nextTexture, 0);// 设置材质的 uniformsconst uvOffsetX = col / cols;const uvOffsetY = 1 - (row + 1) / rows;const uvOffsetWidth = 1 / cols;const uvOffsetHeight = 1 / rows;const uvArray = geometry.attributes.uv.array;uvArray[0] = uvOffsetX;uvArray[1] = uvOffsetY + uvOffsetHeight;uvArray[2] = uvOffsetX + uvOffsetWidth;uvArray[3] = uvOffsetY + uvOffsetHeight;uvArray[4] = uvOffsetX;uvArray[5] = uvOffsetY;uvArray[6] = uvOffsetX + uvOffsetWidth;uvArray[7] = uvOffsetY;geometry.attributes.uv.needsUpdate = true;const mesh = new THREE.Mesh(geometry, material);mesh.position.x = col * pieceWidth - bannerWidth / 2 + pieceWidth / 2;mesh.position.y =bannerHeight / 2 - row * pieceHeight - pieceHeight / 2;tempGroup.add(mesh);const distance = Math.abs(row - centerRow) + Math.abs(col - centerCol);pieces.push({ mesh, distance });}}// 计算最大距离const maxDistance = Math.max(...pieces.map((p) => p.distance));// 使用 setTimeout 控制每个菱形片段的动画for (let d = 0; d <= maxDistance; d++) {setTimeout(() => {pieces.forEach(({ mesh, distance }) => {if (distance === d) {let progress = 0;const duration = 400;function fadeIn() {progress += 16;const t = Math.min(progress / duration, 1);mesh.material.uniforms.uOpacity.value = t;if (t < 1) {requestAnimationFrame(fadeIn);}}fadeIn();}});}, d * 200);}// 所有菱形片段动画完成后,移除它们并显示下一张图片setTimeout(() => {scene.remove(tempGroup);if (currentFullMesh) scene.remove(currentFullMesh);const geometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const material = createFullScreenShaderMaterial(nextTexture, 1);const mesh = new THREE.Mesh(geometry, material);scene.add(mesh);currentFullMesh = mesh;}, (maxDistance + 1) * 200 + 800);}/*** 圆形扩展动画:* 效果:圆形从左上角开始扩展,直到覆盖整个纹理,* */function revealPixels(nextTexture) {const geometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);// 创建 uniformsconst uniforms = {uTexture: { value: nextTexture },uResolution: { value: new THREE.Vector2(bannerWidth, bannerHeight) },uRadius: { value: 0.0 },};// 创建 ShaderMaterialconst material = new THREE.ShaderMaterial({uniforms,transparent: true,vertexShader: `varying vec2 vUv;void main() {vUv = uv;gl_Position = projectionMatrix * modelViewMatrix * vec4(position,1.0);}`,fragmentShader: `uniform sampler2D uTexture;uniform vec2 uResolution;uniform float uRadius;varying vec2 vUv;void main() {vec2 uv = vUv;vec2 pixelPos = uv * uResolution;float dist = length(pixelPos - vec2(0.0, uResolution.y));float alpha = smoothstep(uRadius - 10.0, uRadius, dist);vec4 color = texture2D(uTexture, uv);color.a *= 1.0 - alpha;gl_FragColor = color;}`,});// 创建网格并添加到场景const mesh = new THREE.Mesh(geometry, material);scene.add(mesh);// 设置当前全屏网格let progress = 0;const duration = 2000;const maxRadius = Math.sqrt(bannerWidth * bannerWidth + bannerHeight * bannerHeight);// 动画函数:使用 requestAnimationFrame 实现圆形扩展动画function animateReveal() {progress += 16;const t = Math.min(progress / duration, 1);uniforms.uRadius.value = t * maxRadius;if (t < 1) {requestAnimationFrame(animateReveal);} else {if (currentFullMesh) scene.remove(currentFullMesh);scene.remove(mesh);const geometry2 = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const material2 = createFullScreenShaderMaterial(nextTexture, 1);const mesh2 = new THREE.Mesh(geometry2, material2);scene.add(mesh2);currentFullMesh = mesh2;}}animateReveal();}/*** 轮播逻辑:动画效果有:百叶窗、手风琴、拼图、渐变、菱形波次、圆形扩展动画。* 每次切换效果时,先加载下一张图片的纹理,* 如果是第一次加载,则直接显示第一张图片。* 然后根据当前效果索引调用对应的动画函数。* 每个效果动画完成后,更新 currentFullMesh,* 并在最后一个效果后延时8000毫秒再开始下一轮。* 使用 setTimeout 控制每个效果的切换时间。* 使用 effectIndex 来控制当前使用的特效。* 使用 currentIndex 来控制当前显示的图片索引。* 使用 isFirstLoad 来判断是否是第一次加载,* */let currentIndex = 0;let effectIndex = 0;let isFirstLoad = true;// 开始轮播function startSlideshow() {const nextIndex = (currentIndex + 1) % textures.length;if (isFirstLoad) {const material = createFullScreenShaderMaterial(textures[currentIndex],1);const geometry = new THREE.PlaneGeometry(bannerWidth, bannerHeight);const mesh = new THREE.Mesh(geometry, material);scene.add(mesh);currentFullMesh = mesh;isFirstLoad = false;setTimeout(startSlideshow, 2000);return;}if (effectIndex === 0) {animateBlinds(textures[nextIndex]);} else if (effectIndex === 1) {createAccordionEffect(textures[nextIndex]);} else if (effectIndex === 2) {createPuzzleEffect(textures[nextIndex]);} else if (effectIndex === 3) {createGradientEffect(textures[nextIndex]);} else if (effectIndex === 4) {createDiamondGradientEffect(textures[nextIndex]);} else if (effectIndex === 5) {revealPixels(textures[nextIndex]);}effectIndex = (effectIndex + 1) % 6;// 更新 currentIndexcurrentIndex = nextIndex;slideshowTimeout = setTimeout(startSlideshow, 5000);}function animate() {animationFrameId = requestAnimationFrame(animate);renderer.render(scene, camera);}startSlideshow();animate();// 窗口大小调整时更新渲染器和相机window.addEventListener("resize", () => {const newWidth = bannerBox.value.clientWidth;const newHeight = bannerBox.value.clientHeight;camera.aspect = newWidth / newHeight;camera.updateProjectionMatrix();renderer.setSize(newWidth, newHeight);// 更新平面几何体的宽高if (currentFullMesh) {currentFullMesh.geometry.dispose(); // 销毁旧几何体const newGeometry = new THREE.PlaneGeometry(newWidth, newHeight);currentFullMesh.geometry = newGeometry; // 替换为新几何体}});// 浏览器切换到其他网页时,关闭定时器window.addEventListener("blur", () => {console.log("浏览器失去焦点,清理定时器");stopSlideshow();});// 浏览器切换回当前网页时,重新开始轮播window.addEventListener("focus", () => {console.log("浏览器获得焦点,重新开始轮播");if (!slideshowTimeout) {startSlideshow();}});
});
// 轮播定时器控制函数
// stopSlideshow 函数可在任何需要时调用,确保定时器被清理。
function stopSlideshow() {if (slideshowTimeout) {clearTimeout(slideshowTimeout);slideshowTimeout = null;}
}// onUnmounted:切换路由或组件卸载时调用,确保轮播定时器被清理。
// 这样可以保证组件卸载时动画和定时器被正确清理,回来时动画不会乱。
onUnmounted(() => {console.log("组件卸载,清理定时器和动画帧");stopSlideshow(); // 停止轮播定时器if (animationFrameId) {cancelAnimationFrame(animationFrameId);animationFrameId = null;}
});
</script><style scoped>
.banner_box {width: 1100px;margin: 0 auto;height: 750px;position: relative;overflow: hidden;
}
</style>

相关文章:

  • 【Dv3Admin】系统视图菜单按钮管理API文件解析
  • SSIM、PSNR、LPIPS、MUSIQ、NRQM、NIQE 六个图像质量评估指标
  • vxe-table 如何设置单元格垂直对齐
  • MS31912TEA 多通道半桥驱动器 氛围灯 照明灯 示宽灯 转向灯驱动 后视镜方向调节 可替代DRV8912
  • 设置应用程序图标
  • 北斗卫星导航系统(BDS)的 RNSS 和 RDSS
  • 应用篇| MCP为智能体插上翅膀
  • 使用WebSocket实时获取印度股票数据源(无调用次数限制)实战
  • MyBatis————入门
  • onSaveInstanceState() 和 ViewModel 在数据保存能力差异
  • 电动螺丝刀-多实体拆图建模案例
  • 练习:对象数组 4
  • 中医的十问歌和脉象分类
  • D1675/HBT191单通道高清视频放大电路解析
  • day45python打卡
  • DAY45 可视化
  • 现代Web安全实践:基于Token与Refresh Token的单点登录(SSO)实现
  • Dify工具插件开发和智能体开发全流程
  • ​​TPS3808​​低静态电流、可编程延迟电压监控电路,应用笔记
  • 深入理解数字音频:采样率、位深与量化
  • 做哪些网站比较好的/怎么做推广网络
  • 书籍扉页页面设计模板/seo流量的提升的软件
  • 深圳网站建设开发需要多少钱/微信软文范例
  • 网站建设和编程的区别/郑州网站优化外包
  • 平乡县网站建设平台/淘宝seo具体优化方法
  • 佛山 网站建设培训班/天津seo推广软件