Jim Vallandingham
@vlandham
2013
Abusing The Force
So You Think You Can Scroll
Jim Vallandingham
@vlandham
Scrolling
A Short History
"One of the most useful innovations in pointing devices"
Scrolling is a continuation
Clicking is a decision
Parallax Scrolling
Infinite Scrolling
Art Scrolling
Scrolling
In Visualization
Scrollable Dimension
Scrolling Triggers
Scrolling Steps
Scrolling Continuous
Interactive Storytelling
Scrollytelling
"The Web is a Storytelling Medium"
Scrollwork
Implementation Details
Demo
Potentially Humorous
Events
&
Transitions
Scroll Events
Detecting The Scroll
sectionPositions = [];
var startPos;
sections.each(function(d,i) {
var top = this.getBoundingClientRect().top;
if(i === 0) {
startPos = top;
}
sectionPositions.push(top - startPos);
});
sectionPositions = [];
var startPos;
sections.each(function(d,i) {
var top = this.getBoundingClientRect().top;
if(i === 0) {
startPos = top;
}
sectionPositions.push(top - startPos);
});
function position() {
var pos = window.pageYOffset - 10;
var sectionIndex = d3.bisect(sectionPositions, pos);
sectionIndex = Math.min(sections.size() - 1, sectionIndex);
if (currentIndex !== sectionIndex) {
dispatch.active(sectionIndex);
currentIndex = sectionIndex;
}
}
function position() {
var pos = window.pageYOffset - 10;
var sectionIndex = d3.bisect(sectionPositions, pos);
sectionIndex = Math.min(sections.size() - 1, sectionIndex);
if (currentIndex !== sectionIndex) {
dispatch.active(sectionIndex);
currentIndex = sectionIndex;
}
}
function position() {
var pos = window.pageYOffset - 10;
var sectionIndex = d3.bisect(sectionPositions, pos);
sectionIndex = Math.min(sections.size() - 1, sectionIndex);
if (currentIndex !== sectionIndex) {
dispatch.active(sectionIndex);
currentIndex = sectionIndex;
}
}
// create dispatcher
var dispatch = d3.dispatch("active");
// create dispatcher
var dispatch = d3.dispatch("active");
// send event
dispatch.active(sectionIndex);
// create dispatcher
var dispatch = d3.dispatch("active");
// send event
dispatch.active(sectionIndex);
// listen for event
dispatch.on("active", function(index) {
// ...
});
function position() {
var pos = window.pageYOffset - 10;
var sectionIndex = d3.bisect(sectionPositions, pos);
sectionIndex = Math.min(sections.size() - 1, sectionIndex);
if (currentIndex !== sectionIndex) {
dispatch.active(sectionIndex);
currentIndex = sectionIndex;
}
}
Transitions
transition all the things
Setup All Elements at the Start
function setupVis() {
g.append("text")
.attr("class", "sub-title openvis-title")
.attr("x", width / 2)
.attr("y", (height / 3) + (height / 5) )
.text("OpenVis Conf");
g.selectAll(".openvis-title")
.attr("opacity", 0);
// everything else ...
}
function setupVis() {
g.append("text")
.attr("class", "sub-title openvis-title")
.attr("x", width / 2)
.attr("y", (height / 3) + (height / 5) )
.text("OpenVis Conf");
g.selectAll(".openvis-title")
.attr("opacity", 0);
// everything else ...
}
Each Section Gets a Function
activateFunctions[0] = showTitle;
activateFunctions[1] = showFillerTitle;
activateFunctions[2] = showGrid;
activateFunctions[3] = highlightGrid;
activateFunctions[4] = showBar;
activateFunctions[5] = showHistPart;
activateFunctions[6] = showHistAll;
activateFunctions[7] = showCough;
activateFunctions[8] = showHistAll;
dispatch.on("active", function(index) {
activateFunctions[index]();
}
Section Functions:
Show Themselves
Hide Their Neighbors
function showGrid() {
g.selectAll(".count-title")
.transition()
.duration(0)
.attr("opacity", 0);
g.selectAll(".square")
.transition()
.duration(600)
.delay(function(d,i) {
return 5 * d.row;
})
.attr("opacity", 1.0)
.attr("fill", "#ddd");
}
function showGrid() {
g.selectAll(".count-title")
.transition()
.duration(0)
.attr("opacity", 0);
g.selectAll(".square")
.transition()
.duration(600)
.delay(function(d,i) {
return 5 * d.row;
})
.attr("opacity", 1.0)
.attr("fill", "#ddd");
}
function showGrid() {
g.selectAll(".count-title")
.transition()
.duration(0)
.attr("opacity", 0);
g.selectAll(".square")
.transition()
.duration(600)
.delay(function(d,i) {
return 5 * d.row;
})
.attr("opacity", 1.0)
.attr("fill", "#ddd");
}
function showGrid() {
g.selectAll(".count-title")
.transition()
.duration(0)
.attr("opacity", 0);
g.selectAll(".square")
.transition()
.duration(600)
.delay(function(d,i) {
return 5 * d.row;
})
.attr("opacity", 1.0)
.attr("fill", "#ddd");
}
Everything is a Transition
function showGrid() {
g.selectAll(".count-title")
.transition()
.duration(0)
.attr("opacity", 0);
g.selectAll(".square")
.transition()
.duration(600)
.delay(function(d,i) {
return 5 * d.row;
})
.attr("opacity", 1.0)
.attr("fill", "#ddd");
}
function showGrid() {
g.selectAll(".count-title")
.transition()
.duration(0)
.attr("opacity", 0);
g.selectAll(".square")
.transition()
.duration(600)
.delay(function(d,i) {
return 5 * d.row;
})
.attr("opacity", 1.0)
.attr("fill", "#ddd");
}
function showGrid() {
g.selectAll(".count-title")
.transition()
.duration(0)
.attr("opacity", 0);
g.selectAll(".square")
.transition()
.duration(600)
.delay(function(d,i) {
return 5 * d.row;
})
.attr("opacity", 1.0)
.attr("fill", "#ddd");
}
vallandingham.me/scroller.html
Dangers
In Scrolling
Scrolling is a continuation
Unless it is disrupted
How To Scroll
Mike Bostock
Scrolljacking
Scrolling Mental Model
Inconsistent Metaphor?
Alternatives
"I miss the days of the stepper."
Mobilescroll?
Wrong to Scroll?
Or Just Too Slow?
Swipe
The New Scroll?
Scrolltools
Libraries
graph-scrollSpecial Thanks To:
Adam Pearce
@adamrpearce
Irene Ros
@ireneros
Lynn Cherny
@arnicas
Thanks
vallandingham.me
@vlandham
Thanks
vallandingham.me
@vlandham