起因是高中同学发微博感叹这个网站的交互做得很漂亮,本能地就去看了一下。这是纽约时报悼念因新冠去世的人的一个页面,确实很漂亮,大家可以先去看看(需要fq)Remembering the Nearly 100,000 Lives Lost to Coronavirus in America - The New York Times
下面我们来分析一下这些效果是怎么做出来的。
页面里每隔一段距离就会讲述一下关于十万的一些意义,这个其实比较简单,看html对应的代码。
<div id="g-total">
<div class="g-slide g-header">
<header id="g-interactive-header" style="transform: translate(-50%, -1728.13px);">
<h1 id="g-interactive-heading" class="interactive-heading" itemprop="headline">An Incalculable Lossh1>
<p id="g-interactive-leadin" class="interactive-leadin">America is fast approaching a grim milestone in the coronavirus outbreak — each figure here represents one of the nearly 100,000 lives lost so far. But a count reveals only so much. Memories, gathered from obituaries across the country, help us to reckon with what was lost.p>
<div class="g-interactive-byline">
<span class="g-byline" itemprop="name">By The New York Timesspan>
<time id="interactive-timestamp" class="interactive-timestamp" datetime="2020-05-24">May 24, 2020time>
div>
header>
div>
<div class="g-slide">
<div class="g-slide-inner">
<p>One hundred thousand.p>
<p>Toward the end of May in the year 2020, the number of people in the United States who have died from the coronavirus neared 100,000 — almost all of them within a three-month span. An average of more than 1,100 deaths a day.p>
div>
div>
<div class="g-slide g-spacer">div>
<div class="g-slide">
<div class="g-slide-inner">
<p>One hundred thousand.p>
<p>A number is an imperfect measure when applied to the human condition. A number provides an answer to how many, but it can never convey the individual arcs of life, the 100,000 ways of greeting the morning and saying good night.p>
div>
div>
…………………………(此处省略若干行代码)
<div class="g-slide g-spacer">div>
div>
大家看其中一部分就好了。这里其实就是显式地写出来的,用了一定的CSS样式,打一段话,用一个g-spacer隔开,对应的CSS可以在这一段上方给的连接里找到,来看一下相关的:
.g-slide {
height: 100vh;
pointer-events: none;
position: relative;
}
.g-slide.g-spacer {
height: 300vh;
}
@media all and (min-width: 740px) {
.g-slide.g-spacer {
height: 200vh;
}
}
.g-slide .g-slide-inner {
background: white;
box-sizing: border-box;
border-radius: 4px;
left: 50%;
max-width: 680px;
padding: 15px;
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: calc(100% - 30px);
}
页面上给了很多小人的信息,让你确切地感受到这些逝去的生命都曾是那样鲜活,他们离我们很近。这是怎么做到的呢?
其实分析代码,发现还是挺暴力的(笑)。当然纽约时报不担心这些数据被恶意盗取做什么坏事,所以也没有费劲写什么反爬虫的。
来看对应的html部分:
<div id="g-0" class="g-obit g-init" data-date="February 6" style="width: 320px; transform: translate(292.725px, 526.342px);">
<div class="g-obit-inner">
<b>Auditor in Silicon Valley.b>
<i>Patricia Dowd, 57, San Jose, Calif.i>
div>
div>
………………此处省略代码若干
<div id="g-1000" class="g-obit g-init" data-date="May 19" style="width: 320px; transform: translate(642.249px, 240929px);">
<div class="g-obit-inner">
<b>Champion of people with speech disorders.b>
<i>Annie Glenn, 100, St. Paul, Minn.i>
div>
div>
所以程序员就这样将1001个人的小信息这样显式地写出来了。对应的CSS代码如下:
#g-obits .g-obit .g-obit-inner {
opacity: 0;
visibility: hidden;
max-width: 320px;
position: absolute;
top: 50%;
transform: translate(0, -55%);
}
#g-obits .g-obit.g-init .g-obit-inner {
opacity: 1;
visibility: visible;
}
在html里面设置了transform: translate的值,所以就覆盖了,将这些文字固定在页面的一个位置上。
比较复杂的是这个地方。随着滚动条向下拉,日期会不断增加,随之显示当天的累计死亡人数。
对应的html其实很简单:
<div class="g-date g-show">
<div id="g-day">March 30div>
<div id="g-count">Deaths: 3,356div>
div>
当然对应的CSS里面会设置样式,我们就不看了。重点来看看JS里面有什么东西。
JS在这一坨的最后,还很贴心地写了注释
<!-- JS generated from src/script.js -->
<script id="nytg-rendered-js" src="https://static01.nyt.com/newsgraphics/2020/05/18/usobits/c932bd49b3babd39ed429d9d4a7d49f68f7e59b8/build.js"></script>
所以我们顺着链接打开看看。
首先注意到的是它把这两个元素给连接出来了,因此这个文档里修改display_day和display_count的值,就可以修改日期和累计死亡人数的值了。
var display_day = document.getElementById('g-day');
var display_count = document.getElementById('g-count');
在哪里被修改了呢?我们找到了这个update函数:
function update(){
updating = true;
scrollY = scrollY + ((newScrollY - scrollY) / 4);
// var pct = scrollY / height
var y = scrollY * speed;
render(y);
htmlFrame.style.transform = 'translate(0,'+ -y +'px)';
if(y < winH * 2.5) header.style.transform = 'translate(-50%,'+ -y +'px)';
for (var i = 0; i < days.length; i++) {
if(y + winH > days[i].begin && y < days[i].end) currentDay = days[i];
}
if(y > winH / 2 && y < fieldHeight - (winH * 5) && currentDay.data.dayNum <= 141 ) {
date.classList.add('g-show');
} else {
date.classList.remove('g-show');
}
display_day.innerText = currentDay.data.day;
display_count.innerText = winW < 720 ? currentDay.data.display + " deaths" : "Deaths: " + currentDay.data.display;
if(Math.abs(scrollY - newScrollY) > 0.15 ) {
// requestAnimationFrame(update)
setTimeout(update, 1000/60);
} else {
updating = false;
}
}
可以看到根据目前滚动条的位置设置了y值,然后根据y+窗口高度winH,设置currentDay = days[i],然后就设置display_day和display_count,显示在页面上的日期和死亡人数就会一直变化啦。
什么时候update呢?紧跟着就可以看到下面两个监听:
jquery(window).on('scroll', function(){
newScrollY = getScrollY();
if(!updating) update();
});
这就很明显了,当你滚动滚动条的时候就会触发update函数。
此外还有一些配套的关于滚动条的设置。
比如初始化部分
var scrollY = getScrollY(),
newScrollY = getScrollY(),
updating = false;
比如调整窗口大小的时候,滚动条的比例会变嘛,就会修改一些参数
jquery(window).on('resize', lodash.debounce(function(){
winH = window.innerHeight;
winW = window.innerWidth;
canvas.setAttribute('width', winW);
canvas.setAttribute('height', winH);
newScrollY = getScrollY();
height = wrap.offsetHeight - (winH / 2);
speed = fieldHeight / height;
}, 150));
还有一个要注意的地方就是,他update的数据days[i]是怎么来的呢?
其实他显式地写在了html里面内嵌的一段js代码里
var all_deaths = [{"cumulative":1,"perDay":1,"pctByDay":0,"day":"Feb. 6","dayNum":37,"entries":[{"prettyInfo":"Patricia Dowd, 57, San Jose, Calif.","prettySnippet":"Auditor in Silicon Valley.","date":"February 6","dayNum":37,"id":"g-0"}]},
{"cumulative":1,"perDay":1,"pctByDay":0,"day":"Feb. 26","dayNum":57,"entries":[{"prettyInfo":"Marion Krueger, 85, Kirkland, Wash.","prettySnippet":"Great-grandmother with an easy laugh.","date":"February 26","dayNum":57,"id":"g-1"}]},
{"cumulative":1,"display":"3","perDay":1,"day":"March 1","dayNum":60,"entries":[]},
{"cumulative":1,"display":"6","perDay":0,"day":"March 2","dayNum":61,"entries":[]},
{"cumulative":3,"display":"10","perDay":2,"day":"March 3","dayNum":62,"entries":[]},
……………………省略若干行
{"cumulative":96011,"display":"96,011","perDay":null,"day":"May 24","dayNum":143,"entries":[]}];
在JS里面,init()函数里就将这些数据初始化赋给了days[]
for(var i = 0; i < all_deaths.length; i++) {
days[i] = {
begin: currentY,
data: all_deaths[i]
};
var entries = all_deaths[i].entries;
var spread = entries.length ? Math.floor( all_deaths[i].perDay / entries.length ) : 0;
…………省略初始化的其它模块
}
好啦,暂且就分析到这里。
初看到的时候我以为他做了很多button类的小按钮,是小人形状的,hover在上面的时候会显示对应小人的生平,按下长显示,再按下不显示……这样应该也会很酷。