|
<!DOCTYPE html> |
|
<meta charset="utf-8"> |
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
<style> |
|
|
|
@import url("image-grid.css"); |
|
|
|
body, html { |
|
padding: 0; |
|
margin: 0; |
|
} |
|
.row { |
|
display: block; |
|
background-size: 100% auto; |
|
position: absolute; |
|
} |
|
.hidden { |
|
opacity: 0; |
|
} |
|
img { |
|
margin: 0; |
|
border: 2px solid white; |
|
-webkit-transition: opacity 500ms; |
|
transition: opacity 500ms; |
|
} |
|
</style> |
|
|
|
<body> |
|
<div class="grid"></div> |
|
<script src="http://d3js.org/d3.v3.js"></script> |
|
<script src="//cdnjs.cloudflare.com/ajax/libs/async/0.7.0/async.js"></script> |
|
|
|
<script> |
|
var transformCSSProp = (function(property) { |
|
var prefixes = ['webkit', 'ms', 'Moz', 'O'], |
|
i = -1, |
|
n = prefixes.length, |
|
s = document.body.style; |
|
|
|
if (property.toLowerCase() in s) |
|
return property.toLowerCase(); |
|
|
|
while (++i < n) |
|
if (prefixes[i] + property in s) |
|
return '-' + prefixes[i].toLowerCase() + property.replace(/([A-Z])/g, '-$1').toLowerCase(); |
|
|
|
return false; |
|
})('Transform'); |
|
|
|
var flickrApiKey = 'ea621d507593aa247dcaa792268b93d7'; |
|
var maxImageSize = 150; |
|
var data = []; |
|
var buffer, lastHeight, dimensions, imagesPerRow, imagesPerPage, imageSize, nest; |
|
|
|
var q = async.queue(image2canvas, 10); |
|
|
|
// var outer = d3.select('div.grid') |
|
// .on('scroll', render); |
|
|
|
d3.select(window) |
|
.on('resize', setDimensions) |
|
.on('scroll', render); |
|
|
|
var inner = d3.select('div.grid'); |
|
|
|
// We pull down 500,000 images, but flickr only gives us 4,000 max, so there will be repeats |
|
for (var i = 0; i < 100; i++) { |
|
d3.json('https://api.flickr.com/services/rest/?' + |
|
'method=flickr.photos.search&' + |
|
'api_key=ea621d507593aa247dcaa792268b93d7&' + |
|
'tags=mountains,forest,beach&' + |
|
'sort=interestingness-desc&' + |
|
'media=photos&' + |
|
'extras=url_q&' + |
|
'format=json&' + |
|
'nojsoncallback=1&' + |
|
'per_page=500' + |
|
'page=' + i, |
|
function(err, json) { |
|
if (err) return console.log(err); |
|
data = data.concat(json.photos.photo.map(function(d) { |
|
return d.url_q; |
|
})); |
|
setDimensions(); |
|
}) |
|
} |
|
|
|
function setDimensions() { |
|
dimensions = [inner.node().clientWidth, innerHeight]; |
|
imagesPerRow = Math.ceil(dimensions[0] / maxImageSize); |
|
imagesPerPage = Math.ceil(dimensions[1] / maxImageSize); |
|
imageSize = dimensions[0] / imagesPerRow; |
|
|
|
buffer = imagesPerPage; |
|
|
|
nest = data.reduce(function(prev, item, i) { |
|
var group = Math.floor(i / imagesPerRow); |
|
(prev[group]) ? prev[group].value.push(item) : prev.push({ |
|
key: group, |
|
value: [item] |
|
}); |
|
return prev; |
|
}, []); |
|
|
|
var newHeight = Math.ceil(nest.length * imageSize) + 'px'; |
|
|
|
inner.style('height', newHeight); |
|
|
|
if (newHeight > dimensions[1] && lastHeight < dimensions[1]) { |
|
lastHeight = newHeight; |
|
setDimensions(); |
|
} |
|
|
|
//inner.style('background-image', 'url(grid-' + imagesPerRow + 'sm.png)'); |
|
inner.selectAll('div.row') |
|
.call(styleRows); |
|
|
|
inner.selectAll('img') |
|
.call(styleImages); |
|
|
|
render(); |
|
} |
|
|
|
function render() { |
|
if (!nest) return; |
|
var scrollY = window.scrollY; |
|
var count = imagesPerPage + buffer * 2; |
|
var offset = Math.max(0, Math.floor(scrollY / imageSize) - buffer); |
|
|
|
var dataSlice = nest.slice(offset, offset + count); |
|
|
|
// var pre = [], |
|
// post = []; |
|
|
|
// for (var i = 0; i < buffer * 2; i++) { |
|
// pre.push({ |
|
// key: offset - i - 1, |
|
// value: [] |
|
// }); |
|
// post.push({ |
|
// key: offset + count + i, |
|
// value: [] |
|
// }); |
|
// } |
|
|
|
// dataSlice = pre.concat(dataSlice, post); |
|
|
|
var rows = inner.selectAll('canvas') |
|
.data(dataSlice, function(d) { |
|
return d.key; |
|
}); |
|
|
|
reuseNodes.call(rows.enter(), rows.exit()) |
|
.attr('class', 'row') |
|
.call(styleRows) |
|
.each(drawImages); |
|
|
|
rows.exit() |
|
.remove(); |
|
|
|
// var images = rows.selectAll('img').data(function(d) { |
|
// return d.value; |
|
// }); |
|
|
|
// images.enter() |
|
// .append('img') |
|
// .classed('hidden', true) |
|
// .call(styleImages); |
|
|
|
// images |
|
// .attr('src', function(d) { |
|
// return d; |
|
// }) |
|
// .on('load', function() { |
|
// d3.select(this) |
|
// .classed('hidden', false); |
|
// }); |
|
|
|
// images.exit() |
|
// .remove(); |
|
|
|
function reuseNodes(exitNodes) { |
|
return this.select(function() { |
|
var reusableNode; |
|
for (var i = -1, n = exitNodes[0].length; ++i < n;) { |
|
if (reusableNode = exitNodes[0][i]) { |
|
exitNodes[0][i] = undefined; |
|
return reusableNode; |
|
} |
|
} |
|
return this.appendChild(document.createElement('canvas')); |
|
}); |
|
} |
|
} |
|
|
|
function drawImages(d) { |
|
var images = d.value, |
|
ctx = this.getContext('2d'); |
|
console.log(images); |
|
|
|
for (var i=0; i < images.length; i++) { |
|
image2canvas({ |
|
url: images[i], |
|
ctx: ctx, |
|
dx: i % imagesPerRow * imageSize, |
|
size: imageSize |
|
}) |
|
} |
|
} |
|
|
|
function image2canvas(task, callback) { |
|
var img = new Image(); |
|
|
|
img.crossOrigin = 'anonymous'; |
|
|
|
img.onload = function(e) { |
|
this.onload = null; |
|
task.ctx.drawImage(this, task.dx, 0, task.size, task.size); |
|
if (callback) callback(); |
|
}; |
|
|
|
img.src = task.url; |
|
} |
|
|
|
function styleRows(selection) { |
|
selection |
|
.attr('class', 'row grid-' + imagesPerRow) |
|
.attr('height', imageSize + 'px') |
|
.attr('width', dimensions[0] + 'px') |
|
.style(transformCSSProp, function(d, i) { |
|
return 'translate3d(0,' + d.key * imageSize + 'px,0)'; |
|
}); |
|
} |
|
|
|
function styleImages(selection) { |
|
selection |
|
.style('width', imageSize - 4 + 'px') |
|
.style('height', imageSize - 4 + 'px'); |
|
} |
|
|
|
//d3.select(self.frameElement).attr('scrolling', null); |
|
</script> |
|
</body> |