东营码良软件开发
  • 首页
  • 产品与服务
    抖音软件
  • 开发案例
  • 新闻资讯
    二级菜单A
  • 关于我们
  • GEO 优化
  • Ai超市
咨询客服
企业咨询
会员中心
登录注册找回密码
东营码良软件开发
首页›
产品与服务›
开发案例›
新闻资讯›
二级菜单A›
关于我们›GEO 优化›Ai超市›
会员中心
首页产品与服务开发案例新闻资讯我的
Dongying Maliang Software

码良软件开发

企业软件开发·微信小程序·网站建设·GEO 优化·AI 应用落地

咨询客服

导航

  • 首页
  • 产品与服务
  • 开发案例
  • 新闻资讯
  • 关于我们
  • GEO 优化
  • Ai超市

服务

  • 软件开发定制
  • GEO 搜索优化
  • 软件资源商城
  • 会员服务
  • 在线客服

行业方案

  • 零售门店
  • 教育培训
  • 医疗健康
  • 商务服务
  • 本地生活
  • 生产制造

联系电话

1886546092715154670927

地址

山东省东营市垦利区兴隆路8-5号

扫码

码良软件开发 · 品牌入驻
品牌入驻手机端查看商城、案例与在线咨询

Copyright © 2014-2026 东营码良软件开发 Inc. 版权所有鲁ICP备2025202136号-2豫公网安备41019602002340经营许可证编号:豫B2-20190103
开发资讯 > 资讯详情

1232222

2026-10-03 14:29:27阅读 1

1232222

下面给你升级版:可视区域固定 5 张卡片,不足 5 张循环补全,超出 5 张只显示 5 张,样式完全不变。实现方式是卡片池固定 5 张,通过“槽位循环复用”来显示任意数量的图片,而不是之前那样渲染 N 张卡片。

一、核心思路

5 个槽位:[-2] [-1] [ 0] [+1] [+2]
          ↑     ↑   ↑   ↑    ↑
         图片   图片  当前  图片  图片

槽位 s 显示图片 = (current + s + N) % N

- N < 5:槽位会重复出现同一张图片,视觉上循环补全
- N = 5:正好 5 张
- N > 5:只有 5 张同时可见,其它通过槽位旋转进出

卡片不再按图片数量渲染,而是固定 5 个 DOM 元素。 切换时:

  1. 每张卡片的槽位 k 减 delta。
  2. k 超出 [-2, 2] 的卡片绕回另一端,并重新绑定图片。
  3. 从屏幕外滑入,形成循环。

二、完整预览文件

保存为 .html 双击打开即可。

<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>叠加灯箱画廊 · 5 槽位循环</title>
<style>
  :root {
    --card-w: 220px;
    --card-h: 320px;
  }

  * { box-sizing: border-box; }

  html, body {
    margin: 0;
    height: 100%;
    overflow: hidden;
  }

  body {
    background: radial-gradient(ellipse at center, #1b1f2a 0%, #0a0c12 70%);
    color: #fff;
    font-family: system-ui, -apple-system, "PingFang SC", "Microsoft YaHei", sans-serif;
    display: flex;
    align-items: center;
    justify-content: center;
  }

  .gallery {
    position: relative;
    width: 100%;
    max-width: 860px;
    padding: 60px 0 100px;
    perspective: 1400px;
  }

  .stage {
    position: relative;
    width: 100%;
    height: calc(var(--card-h) + 70px);
    touch-action: none;
    cursor: grab;
    user-select: none;
    -webkit-user-select: none;
    transform-style: preserve-3d;
  }
  .stage.dragging { cursor: grabbing; }

  .card {
    position: absolute;
    left: 50%;
    top: 50%;
    width: var(--card-w);
    height: var(--card-h);
    margin: calc(var(--card-h) / -2) 0 0 calc(var(--card-w) / -2);
    border-radius: 16px;
    overflow: hidden;
    cursor: pointer;
    background: #1a1e28;
    box-shadow: 0 20px 40px rgba(0,0,0,.55), 0 4px 12px rgba(0,0,0,.4);
    transition: transform .55s cubic-bezier(.22,1,.36,1),
                opacity .45s ease,
                box-shadow .35s ease;
    will-change: transform, opacity;
    -webkit-tap-highlight-color: transparent;
    backface-visibility: hidden;
  }

  .stage.dragging .card {
    transition: none !important;
  }

  .card img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
    pointer-events: none;
    -webkit-user-drag: none;
    user-select: none;
  }

  .card .cap {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 50px 14px 12px;
    font-size: 13px;
    line-height: 1.4;
    background: linear-gradient(to top, rgba(0,0,0,.9), transparent);
    opacity: 0;
    transition: opacity .3s ease;
    pointer-events: none;
  }

  .card.is-front .cap { opacity: 1; }

  .card.is-front:hover {
    box-shadow: 0 28px 56px rgba(0,0,0,.7), 0 0 0 2px rgba(255,255,255,.2);
  }

  .arrow {
    position: absolute;
    top: 50%;
    transform: translateY(-50%);
    width: 48px; height: 48px;
    border-radius: 50%;
    border: 1px solid rgba(255,255,255,.18);
    background: rgba(255,255,255,.06);
    color: #fff;
    font-size: 24px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    transition: background .25s, transform .25s, border-color .25s;
    z-index: 100;
    padding-bottom: 3px;
  }

  .arrow:hover {
    background: rgba(255,255,255,.16);
    border-color: rgba(255,255,255,.4);
  }

  .arrow:active { transform: translateY(-50%) scale(.92); }
  .arrow.prev { left: 8px; }
  .arrow.next { right: 8px; }

  .dots {
    position: absolute;
    bottom: 40px;
    left: 50%;
    transform: translateX(-50%);
    display: flex;
    gap: 8px;
    z-index: 100;
    max-width: 90%;
    overflow-x: auto;
    padding: 4px 8px;
  }
  .dots::-webkit-scrollbar { display: none; }

  .dot {
    width: 7px; height: 7px;
    border-radius: 50%;
    background: rgba(255,255,255,.25);
    border: 0; padding: 0;
    cursor: pointer;
    transition: background .3s, width .3s;
    flex: 0 0 auto;
  }

  .dot.is-on {
    background: #fff;
    width: 22px;
    border-radius: 4px;
  }

  .lb-backdrop {
    position: fixed;
    inset: 0;
    background: rgba(5,6,10,.92);
    backdrop-filter: blur(10px);
    -webkit-backdrop-filter: blur(10px);
    opacity: 0;
    transition: opacity .45s ease;
    z-index: 900;
  }
  .lb-backdrop.is-on { opacity: 1; }

  .lb-copy {
    position: fixed;
    z-index: 1000;
    border-radius: 16px;
    overflow: hidden;
    cursor: zoom-out;
    box-shadow: 0 40px 100px rgba(0,0,0,.8);
    background: #1a1e28;
    transition: left .5s cubic-bezier(.22,1,.36,1),
                top .5s cubic-bezier(.22,1,.36,1),
                width .5s cubic-bezier(.22,1,.36,1),
                height .5s cubic-bezier(.22,1,.36,1),
                border-radius .5s ease;
  }

  .lb-copy img {
    width: 100%; height: 100%;
    object-fit: cover;
    display: block;
    transition: opacity .25s ease;
  }

  .lb-copy .cap {
    position: absolute;
    left: 0; right: 0; bottom: 0;
    padding: 60px 20px 16px;
    background: linear-gradient(to top, rgba(0,0,0,.9), transparent);
    font-size: 14px;
  }

  .lb-nav {
    position: fixed;
    top: 50%;
    transform: translateY(-50%);
    width: 52px; height: 52px;
    border-radius: 50%;
    border: 1px solid rgba(255,255,255,.18);
    background: rgba(255,255,255,.08);
    color: #fff;
    font-size: 26px;
    line-height: 1;
    cursor: pointer;
    display: flex;
    align-items: center;
    justify-content: center;
    backdrop-filter: blur(8px);
    -webkit-backdrop-filter: blur(8px);
    z-index: 1100;
    opacity: 0;
    pointer-events: none;
    padding-bottom: 3px;
    transition: opacity .35s ease, background .25s ease;
  }
  .lb-nav.is-on { opacity: 1; pointer-events: auto; }
  .lb-nav:hover { background: rgba(255,255,255,.2); }
  .lb-nav:active { transform: translateY(-50%) scale(.94); }
  .lb-nav.prev { left: 24px; }
  .lb-nav.next { right: 24px; }

  @media (max-width: 640px) {
    :root { --card-w: 170px; --card-h: 250px; }
    .arrow { width: 42px; height: 42px; font-size: 20px; }
    .arrow.prev { left: 0; }
    .arrow.next { right: 0; }
    .dots { bottom: 30px; }
    .lb-nav { width: 44px; height: 44px; font-size: 22px; }
    .lb-nav.prev { left: 10px; }
    .lb-nav.next { right: 10px; }
  }
</style>
</head>
<body>

<div class="gallery">
  <div class="stage" id="stage"></div>
  <button class="arrow prev" id="prev" aria-label="上一张">‹</button>
  <button class="arrow next" id="next" aria-label="下一张">›</button>
  <div class="dots" id="dots"></div>
</div>

<script>
(() => {
  /* =========================================================
     数据:换成你自己的图片
     - 少于 5 张:自动循环补全 5 个可见槽位
     - 等于 5 张:正好 5 张
     - 多于 5 张:可视区域只显示 5 张,其余通过循环进出
     ========================================================= */
  const DATA = [
    { src: 'https://picsum.photos/seed/a1/600/900', cap: '雾中山谷' },
    { src: 'https://picsum.photos/seed/b2/600/900', cap: '暮色海岸' },
    { src: 'https://picsum.photos/seed/c3/600/900', cap: '林间小径' },
    { src: 'https://picsum.photos/seed/d4/600/900', cap: '雪后屋顶' },
    { src: 'https://picsum.photos/seed/e5/600/900', cap: '夜行车灯' },
  ];

  const N = DATA.length;
  const HALF = 2;          // 中心左右各 2 张
  const VISIBLE = 5;       // 固定 5 张可见

  const stage = document.getElementById('stage');
  const dotsBox = document.getElementById('dots');

  let current = 0;
  let expanded = null;
  let busy = false;
  let drag = null;
  let dragX = 0;
  let justDragged = false;

  /* 槽位坐标:0 居中,±1 两侧,±2 最外;±3 用于从屏幕外滑入 */
  const POS = {
    '-3': { x: -340, y: 40, rot: -22, scale: .62, z: 20, op: 0 },
    '-2': { x: -255, y: 32, rot: -16, scale: .74, z: 30, op: .45 },
    '-1': { x: -145, y: 16, rot: -9,  scale: .87, z: 40, op: 1 },
    '0':  { x: 0,    y: 0,  rot: 0,   scale: 1,   z: 50, op: 1 },
    '1':  { x: 145,  y: 16, rot: 9,   scale: .87, z: 40, op: 1 },
    '2':  { x: 255,  y: 32, rot: 16,  scale: .74, z: 30, op: .45 },
    '3':  { x: 340,  y: 40, rot: 22,  scale: .62, z: 20, op: 0 },
  };

  /* ---------- 固定创建 5 张卡片(不随图片数量变) ---------- */
  const cards = [];
  for (let i = 0; i < VISIBLE; i++) {
    const el = document.createElement('div');
    el.className = 'card';
    el.innerHTML = '<img draggable="false" alt=""><div class="cap"></div>';

    const card = { el, k: i - HALF, imgIdx: -1 };

    el.addEventListener('click', () => {
      if (busy || expanded || justDragged) return;
      if (card.k === 0) expand();
      else go(current + card.k);
    });

    stage.appendChild(el);
    cards.push(card);
  }

  /* ---------- 圆点:按图片数量生成 ---------- */
  DATA.forEach((_, i) => {
    const b = document.createElement('button');
    b.className = 'dot';
    b.setAttribute('aria-label', `第 ${i + 1} 张`);
    b.addEventListener('click', () => go(i));
    dotsBox.appendChild(b);
  });

  /* ---------- 工具 ---------- */
  const mod = (a, b) => ((a % b) + b) % b;

  function imgIdxFor(k) {
    return mod(current + k, N);
  }

  function setCardContent(card, idx) {
    if (card.imgIdx === idx) return;
    card.imgIdx = idx;
    const img = card.el.querySelector('img');
    const cap = card.el.querySelector('.cap');
    img.src = DATA[idx].src;
    img.alt = DATA[idx].cap;
    cap.textContent = DATA[idx].cap;
  }

  function transformString(k, extraX = 0) {
    const cfg = POS[String(k)] || POS['0'];
    const absK = Math.abs(k);
    const weight = 1 - absK * 0.28;
    const x = cfg.x + extraX * (weight + 0.22);
    const rotY = -extraX * 0.05 * (1 - absK * 0.15);
    return `translate3d(${x}px, ${cfg.y}px, 0) rotateZ(${cfg.rot}deg) rotateY(${rotY}deg) scale(${cfg.scale})`;
  }

  function applyTransform(el, k, extraX = 0) {
    const cfg = POS[String(k)] || POS['0'];
    el.style.transform = transformString(k, extraX);
    el.style.zIndex = cfg.z;
    el.style.opacity = cfg.op;
  }

  function updateDots() {
    [...dotsBox.children].forEach((d, i) => {
      d.classList.toggle('is-on', i === current);
    });
  }

  /* ---------- 初始化:把 5 张卡片绑定到槽位 ---------- */
  function initCards() {
    cards.forEach(card => {
      setCardContent(card, imgIdxFor(card.k));
      applyTransform(card.el, card.k, 0);
      card.el.classList.toggle('is-front', card.k === 0);
    });
    updateDots();
  }

  /* ---------- 单步切换:所有卡片槽位平移 ---------- */
  function step(delta) {
    if (!delta) return;
    current = mod(current + delta, N);

    cards.forEach(card => {
      const oldK = card.k;
      const rawNewK = oldK - delta;
      let newK = rawNewK;
      let wrapped = false;

      if (rawNewK > HALF)  { newK = rawNewK - VISIBLE; wrapped = true; }
      if (rawNewK < -HALF) { newK = rawNewK + VISIBLE; wrapped = true; }

      const newImgIdx = imgIdxFor(newK);

      if (wrapped) {
        // 从屏幕外滑入:先瞬移到范围外,再动画到目标位置
        const fromK = rawNewK > HALF ? HALF + 1 : -HALF - 1;
        const cfgFrom = POS[String(fromK)] || POS['0'];

        card.el.style.transition = 'none';
        card.el.style.transform = transformString(fromK, 0);
        card.el.style.opacity = '0';
        card.el.style.zIndex = cfgFrom.z;

        setCardContent(card, newImgIdx);
        card.k = newK;

        void card.el.offsetWidth;   // 强制 reflow
        card.el.style.transition = '';
        requestAnimationFrame(() => {
          applyTransform(card.el, newK, 0);
        });
      } else {
        card.k = newK;
        setCardContent(card, newImgIdx);
        applyTransform(card.el, newK, 0);
      }

      card.el.classList.toggle('is-front', newK === 0);
    });

    updateDots();
  }

  /* ---------- 跳转:计算最短 delta,单步或跨步 ---------- */
  function go(idx) {
    if (busy) return;
    idx = mod(idx, N);
    if (idx === current) return;

    let delta = idx - current;
    if (delta > N / 2)  delta -= N;
    if (delta < -N / 2) delta += N;

    step(delta);
  }

  document.getElementById('prev').addEventListener('click', () => step(-1));
  document.getElementById('next').addEventListener('click', () => step(1));

  document.addEventListener('keydown', e => {
    if (expanded) {
      if (e.key === 'Escape') { collapse(); return; }
      if (e.key === 'ArrowLeft')  { step(-1); switchExpanded(); return; }
      if (e.key === 'ArrowRight') { step(1);  switchExpanded(); return; }
      return;
    }
    if (e.key === 'ArrowLeft')  step(-1);
    if (e.key === 'ArrowRight') step(1);
  });

  /* ================= 拖拽 ================= */
  stage.addEventListener('pointerdown', e => {
    if (expanded || busy) return;
    if (e.pointerType === 'mouse' && e.button !== 0) return;
    drag = { id: e.pointerId, startX: e.clientX, startY: e.clientY, moved: false };
    dragX = 0;
  });

  document.addEventListener('pointermove', e => {
    if (!drag || e.pointerId !== drag.id) return;

    const dx = e.clientX - drag.startX;
    const dy = e.clientY - drag.startY;

    if (!drag.moved && Math.hypot(dx, dy) > 6) {
      drag.moved = true;
      stage.classList.add('dragging');
    }

    if (drag.moved) {
      dragX = dx * 0.9;
      cards.forEach(card => applyTransform(card.el, card.k, dragX));
      e.preventDefault();
    }
  }, { passive: false });

  document.addEventListener('pointerup', endDrag);
  document.addEventListener('pointercancel', endDrag);

  function endDrag(e) {
    if (!drag || e.pointerId !== drag.id) return;

    const moved = drag.moved;
    const dx = dragX;
    drag = null;

    if (!moved) {
      dragX = 0;
      stage.classList.remove('dragging');
      return;
    }

    justDragged = true;
    setTimeout(() => { justDragged = false; }, 60);

    stage.classList.remove('dragging');
    void stage.offsetWidth;

    const threshold = Math.min(60, window.innerWidth * 0.12);
    dragX = 0;

    // 拖拽后先把卡片还原到当前位置,再执行 step
    cards.forEach(card => applyTransform(card.el, card.k, 0));

    if (dx < -threshold) step(1);
    else if (dx > threshold) step(-1);
  }

  /* ================= 展开 ================= */
  function getCenterCard() {
    return cards.find(c => c.k === 0) || cards[HALF];
  }

  function expand() {
    if (expanded || busy) return;
    const center = getCenterCard();
    const rect = center.el.getBoundingClientRect();
    const data = DATA[imgIdxFor(0)];

    const back = document.createElement('div');
    back.className = 'lb-backdrop';
    document.body.appendChild(back);

    const copy = document.createElement('div');
    copy.className = 'lb-copy';
    copy.innerHTML = `<img src="${data.src}" alt="${data.cap}"><div class="cap">${data.cap}</div>`;
    Object.assign(copy.style, {
      left: rect.left + 'px',
      top: rect.top + 'px',
      width: rect.width + 'px',
      height: rect.height + 'px',
      borderRadius: '16px',
    });
    document.body.appendChild(copy);

    const navPrev = document.createElement('button');
    navPrev.className = 'lb-nav prev';
    navPrev.setAttribute('aria-label', '上一张');
    navPrev.textContent = '‹';
    const navNext = document.createElement('button');
    navNext.className = 'lb-nav next';
    navNext.setAttribute('aria-label', '下一张');
    navNext.textContent = '›';
    document.body.appendChild(navPrev);
    document.body.appendChild(navNext);

    navPrev.addEventListener('click', e => { e.stopPropagation(); step(-1); switchExpanded(); });
    navNext.addEventListener('click', e => { e.stopPropagation(); step(1);  switchExpanded(); });

    expanded = { copy, back, navPrev, navNext };
    center.el.style.opacity = '0';

    void copy.offsetWidth;

    requestAnimationFrame(() => {
      back.classList.add('is-on');
      navPrev.classList.add('is-on');
      navNext.classList.add('is-on');

      const vw = window.innerWidth;
      const vh = window.innerHeight;
      const ratio = rect.width / rect.height;
      let w = vw * 0.9;
      let h = w / ratio;
      if (h > vh * 0.86) { h = vh * 0.86; w = h * ratio; }
      const l = (vw - w) / 2;
      const t = (vh - h) / 2;

      Object.assign(copy.style, {
        left: l + 'px',
        top: t + 'px',
        width: w + 'px',
        height: h + 'px',
        borderRadius: '10px',
      });
    });

    copy.addEventListener('click', collapse);
    back.addEventListener('click', collapse);
  }

  /* ---------- 大图内容切换(淡入淡出) ---------- */
  function switchExpanded() {
    if (!expanded) return;
    const img = expanded.copy.querySelector('img');
    const cap = expanded.copy.querySelector('.cap');
    const data = DATA[imgIdxFor(0)];

    const tmp = new Image();
    tmp.onload = () => {
      if (!expanded) return;
      img.src = data.src;
      img.alt = data.cap;
      cap.textContent = data.cap;
      img.style.opacity = '1';
    };
    img.style.opacity = '0';
    tmp.src = data.src;
  }

  /* ================= 还原 ================= */
  function collapse() {
    if (!expanded || busy) return;
    busy = true;
    const { copy, back, navPrev, navNext } = expanded;
    expanded = null;

    const center = getCenterCard();
    center.el.style.opacity = '';
    const rect = center.el.getBoundingClientRect();

    Object.assign(copy.style, {
      left: rect.left + 'px',
      top: rect.top + 'px',
      width: rect.width + 'px',
      height: rect.height + 'px',
      borderRadius: '16px',
    });
    back.classList.remove('is-on');
    navPrev.classList.remove('is-on');
    navNext.classList.remove('is-on');

    setTimeout(() => {
      copy.remove();
      back.remove();
      navPrev.remove();
      navNext.remove();
      busy = false;
    }, 520);
  }

  /* ---------- 启动 ---------- */
  initCards();
})();
</script>
</body>
</html>

三、行为说明

图片数量表现
1 张5 个槽位全部显示同一张,切换无变化,点击中间可展开
2~4 张5 个槽位循环排列,切换时循环滚动
5 张正好 5 张,与上一版一致
6 张以上可视区域始终 5 张,其余通过左右循环进出

四、关键实现说明

  1. 固定 5 张卡片:for (let i = 0; i < VISIBLE; i++) 只创建 5 个 DOM,不再随 N 变化。
  2. 槽位 k:每张卡片有一个 k ∈ [-2, 2],表示它在扇形中的位置。图片索引永远等于 (current + k) % N,所以 N < 5 时会出现重复图片,N > 5 时只有 5 张同时可见。
  3. 循环进出:step(delta) 里每张卡片的 k 减 delta,超出 [-2, 2] 的卡片绕到另一端,并从 ±3(屏幕外)滑入,形成无限循环。
  4. 图片懒切换:setCardContent() 检查 card.imgIdx !== idx 才更新 src,避免 N = 5 时同一张图被反复重载而闪烁。
  5. 大图与扇形同步:step() 改 current 并更新扇形;switchExpanded() 读同一个 current 更新大图,两者天然一致。
  6. 关闭对齐:collapse() 用 getCenterCard() 找到当前 k = 0 的卡片作为缩回目标,保证大图还原后落在扇形正中。

五、可调参数

想改什么改哪里
卡片大小:root 的 --card-w / --card-h
叠加幅度、旋转POS 里的 x / y / rot / scale
外层透明度POS 里的 op
屏幕外滑入位置POS['-3'] / POS['3']
拖拽层次感transformString() 里的 0.28 / 0.22
拖拽切换阈值endDrag() 里的 Math.min(60, innerWidth * 0.12)
拖拽阻尼pointermove 里的 dx * 0.9

六、验收点

  • 5 张图:和之前完全一样。
  • 3 张图:扇形里能看到重复图片,切换循环滚动。
  • 10 张图:始终只显示 5 张,左右切换时新图从屏幕外滑入。
  • 拖拽、箭头、圆点、键盘、大图放大、大图切换、关闭对齐:全部正常。

如果你希望圆点在图片超过 10 张时折叠成滚动条,或者少于 5 张时不显示重复、只显示实际数量,告诉我,我再加一个开关。

DISCUSSION

评论

登录后参与
请使用网站前台用户账号登录后发表评论。

正在加载评论...


上一篇吧vvvvv下一篇扇形图片循环预览灯箱效果演示代码

作者信息

超级管理
超级管理
等级作者
126文章
0点赞
0收藏
查看作者其他文章

文章展示

新闻资讯板块 · 改造预览新闻资讯板块 · 改造预览ce2ce2ces1ces1vvvvvvvvvvvvvvvvvvvvvv吧vvvvv吧vvvvv

热门排行

网站接入支付功能完整教程1网站接入支付功能完整教程8 阅读GEO企业官网完整搭建实操方案(2026最新标准)
2GEO企业官网完整搭建实操方案(2026最新标准)
7 阅读
会员推荐关系 + 新零售商城 数据库 ER + 字段设计文档3会员推荐关系 + 新零售商城 数据库 ER + 字段设计文档15 阅读
扇形图片循环预览灯箱效果演示代码4扇形图片循环预览灯箱效果演示代码5 阅读
发布内容至抖音H5场景完整申请&接入流程5发布内容至抖音H5场景完整申请&接入流程5 阅读
东营母婴商城小程序开发6东营母婴商城小程序开发14 阅读
CONTENT GUIDE

文章目录

6 项
  1. 01一、核心思路
  2. 02二、完整预览文件
  3. 03三、行为说明
  4. 04四、关键实现说明
  5. 05五、可调参数
  6. 06六、验收点