普通视图

发现新文章,点击刷新页面。
昨天 — 2025年5月17日首页

大学生常用-原生js实现:点击切换图片,轮播图,tab切换,分页符,秒杀等等......直接copy就能使

作者 咔咔库奇
2025年5月17日 16:38
本文详细介绍了多种前端开发中常见的功能实现,涵盖了交互组件、表单处理、数据存储、动态内容处理、实用工具、用户体验优化和视觉效果等多个方面。具体内容包括轮播图、Tab切换、模态框、下拉菜单、手风琴效果、

从this丢失到精准绑定:用一个例子彻底搞懂bind的「救命」场景

作者 海底火旺
2025年5月17日 12:03

前几天写代码时遇到个怪事:用setTimeout调用对象方法,结果this.name打印出undefined。折腾半小时才发现是this指向问题——直到用bind修复后,代码才正常运行。今天就用这个真实案例,结合callapplybind的核心差异,带大家彻底搞懂「绑定this」的底层逻辑。

先看一个「翻车现场」:setTimeout里的this丢失

先看用户提供的代码(稍作修改更直观):

var obj = {
    name: 'cherry',
    func1: function() {
        console.log("当前this指向:", this); 
        console.log("打印name:", this.name); 
    },
    func2: function() {
        setTimeout(function() {
            this.func1(); // 目标:调用obj的func1
        }, 1000);
    }
};

obj.func2(); 
// 1秒后输出:
// 当前this指向: Window {...}(浏览器环境)
// 打印name: undefined

为什么会翻车?

setTimeout的回调函数是独立调用的。在JavaScript中,函数的this在调用时动态绑定,规则是:

  • 如果函数作为对象的方法调用(如obj.func2()),this指向该对象;
  • 如果函数独立调用(如function() {}),this默认指向全局对象(浏览器是window,严格模式是undefined)。

上面的代码中,setTimeout的回调函数是独立调用的,所以this指向window。而window没有func1方法(除非你全局定义过),自然会报错this.func1 is not a function(或打印undefined)。

用bind「拯救」this:用户代码的正确写法

用户提供的修复方案是用bind(obj)绑定this

var obj = {
    name: 'cherry',
    func1: function() {
        console.log("当前this指向:", this); 
        console.log("打印name:", this.name); 
    },
    func2: function() {
        setTimeout(function() {
            this.func1(); 
        }.bind(obj), 1000); // 关键:用bind绑定obj作为this
    }
};

obj.func2(); 
// 1秒后输出:
// 当前this指向: {name: 'cherry', func1: ƒ, func2: ƒ}
// 打印name: cherry

为什么bind能解决问题?

bind(obj)做了两件事:

  1. 返回一个新函数:这个新函数的this被永久绑定为obj,无论它在哪里调用;
  2. 不立即执行bind不会像call/apply那样立即执行原函数,而是等待setTimeout触发时执行新函数。

经过1秒后再出现: 屏幕录制 2025-05-17 113037.gif

对比call和apply:为什么不能用它们?

如果尝试用callapply

// 错误示范:用call
setTimeout(function() {
    this.func1(); 
}.call(obj), 1000); 
// 结果:立即执行(不会等1秒),且setTimeout的第一个参数变成了undefined(因为call立即执行函数,返回值是undefined)

callapply立即执行函数,导致两个问题:

  1. 函数会在setTimeout注册时立即执行,而不是延迟1秒;
  2. setTimeout的第一个参数需要是函数(或字符串),但call执行后的返回值是func1的执行结果(这里是undefined),导致定时器无效。

使用call和apply都是以下效果,cherry都是立即执行: 屏幕录制 2025-05-17 113854.gif

深入理解bind:它到底做了什么?

1. 绑定this,忽略后续调用的this

bind绑定this后,无论新函数如何调用,this都会指向绑定的值:

const boundFunc = function() {
    console.log(this.name);
}.bind({ name: '绑定对象' });

boundFunc(); // 输出:绑定对象(即使独立调用)
boundFunc.call({ name: 'call的this' }); // 输出:绑定对象(call无法覆盖bind的绑定)

2. 预填充参数,创建「偏函数」

bind可以提前传递部分参数,后续调用时只需传剩余参数:

function add(a, b) {
    return a + b;
}

const add5 = add.bind(null, 5); // 预填充第一个参数为5
console.log(add5(3)); // 8(相当于add(5,3))

3. 被new操作符覆盖的绑定

如果用bind绑定的函数作为构造函数(用new调用),bindthis会被new创建的新对象覆盖:

const Person = function(name) {
    this.name = name;
};

const BoundPerson = Person.bind({}); // 绑定this为{}
const p = new BoundPerson('张三'); 
console.log(p.name); // 张三(this指向新创建的p对象)

开发中最常用的5种场景(含用户案例)

1. 解决定时器/回调函数的this丢失(用户案例)

setTimeoutsetInterval的回调函数常因独立调用导致this丢失,bind是最直接的解决方案:

// 用户代码解析:
// 1. obj.func2调用时,this指向obj;
// 2. setTimeout的回调函数通过.bind(obj)绑定this为obj;
// 3. 1秒后回调执行时,this指向obj,成功调用func1。

2. 继承父类构造函数(经典OOP)

用构造函数实现继承时,call可以调用父类构造函数,绑定子类实例的this

function Animal(name) {
  this.name = name;
}

function Dog(name, age) {
  // 用call调用父类,绑定Dog实例的this
  Animal.call(this, name); 
  this.age = age;
}

const dog = new Dog('小黑', 3);
console.log(dog); // { name: '小黑', age: 3 }

3. 数组方法应用于类数组(apply经典用法)

类数组对象(如arguments、DOM节点集合)可以用apply调用数组方法:

function sum() {
  // arguments是类数组对象(有length但无数组方法)
  // 用apply调用Array.prototype.reduce,计算总和
  return Array.prototype.reduce.apply(arguments, [function(acc, cur) {
    return acc + cur;
  }, 0]);
}

console.log(sum(1, 2, 3)); // 6

5. 求数组的最大值/最小值(apply简化代码)

Math.max需要多个参数,apply可以将数组展开为参数列表:

const scores = [95, 88, 92, 100, 85];
const maxScore = Math.max.apply(null, scores); // 100(等价于Math.max(95,88,92,100,85))

总结:记住这3个关键点

  1. call和apply:立即执行函数,call传参数列表,apply传数组;
  2. bind:返回绑定后的新函数,不立即执行,适合延迟调用(如setTimeout、事件绑定);
  3. 核心价值:解决this指向问题,让函数在指定上下文中执行。

回到用户的例子,bind(obj)就像给setTimeout的回调函数装了一个「定位器」,无论它在哪里执行,this都会精准指向obj。这就是bind最核心的作用——固定this,让函数的执行上下文可控

下次遇到this丢失的问题,先想想:是需要立即执行(用call/apply),还是延迟执行(用bind)?想清楚这一点,就能在开发中灵活选择啦!

计算机图形学中的齐次坐标:从基础到应用

作者 Mintopia
2025年5月17日 11:34

一、齐次坐标的基本概念

在计算机图形学中,齐次坐标是一种用 N+1 维向量表示 N 维空间点的方法。在二维空间中,我们通常用 (x, y) 表示一个点,但在齐次坐标中,这个点被表示为 (x, y, w)。其中,w 是一个额外的坐标分量,当 w ≠ 0 时,对应的二维笛卡尔坐标为 (x/w, y/w)。

齐次坐标的优势在于它能够统一处理点和向量,并且能够简洁地表示平移、旋转、缩放等变换。

为什么需要齐次坐标?

在二维空间中,我们可以用矩阵乘法来表示旋转和缩放变换,但平移变换无法直接用 2×2 矩阵表示。齐次坐标通过增加一个维度,让我们能够用 3×3 矩阵统一表示所有的仿射变换(平移、旋转、缩放、剪切等)。

二、齐次坐标的表示

在齐次坐标中:

  • 二维点表示为 (x, y, 1)
  • 二维向量表示为 (x, y, 0)
  • 当 w ≠ 1 时,点 (x, y, w) 对应的笛卡尔坐标为 (x/w, y/w)

向量的 w 分量为 0,这意味着向量在平移变换下不会改变,而点会受到平移的影响。

三、齐次坐标的变换矩阵

在齐次坐标系统中,二维变换可以用 3×3 矩阵表示。下面我们用 JavaScript 实现一个简单的齐次坐标和变换矩阵库。

JavaScript 实现

下面是一个基于齐次坐标的二维变换库的实现,包含了点和向量的表示以及基本变换矩阵的创建和应用。

class Vector3 {
  constructor(x, y, w) {
    this.x = x;
    this.y = y;
    this.w = w !== undefined ? w : 1; // 默认 w 为 1,表示点
  }
  // 转换为笛卡尔坐标
  toCartesian() {
    if (this.w === 0) {
      return new Vector3(this.x, this.y, 0); // 向量保持不变
    }
    return new Vector3(this.x / this.w, this.y / this.w, 1);
  }
  // 应用变换矩阵
  applyMatrix(matrix) {
    const { x, y, w } = this;
    const m = matrix.values;
    return new Vector3(
      m[0][0] * x + m[0][1] * y + m[0][2] * w,
      m[1][0] * x + m[1][1] * y + m[1][2] * w,
      m[2][0] * x + m[2][1] * y + m[2][2] * w
    );
  }
  // 向量加法(仅用于 w=0 的向量)
  add(v) {
    if (this.w !== 0 || v.w !== 0) {
      throw new Error('Vector addition is only defined for vectors (w=0)');
    }
    return new Vector3(this.x + v.x, this.y + v.y, 0);
  }
  // 向量点积(仅用于 w=0 的向量)
  dot(v) {
    if (this.w !== 0 || v.w !== 0) {
      throw new Error('Dot product is only defined for vectors (w=0)');
    }
    return this.x * v.x + this.y * v.y;
  }
  // 向量长度(仅用于 w=0 的向量)
  length() {
    if (this.w !== 0) {
      throw new Error('Length is only defined for vectors (w=0)');
    }
    return Math.sqrt(this.dot(this));
  }
}
class Matrix3 {
  constructor() {
    // 初始化为单位矩阵
    this.values = [
      [1, 0, 0],
      [0, 1, 0],
      [0, 0, 1]
    ];
  }
  // 设置为平移矩阵
  setTranslation(tx, ty) {
    this.values = [
      [1, 0, tx],
      [0, 1, ty],
      [0, 0, 1]
    ];
    return this;
  }
  // 设置为旋转变换矩阵
  setRotation(angleInRadians) {
    const c = Math.cos(angleInRadians);
    const s = Math.sin(angleInRadians);
    this.values = [
      [c, -s, 0],
      [s, c, 0],
      [0, 0, 1]
    ];
    return this;
  }
  // 设置为缩放变换矩阵
  setScaling(sx, sy) {
    this.values = [
      [sx, 0, 0],
      [0, sy, 0],
      [0, 0, 1]
    ];
    return this;
  }
  // 矩阵乘法
  multiply(matrix) {
    const result = new Matrix3();
    const a = this.values;
    const b = matrix.values;
    const c = result.values;
    for (let i = 0; i < 3; i++) {
      for (let j = 0; j < 3; j++) {
        c[i][j] = a[i][0] * b[0][j] + a[i][1] * b[1][j] + a[i][2] * b[2][j];
      }
    }
    return result;
  }
  // 应用于点或向量
  transform(point) {
    return point.applyMatrix(this);
  }
}

四、基本变换的实现

1. 平移变换

平移变换将点 (x, y) 移动到 (x + tx, y + ty)。在齐次坐标中,平移矩阵为:

// 创建平移矩阵
const translationMatrix = new Matrix3();
translationMatrix.setTranslation(50, 100); // 沿 x 轴平移 50,沿 y 轴平移 100
// 创建点 (10, 20)
const point = new Vector3(10, 20);
// 应用平移变换
const translatedPoint = translationMatrix.transform(point);
console.log(translatedPoint.toCartesian()); // 输出: (60, 120, 1)

2. 旋转变换

旋转变换将点绕原点旋转一定角度。在齐次坐标中,旋转矩阵为:

// 创建旋转变换矩阵(绕原点旋转 90 度)
const rotationMatrix = new Matrix3();
rotationMatrix.setRotation(Math.PI / 2); // 90 度 = π/2 弧度
// 创建点 (10, 0)
const point = new Vector3(10, 0);
// 应用旋转变换
const rotatedPoint = rotationMatrix.transform(point);
console.log(rotatedPoint.toCartesian()); // 输出: (0, 10, 1)

3. 缩放变换

缩放变换改变点的大小。在齐次坐标中,缩放矩阵为:

// 创建缩放变换矩阵(x 方向缩放 2 倍,y 方向缩放 0.5 倍)
const scalingMatrix = new Matrix3();
scalingMatrix.setScaling(2, 0.5);
// 创建点 (10, 20)
const point = new Vector3(10, 20);
// 应用缩放变换
const scaledPoint = scalingMatrix.transform(point);
console.log(scaledPoint.toCartesian()); // 输出: (20, 10, 1)

五、变换的组合

在计算机图形学中,我们经常需要组合多个变换。例如,先旋转,再平移,最后缩放。变换的组合顺序非常重要,因为矩阵乘法不满足交换律。

变换顺序示例

下面的例子展示了不同变换顺序的效果:

// 创建一个点
const point = new Vector3(10, 0);
// 创建变换矩阵
const translationMatrix = new Matrix3().setTranslation(50, 0);
const rotationMatrix = new Matrix3().setRotation(Math.PI / 2);
// 顺序 1: 先旋转后平移
const transform1 = translationMatrix.multiply(rotationMatrix);
const result1 = transform1.transform(point);
console.log("先旋转后平移:", result1.toCartesian()); // 输出: (50, 10, 1)
// 顺序 2: 先平移后旋转
const transform2 = rotationMatrix.multiply(translationMatrix);
const result2 = transform2.transform(point);
console.log("先平移后旋转:", result2.toCartesian()); // 输出: (-10, 60, 1)

六、齐次坐标在透视投影中的应用

齐次坐标在透视投影中也有重要应用。透视投影是模拟人眼视觉的一种投影方式,远处的物体看起来比近处的小。

在透视投影中,我们可以通过修改齐次坐标的 w 分量来实现这种效果。例如,将点 (x, y, z) 投影到 z=0 的平面上:

// 透视投影矩阵
function createPerspectiveMatrix(d) {
  const matrix = new Matrix3();
  matrix.values = [    [1, 0, 0],
    [0, 1, 0],
    [0, 0, 1/d]
  ];
  return matrix;
}
// 创建透视投影矩阵(d 是投影平面到视点的距离)
const perspectiveMatrix = createPerspectiveMatrix(100);
// 创建三维点 (50, 0, 50) 在齐次坐标中表示为 (50, 0, 50, 1)
// 注意:我们的 Vector3 类可以处理这种情况,w 分量默认为 1
const point3D = new Vector3(50, 0, 50);
// 应用透视投影
const projectedPoint = perspectiveMatrix.transform(point3D);
console.log("投影后的点:", projectedPoint.toCartesian()); // 输出: (100, 0, 1)

七、应用实例:实现一个简单的图形变换工具

下面我们用 HTML5 Canvas 和上面实现的齐次坐标库来创建一个简单的图形变换工具,支持平移、旋转和缩放。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>齐次坐标图形变换示例</title>
  <style>
    canvas {
      border: 1px solid #ccc;
    }
    .controls {
      margin-top: 10px;
    }
  </style>
</head>
<body>
  <canvas id="canvas" width="500" height="400"></canvas>
  <div class="controls">
    <button id="translateBtn">平移</button>
    <button id="rotateBtn">旋转</button>
    <button id="scaleBtn">缩放</button>
    <button id="resetBtn">重置</button>
  </div>
  <script>
    // 前面定义的 Vector3 和 Matrix3 类的代码放在这里
    
    // 获取 canvas 和绘图上下文
    const canvas = document.getElementById('canvas');
    const ctx = canvas.getContext('2d');
    
    // 创建一个简单的图形(三角形)
    const originalPoints = [
      new Vector3(100, 50),
      new Vector3(200, 150),
      new Vector3(50, 150)
    ];
    
    // 当前变换矩阵
    let transformMatrix = new Matrix3();
    
    // 绘制图形
    function draw() {
      // 清空画布
      ctx.clearRect(0, 0, canvas.width, canvas.height);
      
      // 应用变换
      const transformedPoints = originalPoints.map(point => 
        transformMatrix.transform(point).toCartesian()
      );
      
      // 绘制坐标系
      ctx.strokeStyle = '#ccc';
      ctx.beginPath();
      ctx.moveTo(0, canvas.height / 2);
      ctx.lineTo(canvas.width, canvas.height / 2);
      ctx.moveTo(canvas.width / 2, 0);
      ctx.lineTo(canvas.width / 2, canvas.height);
      ctx.stroke();
      
      // 绘制变换后的图形
      ctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
      ctx.beginPath();
      ctx.moveTo(transformedPoints[0].x, transformedPoints[0].y);
      for (let i = 1; i < transformedPoints.length; i++) {
        ctx.lineTo(transformedPoints[i].x, transformedPoints[i].y);
      }
      ctx.closePath();
      ctx.fill();
      ctx.stroke();
      
      // 绘制变换后的点
      ctx.fillStyle = 'blue';
      transformedPoints.forEach(point => {
        ctx.beginPath();
        ctx.arc(point.x, point.y, 5, 0, Math.PI * 2);
        ctx.fill();
      });
    }
    
    // 初始化绘制
    draw();
    
    // 添加按钮事件
    document.getElementById('translateBtn').addEventListener('click', () => {
      // 创建平移矩阵并与当前矩阵相乘
      const translateMatrix = new Matrix3().setTranslation(50, 30);
      transformMatrix = translateMatrix.multiply(transformMatrix);
      draw();
    });
    
    document.getElementById('rotateBtn').addEventListener('click', () => {
      // 创建旋转变换矩阵并与当前矩阵相乘
      const rotateMatrix = new Matrix3().setRotation(Math.PI / 12); // 15度
      transformMatrix = rotateMatrix.multiply(transformMatrix);
      draw();
    });
    
    document.getElementById('scaleBtn').addEventListener('click', () => {
      // 创建缩放变换矩阵并与当前矩阵相乘
      const scaleMatrix = new Matrix3().setScaling(1.2, 1.2);
      transformMatrix = scaleMatrix.multiply(transformMatrix);
      draw();
    });
    
    document.getElementById('resetBtn').addEventListener('click', () => {
      // 重置变换矩阵
      transformMatrix = new Matrix3();
      draw();
    });
  </script>
</body>
</html>

八、总结

齐次坐标是计算机图形学中一个非常重要的概念,它通过增加一个维度,统一了点和向量的表示,并且能够用矩阵乘法简洁地表示各种变换。掌握齐次坐标和变换矩阵是理解和实现更复杂图形算法的基础。

通过本文的介绍和示例代码,你应该对齐次坐标有了基本的理解,并且能够实现简单的图形变换。在实际应用中,齐次坐标还广泛应用于 3D 图形、计算机视觉和机器人学等领域。

81.爬楼梯

2025年5月17日 11:33

题目链接

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?

解法1 暴力递归和记忆化递归

思路

这本质上就是一个斐波那契数列,所以当 n 走到只有 1 个台阶的时候,只有一种解法。

然后递归去处理。到达 n 这个台阶,有两种方法,要么是走 1 步,要么是走 2 步,所以返回结果是 (n - 1) + (n - 2)

但是这个方法存在大量的计算,所以无法通过题解。如果这个解法要想通过题解,必须要保存已经计算过的步数,下面给出两种方法。

代码

朴素计算

function climbStairs(n: number): number {
    if (n <= 1) return 1;
    return climbStairs(n - 1) + climbStairs(n - 2);
};

memo缓存

function climbStairs(n: number): number {
    const memo = new Array(n + 1).fill(-1); // -1 代表未计算
    memo[0] = 1;
    memo[1] = 1;

    function dfs(i: number): number {
        if (memo[i] !== -1) return memo[i];
        memo[i] = dfs(i - 1) + dfs(i - 2);
        return memo[i];
    }

    return dfs(n);
}

时空复杂度

时间复杂度:朴素 O(2^n),记忆化O(n)

空间复杂度:朴素 O(n),记忆化O(n)

解法2 动态规划

思路

其实上面已经将问题分解成了子问题,到达这个台阶只有两种方法,要么 n - 1,要么 n - 2

所以其实只需要两个变量就可计算完毕当前台阶,即 prevcur 。当前台阶就是 prev + cur ,然后再更新 prevcur

代码

function climbStairs(n: number): number {
    if (n <= 1) return 1;
    let prev = 1;
    let cur = 1;
    for (let i = 2; i <= n; i++) {
        const temp = prev + cur;
        prev = cur;
        cur = temp;
    }

    return cur;
}

时空复杂度

时间复杂度:O(n)

空间复杂度:O(1)

three.js 字体使用全解析

作者 Mintopia
2025年5月17日 11:28

在 3D 可视化项目中,文字是传递信息的重要元素。Three.js 作为强大的 3D 库,提供了多种添加和处理文字的方式。本文将深入探讨 Three.js 中字体的使用方法,从基础文字渲染到高级文字动画,帮助你在 3D 场景中完美呈现文字内容。

一、Three.js 中字体的基本概念

在 Three.js 中使用字体,主要有两种方式:

  1. 基于 Canvas 的 2D 文字渲染:使用 Canvas 绘制文字,然后将其作为纹理应用到 3D 平面上。
  1. 3D 文字几何体:使用 Three.js 提供的 TextGeometry 创建具有厚度和深度的 3D 文字模型。

这两种方式各有优缺点,适用于不同的场景。下面我们将详细介绍这两种方式的实现方法。

二、使用 Canvas 生成文字纹理

基础实现方法

通过 Canvas 生成文字纹理是一种简单且灵活的方法,特别适合需要动态更新文字内容的场景。

// 创建Canvas文字纹理函数
function createTextTexture(text, parameters = {}) {
  parameters = Object.assign({
    fontface: "Arial",
    fontsize: 72,
    backgroundColor: { r: 0, g: 0, b: 0, a: 0 },
    textColor: { r: 255, g: 255, b: 255, a: 255 }
  }, parameters);
  // 创建Canvas元素
  const canvas = document.createElement('canvas');
  const context = canvas.getContext('2d');
  
  // 设置Canvas尺寸
  context.font = `${parameters.fontsize}px ${parameters.fontface}`;
  const textWidth = context.measureText(text).width;
  
  // 设置Canvas尺寸,考虑文字宽度和高度
  canvas.width = textWidth + parameters.fontsize;
  canvas.height = parameters.fontsize * 2;
  
  // 重置字体设置,因为Canvas尺寸改变后可能会重置
  context.font = `${parameters.fontsize}px ${parameters.fontface}`;
  context.textBaseline = 'middle';
  context.textAlign = 'center';
  
  // 绘制背景
  context.fillStyle = `rgba(${parameters.backgroundColor.r}, ${parameters.backgroundColor.g}, ${parameters.backgroundColor.b}, ${parameters.backgroundColor.a})`;
  context.fillRect(0, 0, canvas.width, canvas.height);
  
  // 绘制文字
  context.fillStyle = `rgba(${parameters.textColor.r}, ${parameters.textColor.g}, ${parameters.textColor.b}, ${parameters.textColor.a})`;
  context.fillText(text, canvas.width / 2, canvas.height / 2);
  
  // 创建纹理
  const texture = new THREE.CanvasTexture(canvas);
  
  return texture;
}
// 在Three.js场景中使用Canvas生成的文字纹理
function addTextToScene() {
  // 创建文字纹理
  const texture = createTextTexture("Hello Three.js", {
    fontface: "Arial",
    fontsize: 48,
    textColor: { r: 255, g: 255, b: 255 },
    backgroundColor: { r: 0, g: 0, b: 0, a: 0 }
  });
  
  // 创建材质和平面几何体
  const material = new THREE.MeshBasicMaterial({ map: texture, transparent: true });
  const geometry = new THREE.PlaneGeometry(2, 1);
  const textMesh = new THREE.Mesh(geometry, material);
  
  // 添加到场景
  scene.add(textMesh);
}

高级应用:动态更新文字内容

Canvas 纹理的一个重要优势是可以动态更新文字内容。以下是一个实现动态更新文字的示例:

// 动态更新文字内容
function updateText(text) {
  // 重新生成纹理
  const texture = createTextTexture(text);
  
  // 更新材质的纹理
  textMesh.material.map.dispose();
  textMesh.material.map = texture;
  textMesh.material.needsUpdate = true;
}
// 在动画循环中更新文字
let counter = 0;
function animate() {
  requestAnimationFrame(animate);
  
  // 每100帧更新一次文字
  if (counter % 100 === 0) {
    updateText(`Frame: ${counter}`);
  }
  
  counter++;
  renderer.render(scene, camera);
}

三、使用 TextGeometry 创建 3D 文字

准备字体文件

使用 TextGeometry 需要先加载字体文件。Three.js 使用 JSON 格式的字体文件,这些文件可以通过 Three.js 提供的字体工具生成。

以下是加载字体并创建 3D 文字的示例:

// 加载字体并创建3D文字
function loadFontAndCreateText() {
  const loader = new THREE.FontLoader();
  
  // 加载字体文件
  loader.load('fonts/helvetiker_regular.typeface.json', function(font) {
    // 创建文字几何体
    const geometry = new THREE.TextGeometry('Three.js 3D Text', {
      font: font,
      size: 0.5,
      height: 0.1,  // 文字厚度
      curveSegments: 12,
      bevelEnabled: true,
      bevelThickness: 0.03,
      bevelSize: 0.02,
      bevelOffset: 0,
      bevelSegments: 5
    });
    
    // 计算边界框以居中文字
    geometry.computeBoundingBox();
    const centerOffset = -0.5 * (geometry.boundingBox.max.x - geometry.boundingBox.min.x);
    
    // 创建材质
    const material = new THREE.MeshPhongMaterial({ 
      color: 0x44aa88, 
      specular: 0x111111 
    });
    
    // 创建网格
    const textMesh = new THREE.Mesh(geometry, material);
    
    // 设置位置
    textMesh.position.x = centerOffset;
    textMesh.position.y = 0.2;
    textMesh.position.z = 0;
    
    // 添加到场景
    scene.add(textMesh);
  });
}

优化 3D 文字性能

对于复杂场景中的大量文字,性能可能成为问题。以下是一些优化建议:

  1. 合并几何体:如果有多个静态文字,可以使用BufferGeometryUtils.mergeBufferGeometries合并它们以减少绘制调用。
  1. 降低细节:减少curveSegments和bevelSegments的值可以显著提高性能。
  1. 使用实例化:对于重复的文字,可以使用THREE.InstancedMesh。

四、文字的高级效果与动画

文字材质与光照效果

使用不同的材质可以为文字带来不同的视觉效果:

// 使用不同材质的文字示例
function createTextWithMaterials() {
  // 加载字体
  const loader = new THREE.FontLoader();
  loader.load('fonts/helvetiker_regular.typeface.json', function(font) {
    // 创建基础文字几何体
    const geometry = new THREE.TextGeometry('Materials', {
      font: font,
      size: 0.4,
      height: 0.1
    });
    
    // 不同材质的文字
    const materials = [
      new THREE.MeshBasicMaterial({ color: 0xff0000 }),
      new THREE.MeshLambertMaterial({ color: 0x00ff00 }),
      new THREE.MeshPhongMaterial({ color: 0x0000ff, shininess: 100 }),
      new THREE.MeshStandardMaterial({ color: 0xffff00, metalness: 0.5, roughness: 0.5 })
    ];
    
    // 为每种材质创建一个文字实例
    materials.forEach((material, index) => {
      const textMesh = new THREE.Mesh(geometry.clone(), material);
      textMesh.position.set(-2 + index * 1.5, 0, 0);
      scene.add(textMesh);
    });
  });
}

文字动画效果

以下是一个文字波浪动画的实现:

// 文字波浪动画
function createWavyText() {
  const loader = new THREE.FontLoader();
  loader.load('fonts/helvetiker_regular.typeface.json', function(font) {
    const geometry = new THREE.TextGeometry('Wave Animation', {
      font: font,
      size: 0.5,
      height: 0.1
    });
    
    // 创建顶点材质
    const material = new THREE.ShaderMaterial({
      vertexShader: `
        uniform float time;
        varying vec3 vPosition;
        
        void main() {
          vPosition = position;
          // 添加波浪效果
          float offset = sin(position.x * 5.0 + time) * 0.05;
          vec3 newPosition = position + normal * offset;
          gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
        }
      `,
      fragmentShader: `
        varying vec3 vPosition;
        
        void main() {
          // 基于位置创建颜色渐变
          vec3 color = mix(vec3(0.2, 0.2, 1.0), vec3(1.0, 0.2, 0.2), vPosition.y + 0.5);
          gl_FragColor = vec4(color, 1.0);
        }
      `,
      uniforms: {
        time: { value: 0.0 }
      },
      side: THREE.DoubleSide,
      transparent: false
    });
    
    const textMesh = new THREE.Mesh(geometry, material);
    scene.add(textMesh);
    
    // 动画循环
    function animate() {
      requestAnimationFrame(animate);
      material.uniforms.time.value += 0.05;
      renderer.render(scene, camera);
    }
    
    animate();
  });
}

五、性能优化与最佳实践

字体文件管理

  • 对于小型项目,可以直接使用 Three.js 提供的内置字体。
  • 对于大型项目,考虑创建自定义字体以减少文件大小。
  • 使用字体压缩工具减小字体文件体积。

动态文字的性能考量

  • 频繁更新 Canvas 纹理会影响性能,尽量减少更新频率。
  • 对于实时数据显示,考虑使用数字精灵而非完整文字。

渲染性能优化

  • 对于远距离可见的文字,考虑使用低精度几何体。
  • 使用THREE.LOD(Level of Detail)根据距离动态切换文字精度。

六、实际应用案例

游戏中的 3D 文字

在游戏中,3D 文字可用于显示玩家名称、分数或游戏状态:

// 游戏中的玩家名称显示
function createPlayerNameTag(player) {
  const loader = new THREE.FontLoader();
  loader.load('fonts/helvetiker_regular.typeface.json', function(font) {
    const geometry = new THREE.TextGeometry(player.name, {
      font: font,
      size: 0.2,
      height: 0.02
    });
    
    const material = new THREE.MeshPhongMaterial({ color: player.color });
    const nameTag = new THREE.Mesh(geometry, material);
    
    // 设置文字位置,使其始终面向相机
    nameTag.position.set(0, 2, 0);
    player.add(nameTag);
    
    // 确保文字始终面向相机
    scene.add(new THREE.PointLight(0xffffff, 1, 100));
    scene.add(new THREE.PointLight(0xffffff, 0.5, 100));
  });
}

数据可视化中的文字标签

在数据可视化中,文字标签用于标识数据点:

// 数据可视化中的标签
function addDataLabels(data) {
  data.forEach((item, index) => {
    const texture = createTextTexture(item.label, {
      fontsize: 36,
      textColor: { r: 255, g: 255, b: 255 }
    });
    
    const material = new THREE.SpriteMaterial({ map: texture });
    const sprite = new THREE.Sprite(material);
    
    // 设置位置
    sprite.position.set(item.x, item.y, item.z);
    sprite.scale.set(1, 0.5, 1);
    
    scene.add(sprite);
  });
}

七、常见问题与解决方案

文字模糊问题

  • 原因:Canvas 尺寸过小或纹理过滤设置不当。
  • 解决方案:增加 Canvas 尺寸,或设置纹理的minFilter和magFilter为THREE.NearestFilter。

3D 文字渲染不完整

  • 原因:相机的near值设置过大,或文字超出了视锥体。
  • 解决方案:减小相机的near值,或调整文字位置使其在视锥体内。

性能问题

  • 原因:过多的文字对象或复杂的文字几何体。
  • 解决方案:合并几何体、使用实例化、降低细节级别或使用 Canvas 纹理替代 3D 文字。

八、总结

Three.js 提供了丰富的字体使用方式,从简单的 2D 文字纹理到复杂的 3D 文字几何体,能够满足各种场景的需求。在实际应用中,应根据具体需求选择合适的方法,并注意性能优化。通过合理使用材质、动画和交互效果,可以为 3D 场景增添丰富的信息和生动的视觉体验。

希望本文能帮助你掌握 Three.js 中字体的使用技巧,创造出更加精彩的 3D 项目!

【CSS问题】margin塌陷

作者 咔咔库奇
2025年5月17日 10:34

目录

一、什么是css margin塌陷

二、margin塌陷的原因

三、塌陷的深入机制

四、解决margin塌陷的方法

1、避免同级元素margin重叠:

2、解决父子元素粘连:

五、注意事项


一、什么是css margin塌陷

        CSS中的margin塌陷(也称为margin collapsing)是一个常见的布局问题,主要发生在垂直方向上。当两个或多个元素的垂直margin相遇时,它们不会按照预期叠加,而是会发生重叠,导致最终的外边距值比单独设置时小。

二、margin塌陷的原因

  1. 同级元素:两个同级的元素,垂直排列,上面的盒子给margin-bottom,下面的盒子给margin-top,那么他们两个间距就会重叠,以大的那个盒子的外边距计算。
  2. 父子元素:子元素给一个margin-top,其父级元素也会受到影响,同时产生上边距,父子元素会进行粘连。
  3. 示例:设想页面上有一个蓝色的矩形(.parent),它内部有一个粉色的矩形(.child)。由于margin塌陷,.child元素的margin-top实际上导致了整个.parent元素向下移动,使得.parent的顶部与页面顶部之间的间距增加了,而不是.child元素内部增加了间距。     
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Margin Collapse Example</title>
    <style>
        .parent {
            width: 200px;
            height: 200px;
            background-color: blue;
            margin-top: 50px;
        }
        .child {
            width: 100px;
            height: 100px;
            background-color: pink;
            margin-top: 30px; /* 这个边距将与父级边距一起消失 */
        }
    </style>
</head>
<body>
    <div class="parent">
        <div class="child"></div>
    </div>
</body>
</html>

在上述示例中,.child元素的margin-top与.parent元素的margin-top发生了塌陷,导致整个.parent元素相对于页面顶部移动了50px,而不是80px。

三、塌陷的深入机制

  • 合并规则‌:当两个垂直方向上的外边距相遇时,它们会按照特定的规则合并。对于同级元素,取两者中较大的值作为合并后的外边距;对于父子元素,子元素的margin-top会与父元素的margin-top合并,导致整个父元素向下移动。
  • BFC(块级格式化上下文) ‌:BFC是一个独立的渲染区域,只有属于同一个BFC的元素才会发生外边距合并。触发BFC的方法包括设置overflow属性为hiddenautoscroll,将display属性设置为table-cellinline-blockflex等,或者将float属性设置为leftright等。
  • 包含块‌:每个元素都有一个包含块,它决定了元素的定位和大小。在margin塌陷的情况下,子元素的margin-top实际上是与父元素的包含块顶部对齐,而不是与父元素的内部内容对齐。

四、解决margin塌陷的方法

1、避免同级元素margin重叠

可以使两个外边距不同时出现,即要么只设置上面的margin-bottom,要么只设置下面的margin-top

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Avoid Margin Overlap Example</title>
<style>
  .sibling1 {
    background-color: lightcoral;
    margin-bottom: 20px; /* 只设置下面的margin */
  }
  .sibling2 {
    background-color: lightseagreen;
    /* 不设置margin-top,避免与上面的margin重叠 */
  }
</style>
</head>
<body>
  <div class="sibling1">Sibling 1</div>
  <div class="sibling2">Sibling 2</div>
</body>
</html>

2、解决父子元素粘连

为父盒子设置border:为外层添加border后父子盒子就不是真正意义上的贴合,可以设置透明边框(border:1px solid transparent;)。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Solve Parent-Child Adhesion Example</title>
<style>
  .parent {
    background-color: lightblue;
    border: 1px solid transparent; /* 设置透明边框以避免粘连 */
  }
  .child {
    background-color: coral;
    margin-top: 30px;
  }
</style>
</head>
<body>
  <div class="parent">
    <div class="child">Child Element</div>
  </div>
</body>
</html>

为父盒子添加overflow:hidden:这样可以触发父盒子的块级格式化上下文(BFC),从而避免margin塌陷。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Margin Collapse Example</title>
<style>
  .parent {
    background-color: lightblue;
    display: flex; /* 改变display属性以触发BFC */
  }
  .child {
    background-color: coral;
    margin-top: 30px;
  }
</style>
</head>
<body>
  <div class="parent">
    <div class="child">Child Element</div>
  </div>
</body>
</html>

为父盒子设置padding值:通过给父元素添加内边距,可以使得子元素的margin不再与父元素的顶部粘连。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Margin Collapse Example</title>
<style>
  .parent {
    background-color: lightblue;
    padding-top: 20px; /* 添加内边距以避免margin塌陷 */
  }
  .child {
    background-color: coral;
    margin-top: 30px;
  }
</style>
</head>
<body>
  <div class="parent">
    <div class="child">Child Element</div>
  </div>
</body>
</html>

改变父盒子的display属性:如设置为display:table;display:flex;等,都可以触发BFC,从而解决margin塌陷问题。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Margin Collapse Example</title>
<style>
  .parent {
    background-color: lightblue;
    display: flex; /* 改变display属性以触发BFC */
  }
  .child {
    background-color: coral;
    margin-top: 30px;
  }
</style>
</head>
<body>
  <div class="parent">
    <div class="child">Child Element</div>
  </div>
</body>
</html>

利用伪元素:给父元素的前面添加一个空元素,并设置该伪元素的样式以避免margin塌陷。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Margin Collapse Example</title>
<style>
  .parent::before {
    content: "";
    display: block;
    height: 0; /* 创建一个不可见的块级元素 */
  }
  .parent {
    background-color: lightblue;
  }
  .child {
    background-color: coral;
    margin-top: 30px;
  }
</style>
</head>
<body>
  <div class="parent">
    <div class="child">Child Element</div>
  </div>
</body>
</html>

改变子元素的定位:如设置为position:absolute;,这样子元素就不再相对于父元素定位,而是相对于最近的已定位祖先元素定位(如果没有,则相对于初始包含块定位)。

示例:

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Margin Collapse Example</title>
<style>
  .parent {
    background-color: lightblue;
  }
  .child {
    background-color: coral;
    margin-top: 30px;
    position: absolute; /* 改变定位方式 */
    top: 20px; /* 调整位置 */
  }
</style>
</head>
<body>
  <div class="parent">
    <div class="child">Child Element</div>
  </div>
</body>
</html>

3、触发BFC的其他方法

  • 将元素的display属性设置为inline-blocktable-celltable-captionflow-rootflexinline-flexgridinline-grid等。
  • 将元素的float属性设置为leftright等(非none)。
  • 将元素的position属性设置为absolutefixed

五、注意事项

1、在解决margin塌陷问题时,需要根据具体的布局需求和元素关系来选择合适的方法。

2、触发BFC是解决margin塌陷的一种有效手段,但需要注意BFC对布局的其他可能影响。

码字不易,觉得有用的话,各位大佬就点点赞呗

CSS基础知识05(弹性盒子、布局详解,动画,3D转换,calc)

作者 咔咔库奇
2025年5月17日 10:32

0、弹性盒子、布局

弹性盒子(Flex Box)是CSS3引入的一种新的布局模式,旨在提供一种更有效的方式来布局、对齐和分配在容器中项目的空间,即使它们的大小未知或是动态变化的。以下是对弹性盒子的超级详解:

0.1.弹性盒子的基本概念

弹性容器(Flex Container) :通过设置display属性的值为flexinline-flex,将一个元素定义为弹性容器。弹性容器内包含了一个或多个弹性子元素。

弹性子元素(Flex Items) :弹性容器的子元素。弹性子元素在弹性容器内按照弹性盒子的规则进行布局。

0.2.弹性盒子的主轴和交叉轴

主轴(Main Axis) :弹性元素排列的主要方向,可以是水平方向或垂直方向。该轴的开始和结束被称为main start和main end。

交叉轴(Cross Axis) :垂直于主轴的方向。该轴的开始和结束被称为cross start和cross end。

0.3.弹性盒子的属性

没有添加弹性盒子的属性

编辑

<html>
<head>
<style>
.box {
width: 1000px;
height: 1000px;

}

.b1 {
width: 100px;
height: 100px;
background-color: red;
}

.b2 {
width: 100px;
height: 100px;
background-color: blue;
}

.b3 {
width: 100px;
height: 100px;
background-color: green;
}

.b4 {
width: 100px;
height: 100px;
background-color: yellow;
}
</style>
</head>
<body>
<body>
<div class="box">
<div class="b1">1</div>
<div class="b2">2</div>
<div class="b3">3</div>
<div class="b4">4</div>
</div>
</body>

</body>
</html>

flex-direction

设置弹性子元素的排列方向。

row

row:从左到右水平排列(默认值)。

编辑

.box {
width: 1000px;
    height: 1000px;
display: flex;
    flex-direction: row;
}

row-reverse

row-reverse:从右到左水平排列。

编辑

.box {
width: 1000px;
    height: 1000px;
display: flex;
    flex-direction: row-reverse;
}

column

column:从上到下垂直排列。

编辑

        .box{
            width: 1000px;
            height: 1000px;
            display: flex;
            flex-direction: column;
        }

column-reverse

column-reverse:从下到上垂直排列。

编辑

        .box{
            width: 1000px;
            height: 1000px;
            display: flex;
            flex-direction: column-reverse;
        }

flex-wrap

设置弹性子元素是否换行。

nowrap

nowrap:不换行(默认值)。

如果所有子元素的宽/高总值大于父元素的宽/高,那么为了子元素不溢出,会把内容挤压变形到自适应的宽高。

编辑

.box {
    width: 300px;
    height: 200px;
    display: flex;
    flex-wrap: nowrap;
}

wrap

wrap:换行。

编辑

.box {
    width: 300px;
    height: 200px;
    display: flex;
    flex-wrap: nowrap;
}

wrap-reverse

wrap-reverse:反向换行。

编辑

flex-dirction和flex-wrap的组合简写模式
.box{
    width: 300px;
    height: 200px;
    display: flex;
    flex-flow: row wrap;
}

编辑

justify-content

定义弹性子元素在主轴上的对齐方式。

flex-start

flex-start:靠主轴起点对齐。

编辑

.box{
    width: 500px;
    height: 200px;
    border: 1px solid black;

    display: flex;
    justify-content: flex-start;
    
}

flex-end

flex-end:靠主轴终点对齐。

编辑

.box{
    width: 500px;
    height: 200px;
    display: flex;
    justify-content: flex-end;
    border: 1px solid black;
}

center

center:居中对齐。

编辑

.box{
width: 500px;
height: 200px;
display: flex;
justify-content: center;
border: 1px solid black;
}

space-between

space-between:两端对齐,元素之间的间隔相等。

编辑

.box{
width: 500px;
height: 200px;
display: flex;
justify-content: space-between;
border: 1px solid black;
}

space-around

space-around:元素两侧的间距相同,元素之间的间距比两侧的间距大一倍。

编辑

.box{
width: 500px;
height: 200px;
display: flex;
justify-content: space-around;
border: 1px solid black;
}

space-evenly

space-evenly:元素间距离平均分配。

编辑

.box{
width: 500px;
height: 200px;
display: flex;
justify-content: space-evenly;
border: 1px solid black;
}

align-items(单行)

定义弹性子元素在交叉轴上的对齐方式(适用于单行)。

flex-start

flex-start:交叉轴起点对齐。

编辑

.box{
width: 500px;
height: 200px;
display: flex;
align-items: flex-start;
border: 1px solid black;
}

flex-end

flex-end:交叉轴终点对齐。

编辑

.box{
width: 500px;
height: 200px;
display: flex;
align-items: flex-end;
border: 1px solid black;
}

center

center:交叉轴中点对齐。

编辑

.box{
width: 500px;
height: 200px;
display: flex;
align-items: center;
border: 1px solid black;
}

baseline

baseline:元素的第一行文字的基线对齐。

改变了每个盒子字体的大小这样看基线比较直观

编辑

.box{
width: 500px;
height: 200px;
display: flex;
align-items: baseline;
border: 1px solid black;
}

stretch

stretch:默认值,如果元素未设置高度或者为auto,将占满整个容器的高度。

红盒子不设置高度,容器的填充方向是按照侧轴方向填充的

编辑

.box{
width: 500px;
height: 200px;
display: flex;
align-items: stretch;
border: 1px solid black;
}

align-content(多行)

定义多行弹性子元素在交叉轴上的对齐方式(适用于多行)。

flex-start

flex-start:交叉轴的起点对齐。

编辑

.box{
width: 500px;
height: 600px;
display: flex;
align-content: flex-start;
flex-wrap: wrap;
border: 1px solid black;
}

flex-end

flex-end:交叉轴的终点对齐。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: flex-end;
flex-wrap: wrap;
border: 1px solid black;
}

center

center:交叉轴的中点对齐。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: center;
flex-wrap: wrap;
border: 1px solid black;
}

space-between

space-between:交叉轴两端对齐之间间隔平分。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: space-between;
flex-wrap: wrap;
border: 1px solid black;
}

space-around

space-around:元素两侧的间距相同,元素之间的间距比两侧的间距大一倍。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: space-around;
flex-wrap: wrap;
border: 1px solid black;
}

stretch

stretch:默认值,轴线占满整个交叉轴。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: stretch;
flex-wrap: wrap;
border: 1px solid black;
}

space-evenly

space-evenly:在交叉轴上平均分配空间。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: space-evenly;
flex-wrap: wrap;
border: 1px solid black;
}

align-self

允许单个弹性子元素有与其他子元素不同的对齐方式,可覆盖align-items属性。

auto

auto:默认值,表示继承父容器的align-items属性。如果没有父元素,则等同于stretch

其他值(如flex-startflex-endcenterbaselinestretch)与align-items相同。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-items: flex-start;
flex-wrap: wrap;
border: 1px solid black;
}
/*6号盒子靠底部*/
.b6 {
    align-self: flex-end;
    width: 100px;
    height: 100px;
    background-color: aquamarine;
}
/*7号盒子单行居中*/
.b7 {
    align-self: center;
    width: 100px;
    height: 100px;
    background-color: #9317ff;
}

order

order:设置弹性子元素的排列顺序(数值越小,排列越靠前;默认为0)。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: stretch;
flex-wrap: wrap;
border: 1px solid black;
}

.b1 {
width: 100px;
height: 100px;
background-color: red;
order: 3;
}

.b2 {
width: 100px;
height: 100px;
background-color: blue;
order: 1;
}

.b3 {
width: 100px;
height: 100px;
background-color: green;
order: 0;
}
.b4 {
width: 100px;
height: 100px;
background-color: yellow;
order: 2;
}

flex-grow

flex-grow:定义弹性子元素的伸展系数(默认值为0,即如果存在剩余空间,也不伸展)。

编辑

.box{
width: 500px;
height: 300px;
display: flex;
align-content: stretch;
flex-wrap: wrap;
border: 1px solid black;
}

.b1 {
width: 100px;
height: 100px;
background-color: red;
flex-grow: 3;
}

.b2 {
width: 100px;
height: 100px;
background-color: blue;
flex-grow: 1;
}

.b3 {
width: 100px;
height: 100px;
background-color: green;
}
.b4 {
width: 100px;
height: 100px;
background-color: yellow;
}

flex-shrink

flex-shrink:定义了弹性子元素的收缩系数(默认值为1,即如果空间不足,该项目将等比例缩小)。

flex-basis

flex-basis:定义弹性子元素在主轴上的基础长度(默认值auto,即项目的本来大小)。

该属性可以单独使用,也可以与flex-growflex-shrink一起使用,简写为flex属性(如flex: 1 1 auto)。

0.4.弹性盒子的使用场景

响应式布局:弹性盒子可以根据容器的大小和内容的变化自动调整布局,使得页面在不同的屏幕尺寸和设备上都能够适应。

复杂的布局:弹性盒子提供了多种对齐和分布元素的方式,可以方便地实现复杂的布局。

简化代码:与浮动布局相比,弹性布局减少了代码量和复杂度,提高了可读性和可维护性。

一、动画

1.1.动画序列与关键帧

1.@keyframes:

定义动画的关键帧,通过指定0%到100%之间的百分比来定义动画的各个阶段

在每个关键帧中,可以设置元素的样式属性,如位置、大小、颜色等。

/* 创建关键帧动画 动画名字叫one */
@keyframes one {
  /* 从什么到什么 */
  /* 0% */
  from {}
  /* 100% */
  to {}
}

1.2.动画属性详解

1.animation-name:

指定要应用于元素的动画名称,该名称必须与@keyframes中定义的动画名称相匹配

2.animation-duration

定义动画的持续时间,即动画从开始到结束所需要的时间。

单位可以是秒或毫秒(ms)

3.animation-timing-function:

控制动画的缓冲行为,即动画的速度曲线

属性值:ease(逐渐慢下来)、linear(匀速)、ease-in(加速)、ease-out(减速)等,还可以使用cubic-bezier函数指定以速度曲线

4.animation-delay:

指定动画开始前的延迟时间

单位:m或者ms

5.animation-iteration-count:
指定动画应该播放的次数

属性值:数字表示播放次数,infinite表示无限次播放

6.animation-direction

指定动画的播放方向。

属性值:normal(正常)、reverse(反向)、alternate(奇数次正向播放,偶数次反向播放)等。

7.animation-fill-mode

定义动画在开始或结束时元素的状态。

属性值:none(不改变元素状态)、forwards(动画结束时保持最后一帧的状态)、backwards(动画开始前保持第一帧的状态)等。

8.animation-play-state

控制动画的播放状态。

属性值:running(播放)和paused(暂停)。

1.3.动画简写

上述动画属性可简写为一个属性

animation: name duration timing-function delay iteration-count direction fill-mode;

1.4.使用动画库

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/animate.css/4.1.1/animate.min.css" />
<link rel="stylesheet" href="./iconfont.css">
<style>
  h1 {
    animation-iteration-count: infinite;
  }
</style>
<h1 class="animate__animated animate_bounce">我是动画库</h1>
<!-- animate_ _animated基础类名-->
<!-- animate_bounce动画效果-->

1.5.动画事件

使用JavaScript可以监听动画的开始(animationstart)、结束(animationend)、迭代(animationiteration)等事件。

例:

html文件

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Animation Events</title>
    <style>
        .animated-box {
            width: 100px;
            height: 100px;
            background-color: red;
            position: relative;
            animation: moveBox 2s infinite;
        }

        @keyframes moveBox {
            0% { left: 0; }
            50% { left: 200px; }
            100% { left: 0; }
        }
    </style>
</head>
<body>
    <div class="animated-box" id="box"></div>

    <script src="script.js"></script>
</body>
</html>

js文件

// 获取动画元素
const box = document.getElementById('box');

// 监听动画开始事件
box.addEventListener('animationstart', function(event) {
    console.log('Animation started:', event.animationName);
    // 可以在这里添加额外的逻辑,比如改变背景颜色
    box.style.backgroundColor = 'blue';
});

// 监听动画结束事件
box.addEventListener('animationend', function(event) {
    console.log('Animation ended:', event.animationName);
    // 可以在这里添加额外的逻辑,比如重置背景颜色
    box.style.backgroundColor = 'red';
});

// 监听动画迭代事件
box.addEventListener('animationiteration', function(event) {
    console.log('Animation iterated:', event.animationName, 'Iteration count:', event.iterationCount);
    // 可以在这里添加额外的逻辑,比如改变透明度
    box.style.opacity = '0.5';
    // 可以设置一个延时来恢复透明度,避免一直半透明
    setTimeout(() => {
        box.style.opacity = '1';
    }, 100); // 延时100毫秒
});

二、CSS3 3D转换

CSS3的3D转换属性允许开发者对网页元素进行3D空间中的移动、旋转、缩放等操作。

2.1.  3D变换函数

translate3d(tx, ty, tz) :在3D空间中移动元素。

rotate3d(x, y, z, angle) :围绕3D空间中的某个轴旋转元素。

scale3d(sx, sy, sz) :在3D空间中缩放元素。

perspective(d) :为3D元素添加透视效果,使元素看起来更加立体。通常应用于父元素。

transform-style:控制子元素是否开启三维立体环境,如flat(默认,不开启3D立体空间)、preserve-3d(开启3D立体空间)。

2.2. 3D变换与动画结合

可以将3D变换与动画属性结合使用,创建复杂的3D动画效果。

例如,使用rotate3danimation属性创建一个旋转的3D立方体。

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>3D Cube Animation</title>
<style>
  body {
    margin: 0;
    perspective: 1000px; /* 为整个场景添加透视效果 */
    display: flex;
    justify-content: center;
    align-items: center;
    height: 100vh;
    background-color: #f0f0f0;
  }
  .scene {
    width: 200px;
    height: 200px;
    position: relative;
    transform-style: preserve-3d; /* 开启3D立体空间 */
    animation: rotateCube 5s infinite linear; /* 应用旋转动画 */
  }
  .cube {
    width: 100%;
    height: 100%;
    position: absolute;
    transform-style: preserve-3d; /* 开启3D立体空间 */
  }
  .face {
    position: absolute;
    width: 200px;
    height: 200px;
    background: rgba(255, 255, 255, 0.9);
    border: 1px solid #ccc;
    box-sizing: border-box;
    font-size: 20px;
    font-weight: bold;
    color: #333;
    text-align: center;
    line-height: 200px;
    user-select: none; /* 禁止用户选择文本 */
  }
  .front  { transform: translateZ(100px); } /* 前面 */
  .back   { transform: rotateY(180deg) translateZ(100px); } /* 后面 */
  .right  { transform: rotateY(90deg) translateZ(100px); } /* 右面 */
  .left   { transform: rotateY(-90deg) translateZ(100px); } /* 左面 */
  .top    { transform: rotateX(90deg) translateZ(100px); } /* 上面 */
  .bottom { transform: rotateX(-90deg) translateZ(100px); } /* 下面 */
  
  @keyframes rotateCube {
    from { transform: rotateX(0deg) rotateY(0deg); } /* 动画开始时的状态 */
    to   { transform: rotateX(360deg) rotateY(360deg); } /* 动画结束时的状态 */
  }
</style>
</head>
<body>
<div class="scene">
  <div class="cube">
    <div class="face front">Front</div>
    <div class="face back">Back</div>
    <div class="face right">Right</div>
    <div class="face left">Left</div>
    <div class="face top">Top</div>
    <div class="face bottom">Bottom</div>
  </div>
</div>
</body>
</html>

编辑

2.3. 3D变换的浏览器兼容性

        大多数现代浏览器都支持CSS3的3D变换,但为了确保兼容性,最好检查并测试在不同浏览器上的表现。

解决方案

1.使用css前缀

        可以针对不同浏览器添加相应的CSS前缀(例如-moz-、-ms-、-webkit-等)。

2.使用JavaScript库

        可以使用一些JavaScript库(如Three.js、Babylon.js等)来实现跨浏览器的3D变换效果。

这些库封装了底层的浏览器兼容性处理,简化了开发过程,并提供了丰富的3D图形功能。

3.检测浏览器支持

        通过JavaScript代码检测浏览器是否支持CSS3的3D变换属性。

        如果浏览器不支持3D变换,则提供替代方案或降级处理,以确保用户体验的连续性。

4.考虑使用其他技术

        如果3D变换效果在特定浏览器中无法实现,开发者可以考虑使用其他技术来实现相似的效果。

        例如,可以使用SVG、Canvas等技术来绘制和渲染3D图形。

三、calc函数在动画中的应用

calc()函数允许开发者在CSS中执行一些计算,以动态地设置元素的属性。在动画中,calc()函数可以用于计算元素的位移、大小等属性,从而创建更复杂的动画效果。

1、基本用法

calc()函数支持四则运算和混合单位,如百分比、px、em等。

在使用calc()函数时,需要注意运算符两侧的空格要求。

2、在动画中的应用

可以使用calc()函数来计算元素的位移量,以实现更平滑的动画过渡效果。

例如,可以使用calc(50% - 50px)来设置一个元素相对于其父元素宽度的一半再减去50px的位置。

注意:- + * / 四种运算的运算符两边必须有空格,不然不执行

感恩家人们点点赞

❌
❌