Chrome 实验演示工具

发布于 2022-12-12 22:22:50 字数 12749 浏览 81 评论 0

The Chrome Experiments demo harness is a kiosk-style web app that loops through half a dozen non-interactive demos when idle. When you move the mouse, the demo loop lets you select an interactive demo from a larger list to play with. The loop also has fancy transitions between the demos, with the transition screen showing details about the demo.

Chrome Experiments booth at I/O 2011

One of the Chrome Experiment booths at Google I/O 2011.

The goal for the demo loop was to act as an eye-catching show floor presentation at Google I/O 2011. The idea being that visitors see the demo loop, go “Wow!” and come to the Chrome Experiments booth to play around with the interactive demos. Thus getting a feel for some of the awesome things that are possible on the web today.

To fulfill that goal, the demo loop needed a bunch of great-looking demos (thankfully, the Chrome Experiments site has no lack of those) and an experience as smooth as possible. Each of the demos had to run without slowing down the other demos or the transition animation. Which was quite a challenge, given that the best-looking demos also tended to be the heaviest.

IFRAME wrangling

What I finally ended up with was a bunch of iframes controlled via a window.postMessage protocol. What postMessage lets you do is send a message from one window to another, with a JS object as payload. The receiving window needs to add an event listener for the ‘message’ event that handles the data in the event object. What postMessage allowed me to do is start the current demo without reloading it, and pause all the other demos.

最终的演示序列是这样工作的:首先,淡出当前演示并将其停止。 其次,淡入过渡画布并播放过渡动画。 第三,停止过渡动画并淡出过渡画布。 最后,淡入下一个演示并开始播放。 如您所见,在任何给定时间都只有一个演示在播放,这意味着所有演示都以尽可能快的速度运行。 好吧,仍然有额外的内存使用需要应对,但这有点难以解决。

循环运行的demo

在循环中运行的演示。

我还尝试了一些不太成功的变体,例如依靠 requestAnimationFrame 暂停隐藏的演示并从文档中删除 iframe 元素以停止它们。 requestAnimationFrame 的问题是出于某种原因它没有暂停隐藏的演示。 在文档中删除和重新添加 iframe 元素的问题在于,它会导致 iframe 重新加载自身,从而导致每次演示演示的前几秒充满加载栏和不完整的内容。

iframe 交换的代码如下所示:

this.currentIframe = getCurrentDemoIframe();
this.currentIframe.style.display = 'block';
var self = this;
setTimeout(function() {
  if (self.previousIframe && self.previousIframe != self.currentIframe) {
    self.previousIframe.style.display = 'none';
    if (self.previousIframe.contentWindow)
      self.previousIframe.contentWindow.postMessage('pause','*');
  }
}, 0);
this.onTransitionComplete = function() {
  if (self.currentIframe.contentWindow)
    self.currentIframe.contentWindow.postMessage('pause','*');
};
this.startTransitionAnimation();

将此位连接到演示 iframe 的动画刻度功能:

var paused = false;
window.addEventListener('message', function(ev){
 paused = (ev.data == 'pause');
}, false);
var animloop = function() {
  if (!paused)
    tick();
  requestAnimFrame(animloop, canvas);
};
requestAnimFrame(animloop, canvas);
The interactive demo menu

当您在演示循环中移动鼠标时,会出现交互式演示菜单。 如果您不理会鼠标几秒钟,菜单就会淡出。 菜单有一个很好的动画过渡,菜单项旋转到视图中。 过渡是使用 CSS 3D 变换和 CSS 过渡完成的。 每个菜单项都有一个 CSS 过渡持续时间设置为一个值,该值取决于它与显示中心的 x 距离,从而创建一个很酷的交错过渡效果。

菜单样式全部是 HTML 和 CSS。 菜单标题使用 CSS 文本阴影、CSS 渐变背景和 1px 底部边框在凸起的背景上创建凹陷文本的外观。 通过将图像宽度设置为 180 像素,缩放菜单项缩略图以填充其 180x120 容器 div。 菜单项背景设置为 80% 不透明度黑色,即 rgba(0,0,0,0.8),并且菜单项具有 CSS box-shadow 设置以从背景中弹出。

这是一个示例菜单项。 当您将鼠标悬停在它上面时它会淡出(在真实菜单中,当您将鼠标悬停在菜单上时菜单项会淡入):

演示名称 作者姓名 使用的技术

菜单项的 HTML 并不复杂。 演示缩略图位于一个 div 中以对其进行剪辑。

<div>
  <div>
    <div class="image">
      <img src="http://www.wenjiangs.com/wp-content/uploads/2021/docimg34/1542-cfp3dxc1xxc.png">
    </div>
    <a href="#">Demo Name</a>
    <span class="author">
      Author Name
    </span>
    <span class="info">
      Technologies Used
    </span>
  </div>
</div>

不过,菜单项的样式表要复杂得多。

#demoMenu {
  z-index: 100;
  position: absolute;
  top: 0px;
  left: 0px;
  color: white;
  text-align: center;
  width: 100%;
  height: 100%;
  vertical-align: top;
  background-color: rgba(0,0,0,0);
  -webkit-transform-origin: 50% 0;
  -webkit-transition-duration: 0.5s;
  -webkit-transition-timing-function: ease-in-out;
  -webkit-perspective: 1920px;
  -webkit-perspective-origin: 50% 0;
}

#demoMenu > div {
  vertical-align: top;
  display: inline-block;
  font-size: 13px;
  width: 180px;
  height: 208px;
  padding: 0px;
  margin: 10px;
  background-color: rgba(0,0,0,0.8);
  -webkit-box-shadow: 0px 2px 5px rgba(0,0,0,0.75);
  -webkit-transform-origin: 50% 50%;
  -webkit-transition-duration: 0.5s;
  -webkit-transition-timing-function: ease-in-out;
}

#demoMenu .image {
  width: 180px;
  height: 120px;
  overflow: hidden;
  cursor: pointer;
}

#demoMenu .image img {
  width: 180px;
}

#demoMenu a {
  color: black;
  display: block;
  font-size: 14px;
  font-weight: bold;
  text-decoration: none;
  padding-top: 4px;
  padding-bottom: 4px;
  margin-bottom: 4px;
  background-color: #30dfff;
  border-bottom: 1px solid black;
  text-shadow: 0px 1px 1px rgba(255,235,215,0.5);
  background: #ffa84c;
  background: -moz-linear-gradient(top, #ffa84c 0%, #df5b0d 100%);
  background: -o-linear-gradient(top, #ffa84c 0%,#ff7b0d 100%);
  background: -webkit-gradient(
    linear,
    left top,
    left bottom,
    color-stop(0%,#ffa84c),
    color-stop(100%,#df5b0d)
  );
}

.author {
  display: block;
  font-size: 14px;
}

.info {
  margin-top: 4px;
  display: block;
  color: #ddd;
  font-size: 12px;
  margin-left: 4px;
  margin-right: 4px;
}

这是用于演示菜单项淡入淡出效果的悬停魔术。

#demoMenu > div:hover {
  opacity: 0;
  -webkit-transform: rotateX(90deg);
}

菜单本身有一个 80% 不透明度的白色背景,并且在使用 background-color 属性的 CSS 过渡时它会变淡。 菜单标题使用 text-transform 属性将文本设为大写,并使用 letter-spacing 将字母间隔得很远。 我还在“选择一个演示”文本中使用了一个有眼球的左边距来使字母“A”居中。

交互式演示菜单

演示菜单都是 HTML。

当您选择一个演示时,演示循环脚本会为其创建一个新的 iframe 并将其附加到演示容器中。 iframe 容器有淡入淡出动画,使用不透明度属性上的 CSS 转换完成。 iframe 以大半径投影从背景中弹出。 对于关闭按钮,我使用了 UNICODE 数学运算符“⊗”。

选定的演示运行

在插入 iframe 中运行的选定演示。

关闭按钮 onclick 处理程序从 DOM 中删除演示 iframe 以停止播放演示。 它还删除了关闭按钮本身,并使用 opacity 属性上的 CSS 过渡淡出 iframe 容器。

iframe HTML 非常简单:

<div>
  <iframe class="demoFrame"></iframe>
  <div>⊗</div>
</div>

所有花哨的东西都在 CSS 中:

#demoFrameContainer {
  z-index: 100;
  position: absolute;
  top: 0px;
  left: 0px;
  width: 100%;
  height: 100%;
  background-color: rgba(0,0,0,0.8);
  color: white;
  -webkit-transition-duration: 0.8s;
  opacity: 1;
}

.demoFrame {
  border: 0px;
  -webkit-box-shadow: 0px 0px 20px #000;
  position: absolute;
  top: 30px;
  left: 40px;
  width: 520px;
  height: 180px;
  background-color: #444;
  overflow:hidden;
  -webkit-transition-property: opacity;
  -webkit-transition-duration: 1s;
  -webkit-transition-timing-function: ease-in-out;
}

#demoClose {
  width: 40px;
  font-size: 40px;
  position: absolute;
  text-align: center;
  right: 0px;
  top: 19px;
  cursor: pointer;
}

过渡动画

演示过渡画面

转换屏幕显示名称和演示的作者。

过渡动画是使用我自己开发的 Magi 库在 WebGL 中完成的(我写它是为了做这样的事情。)动画的时间是由一堆 if 语句驱动的,随着动画开始的时间切换,所以这次没有时间表或任何花哨的东西,抱歉。

过渡动画由四个不同的部分组成:

  • 演示信息元素飞入和淡出
  • 彩虹彩带飞进飞出
  • 相机变焦淡出
  • “Chrome 实验”——文本淡入淡出

作者图像和演示信息文本通过简单的补间移动。 每个元素都有一个框架处理程序,可以将其移动到更接近其目标位置。 移动开始时间交错以创建更有趣的动作。 为了淡出元素,我将每个元素像素的不透明度与片段着色器中的不透明度统一相乘。

如果你想知道如何制作一个补间函数,这里有一个补间函数。 它默认为正弦曲线缓入缓出补间:

Tween = {};

Tween.linear = function(t) { return t; };
Tween.easeInOut = function(t) { return 0.5-0.5*Math.cos(Math.PI*2*t); };
Tween.easeIn = function(t) { return 1-Math.cos(Math.PI*t); };
Tween.easeOut = function(t) { return Math.sin(Math.PI*t); };

Tween.tween = function(a, b, t, tweenFunc, dst) {
  if (!tweenFunc)
    tweenFunc = this.easeInOut;
  if (!dst)
    dst = [];
  var f = tweenFunc(t);
  var fc = 1-f;
  for (var i=0; i<a.length; i++) {
    dst[i] = a[i]*fc + b[i]*f;
  }
  return dst;
}

的三次 贝塞尔曲线 色带是用超过 500 个点的 4 边多边形扫描 。 为了在功能区中飞行,我控制功能区 drawArrays 调用绘制的顶点数。 例如,要仅绘制功能区的开头,您只需绘制 24 个顶点。 要绘制到功能区的中间,您需要绘制 24*250 的顶点。 为了飞出丝带,我控制了 drawArrays 调用的起始偏移量,以便它只绘制丝带的末端。

如果您不熟悉 Bézier 曲线的工作原理,其想法是您使用范围在 0 和 1 之间的参数(称为 t )递归地在曲线控制点之间进行插值,0 映射到第一个控制点,1 映射到最后一个控制点控制点。

看看这张维基百科图片。 这是我迄今为止看到的最好的解释:

三次贝塞尔曲线的动画插值。

找到对应于参数值 t 要在贝塞尔曲线上 插入t 的点,您首先要在每个后续控制点之间 处的点。 如果最终得到多个点,则将这些点用作新的控制点,并在它们之间插入 t 处的点。 重复直到你只剩下一分。 该点是 t 处的贝塞尔曲线点。

这是评估三次贝塞尔曲线的代码片段:

Bezier = {};

Bezier.cubicCoord = function(a, b, c, d, t) {
  var a3 = a*3, b3 = b*3, c3 = c*3;
  return a + t*(b3 - a3 + t*(a3-2*b3+c3 + t*(b3-a-c3+d)));
};

Bezier.cubicPoint3 = function(a,b,c,d, t, dest) {
  if (dest == null)
    dest = vec3();
  dest[0] = this.cubicCoord(a[0], b[0], c[0], d[0], t);
  dest[1] = this.cubicCoord(a[1], b[1], c[1], d[1], t);
  dest[2] = this.cubicCoord(a[2], b[2], c[2], d[2], t);
  return dest;
};

Bezier.cubicPoint3v = function(p, t, dest) {
  this.cubicPoint3(p[0], p[1], p[2], p[3], t, dest);
};

为了沿贝塞尔曲线扫描多边形,我从曲线中评估 500 个点并将多边形移动到每个点。 然后我连接多边形的点以创建扫描几何体。

SweepGeo = {};

SweepGeo.translatePoly = function(polygon, offset) {
  var a = [];
  for (var i=0; i<polygon.length; i++) {
    var p = polygon[i];
    a.push(vec3(p[0]+offset[0], p[1]+offset[1], p[2]+offset[2]));
  }
  return a;
};

SweepGeo.createFromBezier = function(path, polygon, count) {
  var triangles = new Float32Array(polygon.length*3*count);
  var triIndex = -1;
  var prev = Bezier.cubicPoint3v(path, 0);
  var prevPoly = this.translatePoly(polygon, prev);

  // go through the path
  for (var i=1; i<count; i++) {
    var t = i/(count-1);
    var next = Bezier.cubicPoint3v(path, t);
    var nextPoly = this.translatePoly(polygon, next);

    // add the triangles connecting prevPoly and nextPoly
    for (var i=0; i<polygon.length; i++) {
      var j = i > polygon.length-1 ? 0 : i;

      // /|
      triangles[++triIndex] = prevPoly[i][0];
        triangles[++triIndex] = prevPoly[i][1];
        triangles[++triIndex] = prevPoly[i][2];
      triangles[++triIndex] = nextPoly[j][0];
        triangles[++triIndex] = nextPoly[j][1];
        triangles[++triIndex] = nextPoly[j][2];
      triangles[++triIndex] = nextPoly[i][0];
        triangles[++triIndex] = nextPoly[i][1];
        triangles[++triIndex] = nextPoly[i][2];

      // |‾
      triangles[++triIndex] = prevPoly[i][0];
        triangles[++triIndex] = prevPoly[i][1];
        triangles[++triIndex] = prevPoly[i][2];
      triangles[++triIndex] = prevPoly[j][0];
        triangles[++triIndex] = prevPoly[j][1];
        triangles[++triIndex] = prevPoly[j][2];
      triangles[++triIndex] = nextPoly[j][0];
        triangles[++triIndex] = nextPoly[j][1];
        triangles[++triIndex] = nextPoly[j][2];
    }
    prev = next;
    prevPoly = nextPoly;
  }
  return triangles;
};

这是带有带状线框可视化效果的实时版本。 拖动旋转,滚动缩放。

相机缩放使用与演示信息元素类似的补间。 当画布元素使用 CSS 不透明度过渡淡出时,相机移向目标点。

Chrome Experiments 文本是一个 HTML 元素,通过 CSS 过渡淡入淡出。 它通过其不透明度和右侧属性进行过渡,并为过渡计时功能提供缓入缓出功能。 做起来很简单。 事实上,使用 CSS 过渡的 JS 实现来制作 WebGL 动画可能会更快。

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据

关于作者

清欢

暂无简介

0 文章
0 评论
467 人气
更多

推荐作者

玍銹的英雄夢

文章 0 评论 0

我不会写诗

文章 0 评论 0

十六岁半

文章 0 评论 0

浸婚纱

文章 0 评论 0

qq_kJ6XkX

文章 0 评论 0

    我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
    原文