Facebook
From Matt91111, 3 Years ago, written in JavaScript.
Embed
Download Paste or View Raw
Hits: 155
  1. <template>
  2.     <!-- pictures grid container -->
  3.     <div id="pictures-grid-container" ref="pictureGrid">
  4.         <!-- columns -->
  5.         <template v-for="(column, columnIndex) in columns">
  6.             <div :key="columnIndex" :style="columnWidth" class="column">
  7.                 <!-- render each image in column -->
  8.                 <template v-for="(element, index) in column">
  9.                     <!-- desktop -->
  10.                     <!-- click opens preview, as overlay for desktop -->
  11.                     <div
  12.                         v-if="!isMobile"
  13.                         :key="index"
  14.                         class="picture-container"
  15.                         @click="emitClick(element.hash)"
  16.                     >
  17.                         <!--suppress JSUnusedGlobalSymbols -->
  18.                         <picture-frame
  19.                                 :id="`picture-id-${index}`"
  20.                                 v-observe-visibility="{
  21.                                    callback: (isVisible, entry) => imageVisibilityChanged(isVisible, entry, index),
  22.                                    threshold: 0.1,
  23.                                }"
  24.                                 class="picture"
  25.                                 :path="element.relativeThumbnailPath"
  26.                                 :width="element.thumbnailWidth"
  27.                                 :height="element.thumbnailHeight"
  28.                                 :columns-count="columnsCount"
  29.                                 :frame="true"
  30.  
  31.                                 :class="{vertical: element.height > element.width}"
  32.                                 @loading-start="addToLoading(columnIndex, index)"
  33.                                 @loading-completed="removeFromLoading(columnIndex, index)"
  34.                         />
  35.                         <div class="picture-overlay">
  36.                             <div class="picture-overlay-content">
  37.                                 {{ stringTable.clickToGetImageDetails }}
  38.                             </div>
  39.                         </div>
  40.                     </div>
  41.                     <!-- mobile -->
  42.                     <!-- tap will redirect to '/picture/:hash' -->
  43.                     <router-link
  44.                             v-else
  45.                             :key="element"
  46.                             :to="`/image/${element.hash}`"
  47.                             class="picture-container"
  48.                     >
  49.                         <picture-frame
  50.                                 :id="`picture-id-${index}`"
  51.                                 v-observe-visibility="{
  52.                                    callback: (isVisible, entry) => imageVisibilityChanged(isVisible, entry, index),
  53.                                    threshold: 0.1,
  54.                                }"
  55.                                 class="picture"
  56.                                 :path="element.relativeThumbnailPath"
  57.                                 :width="element.thumbnailWidth"
  58.                                 :height="element.thumbnailHeight"
  59.                                 :columns-count="columnsCount"
  60.                                 :frame="true"
  61.  
  62.                                 :class="{vertical: element.height > element.width}"
  63.                                 @loading-start="addToLoading(columnIndex, index)"
  64.                                 @loading-completed="removeFromLoading(columnIndex, index)"
  65.                         />
  66.                         <div class="picture-overlay">
  67.                             <div class="picture-overlay-content">
  68.                                 {{ stringTable.clickToGetImageDetails }}
  69.                             </div>
  70.                         </div>
  71.                     </router-link>
  72.                 </template>
  73.             </div>
  74.         </template>
  75.     </div>
  76. </template>
  77.  
  78. <!--suppress JSUnusedGlobalSymbols -->
  79. <script>
  80.     import stringTable from '../constants/stringTable'
  81.     import PictureFrame from '../components/pictureFrame'
  82.     import galleryData from '../../public/galleryData.json'
  83.     import { isMobile } from 'mobile-device-detect'
  84.     import { debounce } from 'debounce'
  85.  
  86.     export default {
  87.         name: "PicturesGrid",
  88.         components: {
  89.             PictureFrame
  90.         },
  91.         props: {
  92.             imagesToLoadPerColumn: {
  93.                 type: Number,
  94.                 required: false,
  95.                 default: 4
  96.             }
  97.         },
  98.         data: function() {
  99.             return {
  100.                 //helpers
  101.                 stringTable: stringTable,
  102.                 isMobile: isMobile,
  103.  
  104.                 columns: {
  105.                     columns: [],
  106.                 },
  107.                 columnsCount: 0,
  108.                 columnOffset: 0,
  109.                 columnWidth: {width: '100%'},
  110.  
  111.                 loading: [],
  112.                 loaded: [],
  113.  
  114.                 imageRenderOffset: 0,
  115.                 renderTotal: 0,
  116.                 renderStep: null
  117.             }
  118.         },
  119.         created: function () {
  120.             this.renderTotal = Object.keys(galleryData.files).length - 1
  121.  
  122.             this.calculateColumns()
  123.             this.updateColumns()
  124.         },
  125.         mounted: function () {
  126.             this.$nextTick(() => {
  127.                 window.addEventListener('resize', debounce(this.onResize.bind(this), 200))
  128.  
  129.                 this.requestRender(this.renderStep)
  130.             })
  131.         },
  132.         beforeDestroy: function() {
  133.             window.removeEventListener('resize', this.onResize)
  134.         },
  135.         methods: {
  136.             // -- component state management
  137.             calculateColumns: function() {
  138.                 if(window.matchMedia("(min-width: 2000px)").matches) {
  139.                     this.columnsCount = 4
  140.                 } else if(window.matchMedia("(min-width: 1400px)").matches) {
  141.                     this.columnsCount = 3 // huge screens
  142.                 } else if(window.matchMedia("(min-width: 700px)").matches) {
  143.                     this.columnsCount = 2 // desktops, laptops, tablets
  144.                 } else {
  145.                     this.columnsCount = 1 // mobile devices
  146.                 }
  147.  
  148.                 this.renderStep = this.imagesToLoadPerColumn * this.columnsCount
  149.             },
  150.             updateColumns: function() {
  151.                 this.columns = []
  152.  
  153.                 for(let i = 0; i < this.columnsCount; i++) {
  154.                     this.columns.push([])
  155.                 }
  156.  
  157.                 this.columnWidth.width = Math.round(100 / this.columnsCount) + '%'
  158.             },
  159.             relocateImages: function() {
  160.                 const oldRenderOffset = this.imageRenderOffset
  161.  
  162.                 this.calculateColumns()
  163.                 this.updateColumns()
  164.  
  165.                 //reset current render progress
  166.                 this.imageRenderOffset = 0
  167.                 this.columnOffset = 0
  168.  
  169.                 //rerender images, but with updated columns
  170.                 this.requestRender(oldRenderOffset)
  171.             },
  172.             // -- grid items rendering
  173.             //add images to load
  174.             insert: function() {
  175.                 //make sure we can render
  176.                 if(this.imageRenderOffset + 1 > this.renderTotal) {
  177.                     this.emitAllElementsRendered()
  178.                     return
  179.                 }
  180.  
  181.                 //reset placement counter
  182.                 if(this.columnOffset + 1 > this.columnsCount) {
  183.                     this.columnOffset = 1
  184.                     //increment
  185.                 } else {
  186.                     this.columnOffset++
  187.                 }
  188.  
  189.                 //update offset
  190.                 this.imageRenderOffset++
  191.  
  192.                 //add element to current column, columnOffset isn't starting with 0,
  193.                 //so we have to fix this
  194.                 this.columns[this.columnOffset - 1].push(
  195.                     galleryData.files[this.imageRenderOffset]
  196.                 )
  197.                 //this.rendering[this.imageRenderOffset] = galleryData.files[this.imageRenderOffset]
  198.             },
  199.             requestRender: function(step = null) {
  200.                 // console.log(`requesting render`)
  201.                 // this._debugRender()
  202.  
  203.                 if(step === null)
  204.                     step = this.renderStep
  205.  
  206.                 for(let i = 0; i < step; i++)
  207.                     this.insert()
  208.             },
  209.             imageVisibilityChanged: function(isVisible, entry, index) {
  210.                 if(index === undefined || index == null)
  211.                     return
  212.  
  213.                 if((index + 2) > (this.imageRenderOffset / this.columnsCount)) {
  214.                     //don't load if more then half of dynamically loaded images
  215.                     //are still loading
  216.                     if(this.loading.length < (this.renderStep / 2))
  217.                         this.requestRender(this.renderStep)
  218.                 }
  219.             },
  220.             // -- component events (emit)
  221.             emitClick: function (hash) {
  222.                 this.$emit('clicked', hash)
  223.             },
  224.             emitAllElementsRendered: function () {
  225.                 this.$emit('all-items-rendered')
  226.             },
  227.             // -- images states handling
  228.             addToLoading: function (column, index) {
  229.                 const element = [column, index]
  230.  
  231.                 if(this.loaded.includes(element))
  232.                     return
  233.  
  234.                 this.loading.push(element)
  235.             },
  236.             removeFromLoading: function (column, index) {
  237.                 const element = [column, index]
  238.  
  239.                 this.loaded.push(element)
  240.  
  241.                 const loadingIndex = this.loading.findIndex(e => e === element)
  242.                 this.loading.splice(loadingIndex, 1)
  243.             },
  244.             // -- DOM events handlers
  245.             onResize: function() {
  246.                 const oldColumnCount = this.columnsCount
  247.  
  248.                 this.calculateColumns()
  249.  
  250.                 //adjust columns to new view size and relocate images to
  251.                 //specified columns
  252.                 if(oldColumnCount !== this.columnsCount)
  253.                     this.relocateImages()
  254.             },
  255.             _debugRender: function () {
  256.                 console.log(`can: ${this.loading.length < 3}, loading: ${this.loading.length},  offset: ${this.imageRenderOffset}, total: ${this.renderTotal}`)
  257.             }
  258.         }
  259.     }
  260. </script>
  261.  
  262. <style scoped lang="less">
  263.     @import "../styles/common";
  264.     @import "../styles/fonts";
  265.     @import "../styles/contentAnimations";
  266.  
  267.     #pictures-grid-container {
  268.         margin: 0;
  269.         padding: 0;
  270.  
  271.         width: 100%;
  272.  
  273.         display: flex;
  274.  
  275.         justify-content: flex-start;
  276.         align-items: flex-start;
  277.  
  278.         flex-direction: row;
  279.         flex-wrap: wrap;
  280.  
  281.         .column:first-child {
  282.             margin-top: 5%;
  283.         }
  284.     }
  285.  
  286.     .column {
  287.         display: flex;
  288.  
  289.         justify-content: center;
  290.         align-items: center;
  291.  
  292.         flex-direction: row;
  293.         flex-wrap: wrap;
  294.  
  295.         /*width: 100%;*/
  296.         height: auto;
  297.     }
  298.  
  299.     .picture-container {
  300.         position: relative;
  301.  
  302.         margin: 5% 2.5%;
  303.  
  304.         transition: all 0.30s;
  305.         cursor: pointer;
  306.  
  307.         &:hover {
  308.             .custom-rules-pc({
  309.                 box-shadow: 0 73px 300px -10px rgba(186,186,186,0.20);
  310.                 transform: translateY(-2%);
  311.             });
  312.         }
  313.     }
  314.  
  315.     .picture-overlay {
  316.         z-index: 100;
  317.  
  318.         position: absolute;
  319.  
  320.         left: 0;
  321.         top: 0;
  322.         width: 100%;
  323.         height: 100%;
  324.  
  325.         background-color: transparent;
  326.  
  327.         transition: all 0.30s;
  328.  
  329.         &:hover {
  330.             .custom-rules-pc({
  331.                 background-color: fade(@color-background, 65%);
  332.             });
  333.  
  334.             .picture-overlay-content {
  335.                 opacity: 1;
  336.             }
  337.         }
  338.  
  339.         .picture-overlay-content {
  340.             position: absolute;
  341.  
  342.             color: @color-foreground-primary;
  343.  
  344.             opacity: 0;
  345.  
  346.             top: 50%;
  347.             left: 50%;
  348.  
  349.             width: 75%;
  350.             height: 30%;
  351.  
  352.             text-align: center;
  353.             font-size: 28pt;
  354.  
  355.             transform: translate(-50%, -50%);
  356.  
  357.             transition: all 0.30s;
  358.         }
  359.     }
  360. </style>