Skip to content

Instantly share code, notes, and snippets.

@gmaclennan
Last active February 27, 2023 01:03
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save gmaclennan/11130600 to your computer and use it in GitHub Desktop.
Save gmaclennan/11130600 to your computer and use it in GitHub Desktop.
Fast 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 {
background-size: 100% auto;
position: absolute;
width: 100%;
}
.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>
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 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('div')
.data(dataSlice, function(d) {
return d.key;
});
reuseNodes.call(rows.enter(), rows.exit())
.attr('class', 'row')
.call(styleRows);
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;
d3.select(reusableNode)
.selectAll('img')
.classed('hidden', true);
return reusableNode;
}
}
return this.appendChild(document.createElement('div'));
});
}
}
function styleRows(selection) {
selection
.attr('class', 'row grid-' + imagesPerRow)
.style('height', imageSize + '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