Skip to content

Instantly share code, notes, and snippets.

@gmaclennan
Forked from gmaclennan/README.md
Last active May 4, 2020 21:55
Show Gist options
  • Save gmaclennan/11232274 to your computer and use it in GitHub Desktop.
Save gmaclennan/11232274 to your computer and use it in GitHub Desktop.
Long-scrolling image grid

This is an implementation of a long-scrolling image grid. You should be able to smoothly scroll through 500,000 images from Flickr (you will see repeats because flickr only returns 4,000 images from a search). The images will delay when first loading, but once your browser has cached the images scrolling should be pretty smooth.

The trick to keeping it smooth is by only modifying CSS properties that are cheap to animate and by minimizing modifications to the DOM by reusing our exit nodes as enter nodes.

A full page of images (the same height as window height) is rendered above and below the viewable area. In addition, empty rows with a placeholder background are padded around for an additional 2 x window height. This is useful for mobile, which will not fire a scroll event until you stop scrolling.

.grid-3 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAlgAAADICAMAAAADQW+EAAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLfbtsgFMZfBdGbTZqNseu6oHpVZrtaLzxVWaTt1sG0QSl/BGRx82q72CPtFYbTOq0muVxxDt/vcA58V9eD6diWe7DmD0KV8O/vPxCIvoQ/8jZpTcU34uvB8u+Hbyt22DLSw+vP4GqggzSS+w4M8lE5OpSw6/Wa07Af0wiCo8RvS7gYD8DP9g5U2nKQx3nEkhSDgsQ4PycYfwJpgjOEE5SSCGN6XlB8AV4WDLfZ/p4u65uXu0JUwo33hiK03+/jfRZr+4AwIQQlKUrTKCgi96R8N0TKnU0Vau6YFcYLrcAYd2u98yWE0wjSnMoqFx/HiZmWaOgMwnGC3gjb9n2plCe180t+/77arZ4MR0vu9M4yHuRnI2xoZXnntV1p/Ti94t1Ge+022oCqAh/ajgk1xh+PQNvSW+V8pxi/rUsYMrEQPS3yL01dNaTCeIFxk5EmvSAka5Kiri+TLJ/YWrOd5MpPbP/KLmbZ0QvPNLfiF+9vrJbgODMV870U8708s/18L5ezLArNoP9+ekoF+4zbk29DcHI+V8HuNvj6HyHO8wXcGms6AAABhUlEQVR42u3VwQ0AIAgEwcPYf8v6sAUfGGc6gGygKnDfsAKExTNmkvX8FOefm6PRHC4WXiHCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBYIC2EhLBAWwkJYICyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLhIWwEBYIC2EhLBAWwkJYICyEhbBAWAgLYYGwEBbCAmEhLIQFwkJYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbAQFsICYSEshAXCQlgIC4SFsBAWCAthISwQFsJCWCAshIWwQFgIC2GBsBAWwgJhISyEBcJCWAgLYVkBwkJYCAuEhbAQFggLYSEsEBbCQlggLISFsEBYCAthgbBoqsoOcLEQFl/bkhMEkzzgSqAAAAAASUVORK5CYII=);
}
.grid-4 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAyAAAADICAMAAAAnUXUoAAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAcNpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLBTuMwEIZfxTIXVtrEcUKa2iIgSIKWQ1aoVIJr6hhqldiW7W5DX43DPtK+wjqlKatVwSfP+P/GM/Z/ftnrhq24Awv+LGQO/7z9hkC0OXxI66jWBV+KH1vD77c/52y7YqSFlxfgvKd9pzvuGtB3L9LSPodNqxac+v2QRhDsJG6Vw6vhADzWd6BQhoM0TAMWxRhkJMTpGcH4O4gjnCAcoZgEGNOzjOIJ2C/obzPtE52VN/u7fJTDpXOaIrTZbMJNEirzjDAhBEUxiuPAKwL7Kl3TB9KejBVKbpkR2gklwRA3C7V2OYTjCJ0+lJU23I0TMtWhvtEIhxH6R1jXX0u77qC2bsafvlbb+avmaMatWhvGvfxkgDUtDG+cMnOlXsZXvFsqp+xSaVAU4LRumJBD/G0H1DW9ldY1kvHbMoc+EwrR0mkSVdU0mRQ4STGuElLFE0KSKsrKchol6ciWiq07Lt3Ith9s9ik7eOGd5kb84u2NUR3YzUzFkV6y9Loqi+q6wPjqeC/vbHuklz1bfMoi3wz676fHlLfPsD341gcH53Pp7W68r/8CqsjynLxHnmMAAAGtSURBVHja7daxCQAgDEXBH3H/lbWwtgk2wt0GCTySqgA3wwpAINAyk6zvpzh/ojnM8XwOFwS8WCAQEAgIBAQCAgGBgEBAIIBAQCAgEBAICAQEAgIBgYBAQCCAQEAgIBAQCAgEBAICAYGAQACBgEBAICAQEAgIBAQCAgGBgEAAgYBAQCAgEBAICAQEAgIBgQACAYGAQEAgIBAQCAgEBAICAYEAAgGBgEBAICAQEAgIBAQCAgEEAgIBgYBAQCAgEBAICAQEAgIBBAICAYGAQEAgIBAQCAgEBAIIBAQCAgGBgEBAICAQEAgIBAQCCAQEAgIBgYBAQCAgEBAICAQEAggEBAICAYGAQEAgIBAQCAgEEAgIBAQCAgGBgEBAICAQEAgIBBAICAQEAgIBgYBAQCAgEBAIIBAQCAgEBAICAYGAQEAgIBAQCCAQEAgIBAQCAgGBgEBAICAQQCAgEBAICAQEAgIBgYBAQCAgEEAgIBAQCAgEBAICAYGAQEAggEBAICAQEAgIBAQCAgGBgEBAIIBAQCAgEBAICAQEAgIBgYBAQCCAQKChyg7ABQGBwGMbFwAFk3U8JlkAAAAASUVORK5CYII=);
}
.grid-5 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAA+gAAADICAMAAABMOTt9AAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLfTtswFMZfxTI3Q1riOCG0tgiIJUHjIhPqKm23qWOoVfxHtruGvtou9ki8Ak4hBU0KvvI5/n7H59jfxVVvWrbhHqz4g1AFfP77DwLRFfBX3iSNKflafN9b/nP/Y8n2G0Y6eHUJLnraSyO5b0EvH5WjfQHbTq84DfshjSA4SPymgNfDAfjd3IFSWw7yOI9YkmIwIzHOzwjGX0Ga4AzhBKUkwpiezSg+B28Lhttsd08X1c3bXSEq4Np7QxHa7XbxLou1fUCYEIKSFKVpFBSRe1K+7SPlTsYKFXfMCuOFVmCI25Xe+gLCcQRpjmWViw/jxExL1LcG4ThBH4RN87lUyqPa+QW//1ztlk+GowV3emsZD/KTATa0tLz12i61fhxf8W6tvXZrbUBZgi9Ny4Qa4tMD0DT0VjnfKsZvqwKGTCxER+dZUtfz7LrEWY5xnZE6PSckq5NZVc2TLB/ZSrOt5MqPbPfOfptkBy+80tyKP7y7sVqCw8xUTPcyn+7lle2meyGTLArNoP9+ekwF+wzbo29DcHQ+V8HuNvj6BYEs8iw3C+SJAAAB1klEQVR42u3XwQkAIAwEQU/sv2UVfFtAwgxYQAKLmgygu2kFIHSggXXPLj/F+3+Ywxzm+MzhRgdPd0DogNABoQNCB4QOCB0QOggdEDogdEDogNABoQNCB4QOQgeEDggdEDogdEDogNABoYPQrQCEDggdEDogdEDogNABoQNCB6EDQgeEDggdEDogdEDogNBB6IDQAaEDQgeEDggdEDogdBA6IHRA6IDQAaEDQgeEDggdEDoIHRA6IHRA6IDQAaEDQgeEDkIHhA4IHRA6IHRA6IDQAaGD0AGhA0IHhA4IHRA6IHRA6IDQQeiA0AGhA0IHhA4IHRA6IHQQOiB0QOiA0AGhA0IHhA4IHYQOCB0QOiB0QOiA0AGhA0IHhA5CB4QOCB0QOiB0QOiA0AGhg9ABoQNCB4QOCB0QOiB0QOggdEDogNABoQNCB4QOCB0QOiB0EDogdEDogNABoQNCB4QOCB2EDggdEDogdEDogNABoQNCB6EDQgeEDggdEDogdEDogNABoYPQAaEDQgeEDggdEDogdEDoIHRA6IDQAaEDQgeEDggdEDoIHRA6IHRA6IDQAaEDQgeEDggdhA4IHRA6IHRA6IDQAaEDQgehA+0kdgBudEDoQAEHtdsGk1XoRdoAAAAASUVORK5CYII=);
}
.grid-6 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABLAAAADICAMAAADPwehCAAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbdpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLRbtsgFIZfBdGbVZqNsesmoLrVZDtaLzxVWaTtlmDaoNSAgCxuXm0Xe6S+QnFap9UklyvO4f8O58B/ddMbxrfCg7V4kKqAz3//QSDbAv7Km6QxpdjI7wcrfh5+rPhhy0kLb67BVU/7znTCM9B3j8rRvoCs1WtBw35IIwiOEr8t4LfhAPxu7kCprQB5nEc8STGYkRjnFwTjryBNcIZwglISYUwvZhRfgrcFw222vafLavF2V4gKuPHeUIT2+328z2JtHxAmhKAkRWkaBUXknpRnfaTc2VihEo5babzUCgwxW+udLyAcR+jMqaxy8XGcmOsO9cwgHCfog7BpPpd23Unt/FLcf652qycj0FI4vbNcBPnZABtaWsG8tiutH8dXvNtor91GG1CW4EvDuFRDfH4EmobeKueZ4uK2KmDIxFK2dJ4ldT3P6hJnOcZ1Rur0kpCsTmZVNU+yfGQrzXedUH5k23d2MckOXnilhZV/RLuwugPHmamc7qWc7uWVbad7qSZZFJpB//30mAr2GbYn34bg5Hyhgt1t8PULk7rySk0aGd0AAAIVSURBVHja7dfRDQAQEETBW9F/y3xoQSSY6WDv44WkAO7QnAAQLIDNelWN61esf60ddtjx+A4vLMCXEECwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQIQLADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsQLCcABAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQIQLADBAgQLQLAAwQIQLADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwAMECECwAwQIEC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQIQLADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMECBAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQIQLADBAgQLQLAAwQIQLADBAgQLQLAABAsQLADBAhAsQLAABAtAsADBAhAsAMECBAtAsAAECxAsAMECECxAsAAEC0CwAMECECwAwQIEC0CwAMECECwAwQIEC0CwAAQLECwAwQIQLECwAAQLQLAAwQIQLADBAgQLQLAABAsQLICjEjcAvLAABAv41ARuwgeTHPy0wAAAAABJRU5ErkJggg==);
}
.grid-7 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABXgAAADICAMAAABlJ3nXAAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLRbtsgFIZfBdGbTZoN2HUSUL2qsROtF56qNNJ262DaoNSAgCxuXq0Xe6S9wnBap9MklyvO4f8O58B/dd2Zmu+EBxvxKFUO/7z8hkA2OfyRVbgyhdjKb0cr7o/f1/y447SB11/BVce61rTC16Brn5RjXQ7rRm8EC/s+jSA4Sfwuhzf9AfhZ3YFCWwGyOIs4TgiY0phkl5SQLyDBJEUEo4RGhLDLKSMT8LZguM02D2xVLt/uClEOt94bhtDhcIgPaaztIyKUUoQTlCRRUETuWfm6i5S7GCqUwnErjZdagT6uN3rvcwiHEVpzLqtcfBon5rpFXW0QiTH6R1hVH0vb9qx2fiUePla79bMRaCWc3lsugvyihw0rrKi9tmutn4ZXvNtqr91WG1AU4FNVc6n6+PMJqCp2q5yvFRe3ZQ5DJpayYfPZcjLHc1qQNCNkkdJFMqE0XeBpWc5wmg1sqfm+FcoPbPPO3oyyvRdeaWHlL9EsrW7BaWYmx3uZjvfyyjbjvcxGWRSaQf/99JAK9um3Z9+G4Ox8oYLdbfD1X8YD8oka/5jhAAACPUlEQVR42u3YUQoAEBBFUSP73zLKFjQZnVMW8HzcJKIBkKm7AgDhBfja2GeWX3H+S+ywww47Suzw4gVIJrwAwgsgvAAIL4DwAiC8AMILgPACCC+A8AIgvADCC4DwAggvAMILILwAwguA8AIILwDCCyC8AAgvgPACCC8AwgsgvAAIL4DwAiC8AMILILwACC+A8AIgvADCC4DwAggvgPACILwAwguA8AIILwDCCyC8AMILgPACCC8AwgsgvAAIL4DwAggvAMILILwACC+A8AIgvADCCyC8AAgvgPACILwAwguA8AIIL4DwAiC8AMILgPACCC8AwgsgvADCC4DwAggvAMILILwACC+A8AIILwDCCyC8AAgvgPACILwAwguA8AIIL4DwAiC8AMILgPACCC8AwgsgvADCC4DwAggvAMILILwACC+A8AIILwDCCyC8AAgvgPACILwAwgsgvAAIL4DwAiC8AMILgPACCC+A8AIgvADCC4DwAggvAMILILwAwguA8AIILwDCCyC8AAgvgPACCC8AwgsgvAAIL4DwAiC8AMILILwACC+A8AIgvADCC4DwAggvgPACILwAwguA8AIILwDCCyC8AMILgPACCC8AwgsgvAAIL4DwAggvAMILILwACC+A8AIgvADCCyC8AAgvgPACILwAwguA8AIILwDCCyC8AMILgPACCC8AwgsgvAAIL4DwAggvAMILILwACC+A8AIgvADCCyC8AAgvgPACILwAL4twBwBevADCC8A1C0GmCJNtkqc1AAAAAElFTkSuQmCC);
}
.grid-8 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABkAAAADICAMAAACH4d0aAAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLRbtsgFIZfBdGbVpoN2HVSUN1qsROtF66qNNJ662DaoNSAgCxuXm0Xe6S9wnBaZ9MklyvO4f8O58B/fduZmm+FB2vxIlUOf//8BYFscvg9q3BlCrGR3w5WPB7uV/yw5bSBtzfgumNda1rha9C1r8qxLod1o9eChX2fRhAcJX6bw6/9AXiqHkChrQBZnEUcJwRMaUyyS0rIF5BgkiKCUUIjQtjllJEJ+Fgw3GabZ7YsFx93hSiHG+8NQ2i/38f7NNb2BRFKKcIJSpIoKCL3pnzdRcqdDRVK4biVxkutQB/Xa73zOYTDCK05lVUuPo4Tc92irjaIxBj9I6yqz6Vte1I7vxTPn6vd6s0ItBRO7ywXQX7Ww4YVVtRe25XWr8MrPmy0126jDSgKcF7VXKo+vjgCVcXulPO14uKuzGHIxFI2bHa1mMzwrCxImhEyT+k8mVCazvG0LK9wmg1sqfmuFcoPbPOXnY+yvRfeaWHlD9EsrG7BcWYmx3uZjffyzjbjvRSjLArNoP9+ekgF+/Tbk29DcHK+UMHuNvj6D+CG8q7dt0GuAAACZ0lEQVR42u3YsQ0AIQwEwfOL/luG4CuAwAJppoNzsoKqAMC+zwkAEBAA2owk8/kV/z+cHXbYYYcdjTu8QAA4IiAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICgIA4AQACAoCAACAgAAgIAAgIAAICgIAAICAACAgACAgAAgKAgAAgIAAICAAICAACAoCAACAgAAgIAAgIAAICgIAAICAACAgACAgAAgKAgAAgIAAICAAICAACAoCAACAgAAgIAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAACAgACAgAAgIAAICgIAAICAAICAACAgAAgKAgAAgIAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICgIAAgIAAICAACAgAAgKAgACAgAAgIAAICAACAoCAAICAACAgAAgIAAICAAICgIAAICAACAgAAgIAAgKAgADQr8oNAPACAUBAALjaAi6HCZMSWOITAAAAAElFTkSuQmCC);
}
.grid-9 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABwgAAADICAMAAADLAkWOAAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbdpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLfbtsgFMZfBdGbVZrNH9dNQXWryU60XniqskjdrYNpg1IDArK4ebVd7JH2CsNpnU2VXK44h+93OAe+69veNmIrA1jLJ6UL+OfXbwhUW8CHvMa1LeVGfT04+f3wbSUOW8FaeHsDrnved7aToQF996w97wvYtGYtedwPaQTBURK2BfwyHIAf9T0ojZMgT/NEYErAjKUkv2CEfAYUkwwRjChLCOEXM04uwduC8TbXPvJltXi7K0YF3IRgOUL7/T7dZ6lxT4gwxhCmiNIkKhL/okPTJ9qfjRUq6YVTNiijwRA3a7MLBYTjCJ09ldU+PY6TCtOhvrGIpBj9J6zrj6Vdd1L7sJSPH6v96sVKtJTe7JyQUX42wJaXTjbBuJUxz+Mr3m9MMH5jLChL8KluhNJDfH4E6prfaR8aLeRdVcCYSZVqebXIrzCtaEmynJB5xub0krFsjmdVdYWzfGQrI3ad1GFk239sNskOXnilpVM/ZbtwpgPHmbma7gVP9/LKttO9kEkWxWbQu58eU9E+w/bk2xicnC91tLuLvv4Lg5byMtgN5VIAAAKNSURBVHja7dixDQAgDAPBBLH/ylAwARIN8t0GTvOC7gKAXMMJABBCAAg1q2p9v+L879phhx122GHH9Q4vQgCiCSEAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQggAQgiAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAKAEAIghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAghAAIoRMAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQAIIQA8Eq3GwDgRQgAQggAcTY1ZQqTlfIMnAAAAABJRU5ErkJggg==);
}
.grid-10 {
background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAB9AAAADICAMAAACHxIozAAAABlBMVEXX19f///8qtJEjAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAbhpVFh0WE1MOmNvbS5hZG9iZS54bXAAAQAAADiNfZLfbtsgFMZfBdGbTZqNseOkoHrVZCdaLzxVWaTtlmDaoJQ/ArK4ebVd7JH2CsNpnU2TXK44h+93OAe+m9veMr4XAWzFo9QV/P3zFwSyq+C3ss1aW4ud/Hxy4uvpy4af9px08PYjuOlpr6wSgYFePWlP+wqyzmwFjfshjSA4S8K+gp+GA/C9vQe1cQKUaZnwLMdgQVJczgjGH0Ce4QLhDOUkwZjOFhTPweuC8TbXPdB1s3q9K0YV3IVgKULH4zE9FqlxjwgTQlCWozxPoiLxzzqwPtH+aqzQCM+dtEEaDYaYbc0hVBCOIyh7Kat9eh4n5UahnlmE0wz9I2zbt6VKXdQ+rMXD22q/ebYCrYU3B8dFlF8NsKW1EywYtzHmaXzF+50Jxu+MBXUN3rWMSz3E789A29I77QPTXNw1FYyZVMqONqvyOsubeY2LEuNlQZb5nJBimS2a5jorypFtDD8oocPIdn/ZxSQ7eOGFFk7+EN3KGQXOM1M53ctsupcXtpvupZxkUWwG/ffTYyraZ9hefBuDi/OFjnZ30dd/AI+G8kL8b5mfAAACs0lEQVR42u3ZMRIAEAxFwcS4/5Up9CqFsHuDr3mTkRkAQHXNEwCAoAMAF+gRMcqvWP8Gdthhhx122PHtDhc6ADxA0AFA0AEAQQcABB0AEHQAEHQAQNABAEEHAAQdAAQdABB0AEDQAQBBBwBBBwAEHQAQdABA0AFA0AEAQQcABB0AEHQAEHQAQNABAEEHAAQdAAQdABB0AEDQAQBBBwBBBwAEHQAQdABA0AFA0AEAQQcABB0AEHQAEHQAQNABAEEHAAQdAAQdABB0AEDQAQBBBwAEHQAEHQAQdABA0AEAQQcAQQcABB0AEHQAQNABQNABAEEHAAQdABB0ABB0AEDQAQBBBwAEHQAEHQAQdABA0AEAQQcAQQcABB0AEHQAQNABQNABAEEHAAQdABB0ABB0AEDQAQBBBwAEHQAEHQAQdABA0AEAQQcAQQcABB0AEHQAQNABAEEHAEEHAAQdABB0AEDQAUDQAQBBBwAEHQAQdAAQdABA0AEAQQcABB0ABB0AEHQAQNABAEEHAEEHAAQdABB0AEDQAUDQAQBBBwAEHQAQdAAQdABA0AEAQQcABB0ABB0AEHQAQNABAEEHAEEHAAQdABB0AEDQAUDQAQBBBwAEHQAQdAAQdABA0AEAQQcABB0AEHQAEHQAQNABAEEHAAQdAAQdABB0AEDQAQBBBwBBBwAEHQAQdABA0AFA0AEAQQcABB0AEHQAEHQAQNABAEEHAAQdAAQdABB0AEDQAQBBBwBBBwAEHQAQdABA0AFA0AEAQQcABB0AEHQAEHQAQNABAEEHAAQdAAQdABB0AEDQAQBBBwAEHQAEHQAQdABA0AEAQQcAQQcABB0AEHQAQNABQNABAEEHAAQdABB0ABB0AEDQAQBBBwAEHQAEHQAQdABA0AGAvUxvAAAudABA0AGAAyZWQAuTetCSfgAAAABJRU5ErkJggg==);
}
<!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>
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
html, body, iframe {
padding: 0;
margin: 0;
border: 0;
width: 100%;
height: 100%;
}
iframe {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
}
</style>
<!-- bl.ocks.org uses an iframe with scrolling="no"
which for some reason cannot be overridden with
self.frameSet.removeAttribute(scrolling)
so we embed another iframe with scrolling enabled
Yuck. -->
<iframe src="image-grid.html" marginwidth="0" marginheight="0"></iframe>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment