像Instagram一样的页面过渡
#javascript #设计 #svelte #sveltekit

页面过渡是网络上最复杂的事情之一,有充分的理由。从一个页面导航到另一个页面时涉及很多事情:安装新页面,仅在过渡完成后才下马,处理共享状态,更新URL,处理浏览器导航等...

...

框架多年来一直试图解决这个问题,但今天仍然是一个挑战。好消息是,由于Chrome提出的Shared Element Transition API,我们很快就可以更轻松地做到这一点。
但是在那之前,我们还有更多工作要做!

以我的portfolio为例,让我们看看Svelte可以如何做页面过渡!

简要

使Instagram移动应用程序成功的事情之一就是它的出色UX。特别是“故事”。它在各处复制,Messenger,WhatsApp,Google Photos等。故事打开,关闭和从上一个/下一步移动的方式使导航变得平稳且自然。

让我们看一下要复制的动画:

  • 当我单击其缩略图时,这个故事会打开。
  • 我可以滑动或单击故事以转到下一个/上一个。
  • 我可以拖延故事以返回主页。

那么,我们如何使用Svelte在我们的网站上创建类似Instagram的体验?

Svelte和Sveltekit工具包

svelte提供了特殊的动画属性,可以应用于元素上以定义其在坐骑和下马上的行为:

<div in:fn={params} out:fn={params} />

应用out Prop将等到执行fn后才从DOM删除元素。
Svelte动画工具包的优势在于,用于动画的功能可以是一种自定义功能,可以使您可以很好地控制过渡。

使用Sveltekit,当组件安装/下卸下或Svelte key更改时,inout动画被调用。
因此,如果有URL更改,但是该组件保持不变(例如,如果我们从 /story /1转到 /story /2,则更改了URL,但是显示这两个页面的组件是相同的),我们需要使用一个key

  import { page } from "$app/stores";

...

  {#key $page.params.story}
    <div in:fn={params} out:fn={params} />
  {/key}

不同的动画取决于导航

在我们的情况下,我们希望“故事”能够以不同的位置进行动画动画:

  • “左/右滑动”如果我们下一个/上一个
  • “扩展进出”如果我们进出故事

多亏了自定义Svelte动画功能,我们可以处理所有这些方案:

<script>
$: transitionsConfig = [
  {
    condition: c => c.toHomepage,
    transition: customScale
  },
  {
    condition: c => c.toHigherIndex,
    transition: customFly,
    inParams: { x: width, duration: 400, opacity: 1 },
    outParams: { x: -width, duration: 400, opacity: 1 }
  },
  {
    condition: c => c.toLowerIndex,
    transition: customFly,
    inParams: { x: -width, duration: 400, opacity: 1 },
    outParams: { x: width, duration: 400, opacity: 1 }
  }
];

$: config = transitionsConfig.find(({ condition }) => {
  const { from, to } = $navigating;

  const fromIndex = parseInt(from.params.story);
  const toIndex = parseInt(to.params.story);

  // If there is no story index in the pathname...
  if (!toIndex) {
    // ...it means we are going to the homepage: "/".
    return condition({
      toHomepage: true
    });
  }

  return condition({
    toHigherIndex: fromIndex < toIndex,
    toLowerIndex: fromIndex > toIndex
  });
});

$: ({ transition, inParams, outParams } = config);

</script>

<div in:transition={inParams} out:transition={outParams} />

这样,您可以“解决”用户导航的正确过渡。

adriendenat.com stories swipe transition

这是``customfly''中的``customfly'',这只是从svelte的默认``fly'',但具有基于路由的自定义参数来左/右动画。

自定义Svelte导航过渡

现在,Instagram上最酷的过渡之一是,在退出时,故事是如何“吸尘”回他们的头像的。让我们看看我们是否可以复制!

首先,我们需要知道屏幕位置在关闭故事时在哪里“真空”。为此,我们必须有所作弊,因为当故事关闭时,主屏幕没有安装,因此我们无法轻易找到故事的缩略图位置。

目前,我们将采用当前故事的索引(按顺序显示),并可以假设其在主屏幕上的位置。
最后,当故事退出以模仿真空效果时,我们可以使用Svelte自定义过渡功能来扩展该故事。这是它的简化版本:

import { scale } from "svelte/transition";

const customScale = (node, options) => {
    const storyIndex = stories[$page.params.story].index;
    const storyThumbnailPosition = { x: 50 * storyIndex, y: 100 };

    node.style.setProperty('transform-origin', `${storyThumbnailPosition.x}px ${storyThumbnailPosition.y}px`);
    node.style.setProperty('z-index', 1);
    return scale(node, options);
};

<Story transition:customScale />

在这里,我们将CSS transform-origin与我们计算的动画位置应用于元素,因此Svelte scale可以扩展到原始故事Avatar位置。

Animation from adriendenat.com of a story opening and closing

使这个看起来像一个真实应用的另一个技巧是故事关闭时对故事缩略图的“弹跳”效果。这是可能的,因为Sveltekit为我们提供了非常方便的$navigating.from

let isActive = $navigating.from.url.pathname === storyPath;
let tween = spring(1, {
  stiffness: 0.03,
  damping: 0.15,
  precision: 0.001
});
$: isActive && tween.set(0);

...

<img 
  src={imgSrc} 
  style={isActive
    ? `transform: translate3d(${$tween * -140}px, ${$tween * 120}px, 0px) scale(${$tween * 3 + 1}); transition: none;` 
    : ''} 
/>

顺便说一句,这是在反应世界中特别复杂的事情! (Next.js或Gatsby路由器不包括“来自”)。

有了这个,StoryButton.svelte可以知道我们正在从其相应的故事中导航,因此我们可以对其进行动画。

此时,您可能会看到我们如何实现所需的大多数动画!

代码示例

我在Codesandbox上准备了一个简化的应用程序,请查看!

请注意,阻力导航仅在移动设备或(使用响应模式下的浏览器开发工具)上工作。

您还可以在GitHub https://github.com/Grsmto/grsmto.github.io上找到我的投资组合(从gif中取出的位置)的源代码。

您知道我可以用来使该网站看起来更像本机应用程序的其他技巧吗?在评论中让我知道!