imager - pod4g/tool GitHub Wiki

Q3 OKR准备做一些基础功能

  1. 图片broken美化 ok
  2. 图片broken reload
  3. 图片loading
  4. 图片懒加载 ok
  5. 图片全屏查看 ok
  6. [新增] 图片裁切

这些基础功能需要应用在我们的产品线上,作为前端的基础服务。

目前完成了 图片全屏查看、图片懒加载

例子:http://t.diaox2.com/view/test/lyf/profile/

点击任意一张图片将会触发图片全屏查看

需要在手机端运行

javascript

/*
*
* Author: 李彦峰
* Licence: MIT
* Date: 2016-07-30
*
*/


(function(global, factory) {

  'use strict';

  // UMD setting
  if (typeof module === "object" && typeof module.exports === "object") {
    module.exports = global.document ?
      factory(global, true) :
      function(w) {
        if (!w.document) {
          throw new Error("Imager requires a window with a document");
        }
        return factory(w);
      };
  } else {
    factory(global);
  }

})(typeof window !== 'undefined' ? window : this, function(window, noGlobal) {

  'use strict';

  var document = window.document;
  var html = document.documentElement;
  var body = document.body;
  var viewportHeight = html.clientHeight;
  // var html = document.documentElement;

  // 工具方法
  // Object.assign是浅拷贝
  if (typeof Object.assign !== 'function') {

    Object.assign = function(target) {

      if (target == null) {
        throw new TypeError('Cannot convert undefined or null to object');
      }

      target = Object(target); // 转成对象

      // 处理除了target之外的参数
      for (var index = 1, source; source = arguments[index++];) { //eslint-disable-line

        if (source != null) {
          var keys = Object.keys(source);
          // 使用keys + for 替换掉 forin + hasOwnProperty 加快遍历速度
          // https://github.com/pod4g/tool/wiki/各种遍历的性能
          for (var i = 0, key; key = keys[i++];) { //eslint-disable-line
            target[key] = source[key];
          }
        }
      }
      return target;
    };
  }

  // 扩展HTMLElement原型
  Object.assign(HTMLElement.prototype, {

    addClass: function(klass) {
      this.classList.add(klass);
      return this;
    },

    removeClass: function(klass) {
      this.classList.remove(klass);
      return this;
    },

    toggleClass: function(klass) {
      this.classList.toggle(klass);
      return this;
    },

    hasClass: function(klass) {
      return this.classList.contains(klass);
    },

    html: function(content) {
      this.innerHTML = content;
      return this;
    },

    text: function(content) {
      this.textContent = content;
      return this;
    }

  });

  function getFirstOrAll(arr) {
    if (arr && arr.length === 1) {
      return arr[0];
    } else {
      return arr;
    }
  }

  /**
   * @param  {[String]} id
   * @return {[object Element]}
   */
  function getById(id) {
    return document.getElementById(id);
  }

  /**
   * @param  {[String]} calssName
   * @param  {[object Node]} parent
   * @return {[object HTMLCollection]}   [类数组]
   */
  function getByClass(calssName, p) {
    return getFirstOrAll((p || document).getElementsByClassName(calssName));
  }

  /**
   * @param  {[String]} calssName
   * @param  {[object Node]} parent
   * @return {[object HTMLCollection]}   [类数组]
   */
  function getByTag(calssName, p) {
    return getFirstOrAll((p || document).getElementsByTagName(calssName));
  }

  /**
   * @param  {[String]} tagName
   * @param  {[object Node]} parent
   * @return {[object HTMLCollection]} [类数组]
   */
  function parent(ele) {
    return ele.parentElement;
  }

  // lazy 模块
  // error image 美化
  // error image reload
  // image view
  // 图片懒加载模块
  // lazy.js
  // 图片breaken美化
  // breaken.js
  // 图片breaken reload
  // reload.js
  // 图片全屏查看
  // view.js
  // 图片滑动
  // swipe.js
  // util.js
  // 支持UMD
  function addEvent(element, type, fn, capture) {
    element.addEventListener(type, fn, isNullOrUndefined(capture) ? false : capture); //eslint-disable-line
  }

  // function removeEvent(element, type, fn){
  //      element.removeEventListener(type, fn);
  // }

  function queryFrom(pElement) {
    pElement = pElement || document;
    return function(selector) {
      return pElement.querySelectorAll(selector);
    };
  }

  function isElement(element) {
    return element && element.nodeType;
  }

  function create(tag) {
    return document.createElement(tag);
  }

  function isNullOrUndefined(un) {
    // return un == void 0;
    return un == null;
  }

  function clone(element, deep) {
    return element.cloneNode(isNullOrUndefined(deep) ? false : deep); //eslint-disable-line
  }

  function cssPrefix(key, value) {
    var list = ['-webkit-', '-moz-', '-ms-', '-o-', ' '];
    var ret = '';
    value = value || ret;
    for (var i = 0, prefix; prefix = list[i++];) { //eslint-disable-line
      ret += prefix + key + ':' + value + ';';
    }
    return ret;
  }

  function removeAttribute(element, attr) {
    element.removeAttribute(attr);
  }

  function takeOff(element) {
    var toRemovedAttrs = ['class', 'style', 'width', 'height', 'id', 'data-swipe'];
    for (var i = 0, attr; attr = toRemovedAttrs[i++];) { //eslint-disable-line
      removeAttribute(element, attr);
    }
    return element;
  }

  function getStyle(element, style) {
    return window.getComputedStyle(element, null)[style];
  }

  function doNothing(event) {
    if (event) {
      event.preventDefault();
      event.stopPropagation();
    }
  }

  var Swipe = function() {

    this.prefix = 'swipe-overlay';

    this.show = this.prefix + '-show';

    this.flag = 'data-swipe';

    this.event = 'click';

    this.index = 0;

    this.iw = window.innerWidth;

    this.ih = window.innerHeight;
    // 屏幕宽高比
    this.radio = this.iw / this.ih;
    // 创建基础容器
    this.createView();
    // 初始化
    this.init();
    // 绑定事件
    this.bindEvent();
  };

  Object.assign(Swipe.prototype, {

    createView: function() {
      // 最外层容器
      var wrapper = getById(this.prefix) || getByClass(this.prefix);
      var ul;
      if (!wrapper) {
        // 列表容器
        wrapper = create('div');
        wrapper.addClass(this.prefix);
        wrapper.id = this.prefix;

        wrapper.html('<p class="' + this.prefix + '-index-and-length">' +
          '<span class="' + this.prefix + '-index"></span> / <span class="' + this.prefix + '-length"></span>' +
          '</p>');

        ul = create('ul');
        wrapper.appendChild(ul);

        body.appendChild(wrapper);

      }

      this.wrapper = wrapper;

      this.ul = ul || getByClass(this.prefix + '-list', this.wrapper);
    },

    init: function() {

      var $ = queryFrom(body);

      var imgs = $('[' + this.flag + ']');

      this.swipeImgs = [];

      this.length = imgs.length;

      var ul = this.ul;

      var self = this;

      addEvent(body, self.event, function(e) {
        // 不阻止,否则会影响点击锚点
        // doNothing(e);
        var target = e.target;
        if (target.hasAttribute(self.flag)) {

          self.wrapper.addClass(self.show);

          for (i = 0; img = imgs[i++];) { //eslint-disable-line
            if (target === img) {
              self.index = i - 1;
              break;
            }
          }

          getByClass(self.prefix + '-index').text(self.index + 1);
          getByClass(self.prefix + '-length').text(self.length);

          if (self.ul.children.length > 0) {
            for (var i = 0, img; img = self.swipeImgs[i++];) { //eslint-disable-line
              parent(img).style.cssText = cssPrefix('transform', 'translate3d(' + self.iw * (i - 1 - self.index) + 'px,0,0)');
            }
          } else {
            var frag = document.createDocumentFragment();
            for (i = 0; img = imgs[i++];) { //eslint-disable-line
              var li = create('li');

              var dataSrc = img.dataset.src;
              var cloneImg;

              if (dataSrc && dataSrc !== img.src) {
                cloneImg = new Image();
                cloneImg.src = dataSrc;
                cloneImg.alt = img.alt || '';
                cloneImg.onload = function() {
                    self.adjustImg(this);
                  } //eslint-disable-line
              } else {
                cloneImg = takeOff(clone(img));
              }

              li.style.cssText = cssPrefix('transform', 'translate3d(' + self.iw * (i - 1 - self.index) + 'px,0,0)');
              li.appendChild(cloneImg);
              frag.appendChild(li);
              self.swipeImgs.push(cloneImg);
            }

            ul.appendChild(frag);

            addEvent(self.wrapper, self.event, function(ev) {
              doNothing(ev);
              this.removeClass(self.show);
            });

          }

          self.renderDOM();
        }
      });

    },
    adjustImg: function(img) {

      if ( isElement(img) ) {

        var width = parseInt(getStyle(img, 'width'));

        var height = parseInt(getStyle(img, 'height'));

        // 如果是一张小图,原样显示
        if (width < this.iw && height < this.ih) {
          return;
        }
        var scale = width / height;
        // 说明这个图片是一个宽图
        if (scale > this.radio) {
          img.width = this.iw;
        } else if (scale < this.radio) { // 长图
          img.height = this.ih;
        }
      }
    },
    renderDOM: function() {

      var swipeImgs = this.swipeImgs;

      // 处理要滑动的图片
      for (var i = 0, img; img = swipeImgs[i++];) { //eslint-disable-line

        this.adjustImg(img);
      }

    },

    bindEvent: function() {

      var self = this;

      var startHandler = function(e) {

        self.startX = e.touches[0].pageX;

        self.offsetX = 0;

        self.startTime = +new Date();
      };

      var moveHandler = function(e) {

        self.offsetX = e.touches[0].pageX - self.startX;

        var i = self.index - 1;

        var m = self.index + 2;

        var lis = this.children;

        for (; i < m; i++) {
          var li = lis[i];
          if (li) {
            li.style.cssText =
              cssPrefix('transform', 'translate3d(' + ((i - self.index) * self.iw + self.offsetX) + 'px,0,0)');
          }
        }

      };

      var endHandler = function() {

        if (self.offsetX === 0) {
          return;
        }

        // 屏幕的 1/5 为边界
        var boundary = self.iw / 5;

        var endTime = +new Date();
        // offsetX 左负,右正,左下(一页),右上(一页)
        // 慢操作
        if (endTime - self.startTime > 700) {
          // 向右滑动
          if (self.offsetX >= boundary) { // case 1
            // 进入上一页
            self.go(-1);
            // }else if(self.offsetX < boundary){ // case 2 bug
            // 向左滑动
          } else if (self.offsetX < -boundary) { // case 2 bug already fixed
            // bug描述
            // 如果是向右滑动一段很小的距离,offsetX是一个很小的正数,
            // 此时offsetX显然是小于boundary的
            // 此时期望走 case 3 ,即留在本页,但却走了case2,即进入下一页
            // 本来case2 是向左滑动的case,但是boundary前忘写了符号,所以就出现了这个
            // 困扰我2天的bug
            // 这个bug只有在慢操作下才会触发
            // 因为快操作向左滑动的case 已经有符号
            // 进入下一页
            self.go(1);
          } else { // case 3
            // 偏移太小,留在本页
            self.go();
          }
        } else { // 快操作

          if (self.offsetX >= 50) {
            // 进入上一页
            self.go(-1);
          } else if (self.offsetX < -50) {
            // 进入下一页
            self.go(1);
          } else {
            // 偏移太小,留在本页
            self.go();
          }

        }
      };


      addEvent(this.ul, 'touchstart', startHandler);
      addEvent(this.ul, 'touchmove', moveHandler);
      addEvent(this.ul, 'touchend', endHandler);

    },

    go: function(dir) {

      dir = dir || 0;

      var lis = this.ul.children;

      var nindex = this.index + dir;

      var length = this.length;
      // 索引超出处理
      if (nindex > length - 1) {

        nindex = length - 1;

      } else if (nindex < 0) {

        nindex = 0;

      }


      getByClass(this.prefix + '-index', this.wrapper).text(nindex + 1);

      var transition = cssPrefix('transition', 'all .2s ease-out');

      lis[this.index].style.cssText = cssPrefix('transform', 'translate3d(0,0,0)') + transition;

      lis[nindex].style.cssText = cssPrefix('transform', 'translate3d(0,0,0)') + transition;

      var p = nindex - 1;

      var n = nindex + 1;

      if (p >= 0) {
        lis[p].style.cssText = cssPrefix('transform', 'translate3d(' + (-this.iw) + 'px,0,0)') + transition;
      }
      if (n <= length - 1) {
        lis[n].style.cssText = cssPrefix('transform', 'translate3d(' + (this.iw) + 'px,0,0)') + transition;
      }

      this.index = nindex;

    }

  });

  

  // lazy-load模块
  // 判断一个元素是否在可视区(视口)内部
  function inViewport(element) {
    var top, scrollTop, total, offsetTop;
    if (isElement(element)) {
      top = element.getBoundingClientRect().top;
      if (top <= 0) {
        return false;
      }
      scrollTop = body.scrollTop || html.scrollTop;
      total = scrollTop + viewportHeight;
      offsetTop = scrollTop + top;
      if (offsetTop < total) {
        return true;
      }
    }
    return false;
  }

  function isLazyImg(img) {
        var dataSrc = img.dataset.src;
        if ( isElement(img) && img.tagName === 'IMG' && dataSrc && dataSrc !== img.src ) {
                return true;
        }
        return false;
    }

    function getShouldLazyLoadImgs() {
        var shouldLazyLoad = [];
        var lazyFlagElements = document.querySelectorAll('img[data-lazy-src]');
        // debugger;
        // 取出所有需要lazyload的图片
        // 去重
        // 去掉没有data-src的img
        for (var i = 0, ele; ele = lazyFlagElements[i++];) { //eslint-disable-line
            // if ( ele.tagName !== 'IMG' ) {
            //     // shouldLazyLoad.push.apply(shouldLazyLoad, );
            //     var imgs = ele.getElementsByTagName('img');
            //     for (var j = 0, img; img = imgs[j++];) {
            //         if (isLazyImg(img) && shouldLazyLoad.indexOf(img) === -1 ) {
            //             shouldLazyLoad.push(img);
            //         }
            //     }
            // } else if (isLazyImg(ele) && shouldLazyLoad.indexOf(ele) === -1) {
                shouldLazyLoad.push(ele);
            // }
        }
        return shouldLazyLoad;
    }
    var scrollHandler = function() {
        var shouldLazyLoad = getShouldLazyLoadImgs();
        console.log(shouldLazyLoad);
        return function() {
            for (var i = 0, img; img = shouldLazyLoad[i++];) { //eslint-disable-line
                if (inViewport(img)) {
                    img.src = img.dataset.lazySrc;
                    img.onerror = function(){ this.src = 'http://t.diaox2.com/view/test/lyf/promotion/images/cd.png';}
                }
            }
        }
    }();

    window.addEventListener('scroll', scrollHandler);

    scrollHandler();

  // addEvent(window, 'load', function(){
  //      var imgs = getByTag('img'), brokens = [], i = 0, img;
  //      while( img = imgs[i++] ){
  //        if( !img.complete || ( img.naturalWidth === 0 && img.naturalHeight === 0 ) ){
  //          brokens.push(img);
  //          var span = create('span');
  //          span.text('加载错误');
  //        }
  //      }
  //      console.log(brokens);
  // });


  function imager() {
    var swipe = new Swipe();
    imager = function() {
      return swipe;
    }
    return swipe;
  }

  if (!noGlobal) {
    window.imager = imager;
  }

  if (body.hasAttribute('data-img-swipe-on')) {
    imager();
    // getByTag('img')[0].click();
  }

  // alert(getComputedStyle(getById('node'),':before').content);
  // alert(getComputedStyle(getById('node'),':before').display);
  // alert(getComputedStyle(document.body,':before').display);

  // function getErrorImg(imgs, pseudoElt) {
  //   if (imgs) {
  //     imgs = [].concat(imgs);
  //   } else {
  //     imgs = getByTag('img')
  //   }
  //   pseudoElt = pseudoElt || ':before';
  //   console.log(ret);
  //   var ret = [];
  //   for (var i = 0, img; img = imgs[i++];) {
  //     var style = getComputedStyle(img, pseudoElt);
  //     var display = style.display;
  //     if (display === 'flex') { // chrome / firefox / opera
  //       ret.push(img);
  //     } else if (display === 'inline-block') { // safari
  //       ret.push(img);
  //     } else if (style.content.trim()) {
  //       ret.push(img);
  //     }
  //   }
  //   return ret;
  // }

  // console.log(getErrorImg());
  // console.log(null.toString()); // 触发上报
  // console.error('查询错误');
  // console.warn('id不应该为空');
  return imager;

});

css

.swipe-overlay{
    position: fixed;
    top: 0;
    right: 0;
    bottom: 0;
    left: 0;

    // background-color: rgba(51,51,51,1);
    background-color: #333;
    z-index: -1;
    opacity: 0;
    transition: all .2s ease-in-out;
    overflow: hidden;
  }

.swipe-overlay-show{
  z-index: 9999;
  // background-color: rgba(51,51,51,1);
  opacity: 1;
}

.swipe-overlay ul{
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.swipe-overlay ul li{
    display: flex;
    height: 100%;
    width: 100%;
    justify-content:center;
    align-items:center;
    overflow: hidden;
    position:absolute;
}

// .swipe-overlay ul li img{
//     width: auto;
//     height: auto;
// }

.swipe-overlay-index-and-length{
  position: fixed;
  font-size: 12px;
  color:#fff;
  z-index: 10000;
  bottom: 12px;
  left: 12px;
  width: 100%;
}
⚠️ **GitHub.com Fallback** ⚠️