网页开发老出岔子,这些新活儿跟旧坑到底咋摆弄才得劲?

3,823字
16–24 分钟
in

搞网页开发这摊子事,最怕的就是吭哧吭哧写完代码,一上线发现浏览器压根不买账。最近圈子里头又冒出一堆新东西,像什么跨页面视频状态保持、媒体查询命名、CSS布局的门道,听着都挺唬人。但别慌,今儿咱就撸起袖子,把这些个技术点掰开揉碎了,用最实在的操练流程给大伙儿盘一盘,保证看完就能上手试试。

目录

一、视频状态跨页保存,这操作有点骚

你有没有碰见过这种糟心情况:在一个页面看视频看到一半,点个链接跳转到其他页面,再退回来,好家伙,视频直接从头开始了,之前看到哪全忘光光。这体验简直拉胯到不行。其实这里头藏着个挺硬核的玩法,就是用上那个叫视图过渡的CSS技术,再配合JavaScript的偷梁换柱,能把视频状态牢牢记住。

存状态

先得把视频当前播放时间、是否在播放这些关键信息给抓下来。这一步要在页面即将跳转的瞬间动手,也就是监听那个pageswap事件。这哥们儿就像一个尽职的会计,在页面交接班的时候,把所有账目(视频状态)都一笔笔记下来。把这些信息捣鼓成一个JSON格式的字符串,然后塞进sessionStorage这个临时仓库里。这仓库跟标签页是同生共死的,只要不关标签页,数据就丢不了。

// 在页面跳转前,把视频状态存进sessionStorage
window.addEventListener('pageswap', (event) => {
  // 找到那个视频元素,假设它的id是myVideo
  const video = document.querySelector('#myVideo');
  if (video) {
    const videoState = {
      currentTime: video.currentTime,
      isPlaying: !video.paused
    };
    // 转成字符串存起来,这就好比给视频状态拍了个快照
    sessionStorage.setItem('videoState', JSON.stringify(videoState));
  }
});

干这事儿的时候有个小细节得留心:pageswap事件里头的DOM还是旧页面的,所以获取视频元素完全没问题。但要是视频是通过某些动态加载的框架(比如React、Vue)渲染的,得确保在页面卸载前,视频元素还老老实实待在DOM树里头。

恢复状态

页面跳转完成,新页面闪亮登场的时候,得赶紧把之前存的快照掏出来,给视频状态恢复如初。这时候得靠pagereveal事件来打前站。它在新页面内容还没完全展示出来之前就会触发,是最佳的恢复时机。从sessionStorage里把那个JSON字符串捞出来,解析成对象,然后找到新页面里的视频元素,把播放时间和播放状态给它怼回去。

// 新页面展示前,把状态恢复
window.addEventListener('pagereveal', (event) => {
  const savedState = sessionStorage.getItem('videoState');
  if (savedState) {
    const videoState = JSON.parse(savedState);
    const video = document.querySelector('#myVideo');
    if (video) {
      // 先把时间设置好,就像把书签插回书里
      video.currentTime = videoState.currentTime;
      // 如果之前是在播放状态,那就自动开播
      if (videoState.isPlaying) {
        video.play().catch(e => console.log('播放被浏览器拦了', e));
      }
      // 快照用完了就删掉,省得下次误用
      sessionStorage.removeItem('videoState');
    }
  }
});

恢复的时候可能会遇到音频卡顿一下下,毕竟视频播放器得重新加载资源,这属于正常现象。另外,视频元素得确保加载完成,loadedmetadata事件是个不错的搭档,可以等元数据加载完再设置currentTime,会更稳当。play()方法调用可能会被浏览器拦截,所以最好包在try...catch里,或者给用户一个按钮,让用户主动点一下开始播放,这样体验会更丝滑。

二、媒体查询咋取名,层叠大法来帮忙

写响应式页面,媒体查询那一堆代码常常搞得人头大。什么@media (max-width: 768px),到处复制粘贴,改个断点得翻遍整个样式表。有没有啥法子给这些媒体查询起个名字,像变量一样用起来?这就要祭出CSS层叠层这个神器了。

定义命名层

思路其实不复杂,就是把不同用途的媒体查询样式塞到不同的层里,然后用层的名字来“命名”这个查询。比如,可以把所有针对平板的样式都放在一个叫tablet的层里,把所有针对桌面端的样式放在desktop层里。虽然不能直接写@media tablet,但通过层叠层的优先级管理,效果上差不多。

/* 先定义层的顺序,越靠后的层优先级越高 */
@layer base, tablet, desktop;

/* 基础样式放在base层 */
@layer base {
  .container {
    width: 100%;
    padding: 1rem;
  }
}

/* 平板样式放在tablet层,并且只在屏幕大于768px时生效 */
@layer tablet {
  @media (min-width: 768px) {
    .container {
      max-width: 720px;
      margin: 0 auto;
    }
  }
}

/* 桌面样式放在desktop层,只在屏幕大于1024px时生效 */
@layer desktop {
  @media (min-width: 1024px) {
    .container {
      max-width: 960px;
      padding: 2rem;
    }
  }
}

这么做的好处是,所有的媒体查询逻辑都封装在对应的层里,修改起来特别直观。想要调整平板的断点,只需要去@layer tablet里头改,不会误伤其他样式。层的顺序决定了优先级,desktop层在最后,所以它的样式会覆盖前面层的同名样式,这符合移动优先的设计思路。

三、CSS布局搞不懂,盒子模型得拎清

很多新手朋友写布局,感觉就像在玩俄罗斯方块,这里调调那里改改,莫名其妙就乱了。其实根本原因是对CSS的布局原理一知半解。搞懂浏览器怎么摆放元素,这事儿比啥都重要。

先搞清盒子模型

每个元素在页面上都是一个盒子。这盒子有内容区(content)、内边距(padding)、边框(border)和外边距(margin)。默认情况下,给元素加宽高,指的是内容区的宽高,加上padding和border,盒子实际占的空间会比设定的宽高大。这在做精确布局的时候就容易翻车。

<style>
  /* 经典盒子模型,宽高只管内容区 */
  .box {
    width: 200px;
    padding: 20px;
    border: 1px solid #ccc;
    /* 实际占宽 = 200 + 40 + 2 = 242px */
  }
  /* 换用border-box,宽高就包含padding和border了,舒坦多了 */
  .box-alternative {
    box-sizing: border-box;
    width: 200px;
    padding: 20px;
    border: 1px solid #ccc;
    /* 实际占宽 = 200px,padding和border向内缩 */
  }
</style>

一劳永逸的法子,是在全局把盒子模型改成border-box,这样写宽高的时候心里就有谱了,不会出现“明明设了200px咋给我占了250px”这种灵异事件。大多数现代CSS重置都会做这一步。

布局模式别乱用

搞清楚盒子模型后,就得看怎么把这些盒子排排坐了。这里有几种常见的布局模式:

  • 正常流:块级元素(div、p)从上到下堆叠,行内元素(span、a)从左到右排列。这是默认模式,适合文档流内容。
  • 浮动:早年用的多,让元素脱离正常流,向左或向右靠。但浮动元素会影响后面的内容,得用清除浮动来擦屁股。
  • Flexbox:一维布局神器,适合做导航栏、卡片列表这种要么横向、要么纵向的排列。通过justify-contentalign-items可以轻松搞定对齐问题,不用再算margin。
  • Grid:二维布局大佬,能同时控制行和列。做复杂页面整体框架时,Grid简直就是亲爹。能轻易实现像杂志那种错落有致的布局。

下面用Flexbox做一个常见的三明治布局:顶部导航、中间内容、底部页脚。

<style>
  body {
    margin: 0;
    display: flex;
    flex-direction: column;
    min-height: 100vh; /* 让身体至少撑满整个视口高度 */
  }
  header {
    background: #f0f0f0;
    padding: 1rem;
  }
  main {
    flex: 1; /* 这个关键,让主要内容区域吃掉所有剩余空间 */
    padding: 1rem;
  }
  footer {
    background: #333;
    color: #fff;
    padding: 1rem;
  }
</style>
<body>
  <header>这是脑袋</header>
  <main>这是身子,永远会把脚丫子挤到底部</main>
  <footer>这是脚丫子</footer>
</body>

这里flex: 1是精髓,它告诉浏览器,在垂直方向上,main元素要占满除了headerfooter之外的所有剩余空间。这样不管内容多还是少,页脚都会老老实实待在底部,比用绝对定位去戳屁股优雅多了。