3d swipe 拖动 -改为linear css

下面整段可直接复制进笔记。


```markdown

Swiper 卡片轮播丝滑优化笔记

> 场景:居中大卡 + 两侧倾斜下移(`recommended-area-mclaw.vue`)

> 目标:① 横向位移匀速 ② `translateY` / `rotate` 与横滑同步,不抖


1. 问题现象

  • 松手 / 自动切页时,**横向滑动有动画**,但卡片 **上下位移、旋转瞬间到位**,感觉「先跳一下再滑」。

  • Swiper 默认缓动是 **ease-out**(先快后慢),不是匀速。


2. 根因(很关键)

Swiper 翻页时大致是:

  1. `setTransition(duration)` → 给 **wrapper** 设 CSS transition 时长

  2. `setTranslate(最终位置)` → **立刻把 progress 设成终点值**,wrapper 靠 CSS 在 `speed` ms 内滑过去

如果我们在 `progress` / `setTranslate` 里根据 `slide.progress` 改卡片 `transform`,但卡片自己是 `transition: none` / `0ms`:

| 层 | 行为 |

|----|------|

| `.swiper-wrapper` | 300ms 横移 |

| `.rec-card` transform | **瞬间**到目标 Y / 旋转 |

两条时间线不同步 → 抖动 / 卡顿感。

`transitionEnd` 再 `snap` 吸附,还可能末尾再跳一下。


3. 我们做了什么

① 横向匀速(CSS)

```css

.swiper-wrapper {

transition-timing-function: linear !important;

}

```

  • `speed` 只控制**多久滑完**

  • `timing-function` 控制**速度曲线**(linear = 匀速)

② 卡片 transform 跟 Swiper 同时长(JS)

```js

function syncCardTransition(swiper, duration) {

const ms = `${duration || 0}ms`;

swiper.slides.forEach((slide) => {

const card = slide.querySelector(".rec-card");

if (card) card.style.transitionDuration = ms;

});

}

// Swiper 事件

on: {

setTransition(s, duration) {

syncCardTransition(s, duration); // 翻页:300ms;跟手:0ms

},

transitionEnd(s) {

syncCardTransition(s, 0); // 收尾归零,下次拖动不拖泥带水

applySlideEffect(s, false); // 不再 snap,避免末尾再跳

},

}

```

③ 卡片 CSS 允许 transform 过渡

```css

/* 之前:transition: none; → 改 duration 也开不起过渡 */

.rec-card {

transition: transform 0ms linear;

}

```

跟手时 duration=0 → 贴手;翻页时 duration=speed → 和 wrapper 一起动。


4. 关键配置 / 参数说明

Swiper 初始化

| 参数 | 示例值 | 作用 |

|------|--------|------|

| `slidesPerView` | `"auto"` | 每页可见宽度由 slide CSS 决定 |

| `centeredSlides` | `true` | 当前卡居中 |

| `loop` | `length > 2` | >2 张才循环;=2 关闭避免首尾怪 |

| `loopedSlides` | `max(len, 3)` | loop 时预克隆数量 |

| `watchSlidesProgress` | `true` | **必须**,才有 `slide.progress` |

| `spaceBetween` | 设计稿 24→实际 px | 卡片间距 |

| `speed` | `300` | 翻页动画时长(ms) |

| `grabCursor` | `true` | 鼠标手型 |

| `initialSlide` | 记忆下标 | 切分类回来还原位置 |

动效常量

| 常量 | 值 | 作用 |

|------|-----|------|

| `ROTATE_SIDE` | `5` | 两侧最大旋转角(deg),左正右负 |

| `DESIGN_TRANSLATE_Y` | `14` | 设计稿两侧下移 px |

| `DESIGN_SPACE_BETWEEN` | `24` | 设计稿卡片间距 |

| `ROTATE_EPS` / `TRANSLATE_Y_EPS` | `0.3` / `0.5` | snap 吸附阈值(现主要 init 用) |

`slide.progress`

  • 中心 ≈ `0`

  • 左侧负、右侧正

  • `|progress|` 越大离中心越远

  • 常用:`t = Math.min(Math.abs(p), 1)`,再 `translateY = sideY * t`,`rotate = ±ROTATE_SIDE * t`

事件分工

| 事件 | 做什么 |

|------|--------|

| `init` | duration=0 + 初始 effect(可 snap) |

| `progress` / `setTranslate` | 按 progress 写 transform |

| `setTransition` | **同步卡片 transitionDuration** |

| `transitionEnd` | duration 归零,不 snap |

| `slideChange` | 记当前 realIndex |


5. 可复制 Demo(单文件 HTML,浏览器打开即跑)

把下面存成 `swiper-smooth-demo.html`,用浏览器打开,左右拖或点按钮即可对比。

```html

<!DOCTYPE html>

<html lang="zh-CN">

<head>

<meta charset="UTF-8" />

<meta name="viewport" content="width=device-width, initial-scale=1" />

<title>Swiper 匀速 + transform 同步 Demo</title>

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.css" />

<style>

* { box-sizing: border-box; }

body {

margin: 0;

min-height: 100vh;

background: #f0f2f8;

font-family: system-ui, sans-serif;

padding: 24px 0 48px;

}

h1 { text-align: center; font-size: 16px; color: #122133; }

.hint { text-align: center; font-size: 13px; color: #666; margin-bottom: 16px; }

.btns { text-align: center; margin-bottom: 20px; }

button {

margin: 0 6px; padding: 8px 14px; border: none; border-radius: 8px;

background: #736eff; color: #fff; cursor: pointer;

}

button.ghost { background: #fff; color: #736eff; border: 1px solid #736eff; }

.swiper {

width: 100%;

padding: 24px 0 40px;

overflow: visible;

}

.swiper-wrapper {

/* ① 横向匀速(默认是 ease) */

transition-timing-function: linear !important;

}

.swiper-slide {

width: 180px;

height: 220px;

}

.card {

width: 100%;

height: 100%;

border-radius: 16px;

background: linear-gradient(145deg, #fff, #e8eaff);

box-shadow: 0 4px 16px rgba(18, 33, 51, 0.12);

display: flex;

align-items: center;

justify-content: center;

font-size: 28px;

font-weight: 700;

color: #534dff;

/* ② 允许 transform 过渡;时长由 JS 写入 */

transition: transform 0ms linear;

will-change: transform;

}

</style>

</head>

<body>

<h1>Swiper:横滑匀速 + Y/旋转同步</h1>

<p class="hint">拖动跟手;点「下一张」看翻页是否丝滑(可点「对比:不同步」还原旧问题)</p>

<div class="btns">

<button id="prev">上一张</button>

<button id="next">下一张</button>

<button class="ghost" id="toggle">对比:不同步</button>

</div>

<div class="swiper">

<div class="swiper-wrapper">

<div class="swiper-slide"><div class="card">1</div></div>

<div class="swiper-slide"><div class="card">2</div></div>

<div class="swiper-slide"><div class="card">3</div></div>

<div class="swiper-slide"><div class="card">4</div></div>

<div class="swiper-slide"><div class="card">5</div></div>

</div>

</div>

<script src="https://cdn.jsdelivr.net/npm/swiper@8/swiper-bundle.min.js"></script>

<script>

const ROTATE_SIDE = 5;

const TRANSLATE_Y = 14;

const SPEED = 300;

let syncEnabled = true; // true = 我们的优化;false = 旧问题

function syncCardTransition(swiper, duration) {

if (!syncEnabled) duration = 0;

const ms = (duration || 0) + "ms";

swiper.slides.forEach((slide) => {

const card = slide.querySelector(".card");

if (card) card.style.transitionDuration = ms;

});

}

function applySlideEffect(swiper) {

swiper.slides.forEach((slide) => {

const card = slide.querySelector(".card");

if (!card) return;

const p = slide.progress || 0;

const t = Math.min(Math.abs(p), 1);

const translateY = TRANSLATE_Y * t;

const rotate = (p < 0 ? ROTATE_SIDE : -ROTATE_SIDE) * t;

// 旧写法还会强制 0ms:card.style.transitionDuration = "0ms"

if (!syncEnabled) card.style.transitionDuration = "0ms";

card.style.transform = `rotate({rotate}deg) translate3d(0, {translateY}px, 0)`;

});

}

const swiper = new Swiper(".swiper", {

slidesPerView: "auto",

centeredSlides: true,

spaceBetween: 24,

loop: true,

loopedSlides: 5,

watchSlidesProgress: true,

speed: SPEED,

grabCursor: true,

on: {

init(s) {

syncCardTransition(s, 0);

applySlideEffect(s);

},

progress(s) {

applySlideEffect(s);

},

setTranslate(s) {

applySlideEffect(s);

},

setTransition(s, duration) {

syncCardTransition(s, duration);

},

transitionEnd(s) {

syncCardTransition(s, 0);

applySlideEffect(s);

},

},

});

document.getElementById("prev").onclick = () => swiper.slidePrev();

document.getElementById("next").onclick = () => swiper.slideNext();

document.getElementById("toggle").onclick = (e) => {

syncEnabled = !syncEnabled;

e.target.textContent = syncEnabled ? "对比:不同步" : "已开同步(再点关)";

alert(syncEnabled

? "已开启:卡片 Y/旋转与横滑同时长"

: "已关闭:模拟旧问题(transform 瞬间跳)");

};

</script>

</body>

</html>

相关推荐
Zldaisy3d17 小时前
增材制造陶瓷的强化策略:机制与工艺-结构-性能关系的多尺度综述
3d·制造
在下胡三汉20 小时前
用 glTF/glb 加载自定义 3D 模型
3d·gltf
李航19833 天前
用 DeepDraw 几何引擎开发建筑设计软件(十):推挤工具
python·3d
iNeuOS工业互联网5 天前
大模型(DeepSeek)辅助 3D 实时建模 + 拖拽配置:业务可视化应用快速构建实践(标注、巡检与视角控制等)
人工智能·物联网·3d·语言模型·工业互联网
Zldaisy3d5 天前
点亮高端制造轻量化新图景:铝基材料增材制造进展与挑战
3d·制造
事界见闻6 天前
网页版AI 3D工具通常能完成哪些基础工作?生成、处理与导出流程对比
人工智能·3d
数字新视界6 天前
国产信创动环监控系统赋能智能环境安全新未来
物联网·3d·数字孪生·三维可视化·数据可视化
chaoyuanl6 天前
超元力主题乐园飞行影院:把景区文化做成可持续更新的沉浸体验
大数据·3d·xr·娱乐·mr
姓林君6 天前
分享一个ZW3D二次开发查接口写示例的Agent_MCP工具
3d