放大镜

前置知识

  1. 鼠标焦点丢失的问题:
    1. 鼠标移动过快,鼠标会脱离拖拽的盒子,在盒子外面鼠标移动无法触发盒子的 mousemove,盒子不会再跟着计算最新的位置;
    2. 在盒子外面松开鼠标,也不会触发盒子的 mouseup,导致 mousemove 事件没有被移除,鼠标重新进入盒子,不管是否按住,盒子都会跟着走;
  2. 解决鼠标焦点丢失的问题:
    1. IE 和火狐浏览器中的解决方案:setCapture/releaseCapture 可以实现把元素焦点和鼠标绑定在一起 (或者移除绑定) 的效果,来防止鼠标焦点丢失;
    2. 谷歌中的解决方案:孙猴子 (鼠标) 蹦的在欢快,也逃离不了如来佛祖 (document) 的五指山,所以在项目中 moveup 方法绑定给 document 而不是盒子;
  3. 注意:在不确定当前元素的某个事件行为是否可能绑定多个方法的情况下,尽可能使用 DOM2 事件绑定的方式来实现;
  4. 图解:
  5. 示例代码:
    HTML
    CSS
    JavaScript
    <div class="box" id="box"></div>
    
    .box {
      position: absolute;
      top: 0;
      left: 0;
      width: 100px;
      height: 100px;
      background: lightcoral;
      cursor: move;
    }
    
    let box = document.getElementById('box');
    
    box.addEventListener('mousedown', down);
    
    // 鼠标按下做的事情
    function down(ev) {
      // 禁止右键
      if (ev.which === 3 || ev.which === 2) return;
    
      // 1.记录鼠标和盒子的起始位置,把值都记录在元素的自定义属性上
      this.startX = ev.clientX;
      this.startY = ev.clientY;
      this.startL = this.offsetLeft;
      this.startT = this.offsetTop;
    
      // move.bind() 会返回一个代理函数,this 指向 盒子本身
      this._MOVE = move.bind(this);
      this._UP = up.bind(this);
    
      // 「谷歌中的解决方案」
      // this 是点击的事件源,不可以是 document,需要用 bind 改变 this 指向
      document.addEventListener('mousemove', this._MOVE);
      document.addEventListener('mouseup', this._UP);
    
      // 「IE 和火狐浏览器中的解决方案」
      // this.setCapture();
    }
    
    // 鼠标移动的时候做的事情
    function move(ev) {
      if (ev.which === 3 || ev.which === 2) return;
    
      let curL = ev.clientX - this.startX + this.startL,
        curT = ev.clientY - this.startY + this.startT;
    
      // this.style.cssText = `left:${curL}px;top:${curT}px;`;
      this.style.left = curL + 'px';
      this.style.top = curT + 'px';
    }
    
    // 鼠标抬起时候做的事情 
    function up(ev) {
      if (ev.which === 3 || ev.which === 2) return;
    
      document.removeEventListener('mousemove', this._MOVE);
      document.removeEventListener('mouseup', this._UP);
    
      // this.releaseCapture();
    }
    

具体实现

  1. 图解

  2. 示例代码

    HTML
    CSS
    JavaScript
    <section class="magnifier clearfix">
      <!-- 左侧缩略图 -->
      <div class="abbre">
        <img src="images/1.jpg" alt="">
        <div class="mark"></div>
      </div>
      <!-- 右侧原图(大图) -->
      <div class="origin">
        <img src="images/2.jpg" alt="">
      </div>
    </section>
    
    <script src="js/jquery.min.js"></script>
    
    .magnifier {
      box-sizing: border-box;
      margin: 20px auto;
      width: 500px;
    }
    
    .magnifier .abbre,
    .magnifier .origin {
      float: left;
    }
    
    .magnifier .abbre {
      position: relative;
      box-sizing: border-box;
      width: 200px;
      height: 200px;
    }
    
    .magnifier .abbre img {
      width: 100%;
      height: 100%;
    }
    
    .magnifier .abbre .mark {
      display: none;
      position: absolute;
      top: 0;
      left: 0;
      width: 60px;
      height: 60px;
      background: rgba(255, 0, 0, .3);
      cursor: move;
    }
    
    .magnifier .origin {
      display: none;
      position: relative;
      box-sizing: border-box;
      width: 300px;
      height: 300px;
      overflow: hidden;
    }
    
    .magnifier .origin img {
      position: absolute;
      top: 0;
      left: 0;
    }
    
    /* 首先计算大图的大小 */
    let $abbre = $('.abbre'),
      $mark = $abbre.find('.mark'),
      $origin = $('.origin'),
      $originImg = $origin.find('img');
    
    let abbreW = $abbre.outerWidth(),
      abbreH = $abbre.outerHeight(),
      //=>获取当前元素距离body的偏移 =>{top:xxx,left:xxx}
      abbreOffset = $abbre.offset(),
      markW = $mark.outerWidth(),
      markH = $mark.outerHeight(),
      originW = $origin.outerWidth(),
      originH = $origin.outerHeight(),
      originImgW = abbreW / markW * originW,
      originImgH = abbreH / markH * originH;
    
    $originImg.css({ width: originImgW, height: originImgH });
    
    /* 鼠标进入和离开完成的事情 */
    // 计算 MARK 盒子的位置和控制大图的移动
    function computedMark(ev) {
      // MARK 盒子的偏移量
      let markL = ev.clientX - abbreOffset.left - markW / 2,
        markT = ev.clientY - abbreOffset.top - markH / 2;
      // 最小偏移量
      let minL = 0,
        minT = 0,
        // 最大偏移量(不能在照片外移动)
        maxL = abbreW - markW,
        maxT = abbreH - markH;
      markL = markL < minL ? minL : (markL > maxL ? maxL : markL);
      markT = markT < minT ? minT : (markT > maxT ? maxT : markT);
      $mark.css({
        top: markT,
        left: markL
      });
      // 
      $originImg.css({
        top: -markT / abbreH * originImgH,
        left: -markL / abbreW * originImgW
      });
    }
    
    $abbre.on('mouseenter', function (ev) {
      $mark.css('display', 'block');
      $origin.css('display', 'block');
      computedMark(ev);
    }).on('mouseleave', function (ev) {
      $mark.css('display', 'none');
      $origin.css('display', 'none');
    }).on('mousemove', computedMark);
    
  3. 效果展示

模态框

模态框封装

/*
* 把写好的方法重载到内置的 window.alert 上
*   alert('你好世界!');  => 弹出提升框 可以关闭,3S 后自动消失
*   alert('你好世界!',{  => 支持自定义配置项
*      title: '系统温馨提示',   控制标题的提示内容
*      confirm: false,   是否显示确认和取消按钮
*      handled: null     再点击确认/取消/×按钮的时候,触发的回调函数
*   });
*/
window.alert = (function () {
  // DIALOG:模态框类(每一个模态框都是创建这个类的实例)
  class Dialog {
    constructor(content, options) {
      // 把后续在各个方法中用到的内容全部挂载到实例上
      this.content = content;
      this.options = options;
      // 初始化
      this.init();
    }

    // 创建元素
    create(type, cssText) {
      let element = document.createElement(type);
      // style属性值
      element.style.cssText = cssText;
      return element;
    }

    createElement() {
      this.$DIALOG = this.create('div', `
        position: fixed;
        top: 0;
        left: 0;
        z-index: 9998;
        width: 100%;
        height: 100%;
        background: rgba(0, 0, 0, .8);
        user-select: none;
        opacity: 0;
        transition: opacity .3s;
      `);
      this.$MAIN = this.create('div', `
        position: absolute;
        top: 100px;
        left: 50%;
        margin-left: -200px;
        z-index: 9999;
        width: 400px;
        background: #FFF;
        border-radius: 3px;
        overflow: hidden;
        transform: translateY(-1000px);
        transition: transform .3s;
      `);
      this.$HEADER = this.create('div', `
        position: relative;
        box-sizing: border-box;
        padding: 0 10px;
        height: 40px;
        line-height: 40px;
        background: #2299EE;
        cursor: move;
      `);
      this.$TITLE = this.create('h3', `
        font-size: 18px;
        color: #FFF;
        font-weight: normal;
      `);
      this.$CLOSE = this.create('i', `
        position: absolute;
        right: 10px;
        top: 50%;
        transform: translateY(-50%);
        font-size: 24px;
        font-style: normal;
        color: #FFF;
        font-family: 'Courier New';
        cursor: pointer;
      `);
      this.$BODY = this.create('div', `
        padding: 30px 10px;
        line-height: 30px;
        font-size: 16px;
      `);
      this.$FOOTER = this.create('div', `
        text-align: right;
        padding: 10px 10px;
        border-top: 1px solid #EEE;
      `);
      this.$CONFIRM = this.create('button', `
        margin: 0 5px;
        padding: 0 15px;
        height: 28px;
        line-height: 28px;
        border: none;
        font-size: 14px;
        cursor: pointer;
        color: #FFF;
        background: #2299EE;
      `);
      this.$CANCEL = this.create('button', `
        margin: 0 5px;
        padding: 0 15px;
        height: 28px;
        line-height: 28px;
        border: none;
        font-size: 14px;
        cursor: pointer;
        color: #000;
        background: #DDD;
      `);
      // 把创建的元素按照层级合成(从里向外合成)
      // ES6中的解构赋值
      let { title, confirm } = this.options;
      this.$TITLE.innerHTML = title;
      this.$CLOSE.innerHTML = 'X';
      this.$HEADER.appendChild(this.$TITLE);
      this.$HEADER.appendChild(this.$CLOSE);
      this.$BODY.innerHTML = this.content;
      this.$MAIN.appendChild(this.$HEADER);
      this.$MAIN.appendChild(this.$BODY);
      if (confirm) {
          // 显示底部确定和取消按钮
          this.$CONFIRM.innerHTML = '确定';
          this.$CANCEL.innerHTML = '取消';
          this.$FOOTER.appendChild(this.$CONFIRM);
          this.$FOOTER.appendChild(this.$CANCEL);
          this.$MAIN.appendChild(this.$FOOTER);
      }
      this.$DIALOG.appendChild(this.$MAIN);
      // 插入到页面中:全部处理完后在插入,只引发一次回流
      document.body.appendChild(this.$DIALOG);
    }

    // 显示模态框
    show() {
      // opacity=1 透明度是1
      this.$DIALOG.style.opacity = 1;
      // y轴坐标偏移改外0,隐藏时偏移-1000px
      this.$MAIN.style.transform = 'translateY(0)';
      // 如果没有确定和取消按钮,让其显示3000MS后消失
      if (!confirm) {
        this.$timer = setTimeout(() => {
          this.hide();
          clearTimeout(this.$timer);
        }, 3000);
      }
    }

    // 隐藏模态框 lx='CONFIRM/CANCEL'
    // lx默认值是 CANCEL
    hide(lx = 'CANCEL') {
      this.$MAIN.style.transform = 'translateY(-1000px)';
      this.$DIALOG.style.opacity = 0;
      let fn = () => {
        // 触发handled回调函数执行
        if (typeof this.options.handled === "function") {
          this.options.handled.call(this, lx);
        }
        // 移除创建的元素
        document.body.removeChild(this.$DIALOG);
        // 当前方法只绑定一次
        this.$DIALOG.removeEventListener('transitionend', fn);
      };
      // transitionend 事件在 CSS 动画完成后触发
      this.$DIALOG.addEventListener('transitionend', fn);
    }

    // 拖拽实现
    down(ev) {
      this.startX = ev.clientX;
      this.startY = ev.clientY;
      this.startT = this.$MAIN.offsetTop;
      this.startL = this.$MAIN.offsetLeft;
      this._MOVE = this.move.bind(this);
      this._UP = this.up.bind(this);
      document.addEventListener('mousemove', this._MOVE);
      document.addEventListener('mouseup', this._UP);
    }
    move(ev) {
      let curL = ev.clientX - this.startX + this.startL,
          curT = ev.clientY - this.startY + this.startT;
      let minL = 0,
          minT = 0,
          maxL = this.$DIALOG.offsetWidth - this.$MAIN.offsetWidth,
          maxT = this.$DIALOG.offsetHeight - this.$MAIN.offsetHeight;
      curL = curL < minL ? minL : (curL > maxL ? maxL : curL);
      curT = curT < minT ? minT : (curT > maxT ? maxT : curT);
      this.$MAIN.style.left = curL + 'px';
      this.$MAIN.style.top = curT + 'px';
      this.$MAIN.style.marginLeft = 0;
    }
    up(ev) {
      document.removeEventListener('mousemove', this._MOVE);
      document.removeEventListener('mouseup', this._UP);
    }
    // 执行INIT可以创建元素,让其显示,并且实现对应的逻辑操作
    init() {
      this.createElement();
      //=>阻断渲染队列,让上述代码立即先渲染
      // 如果不加此行代码,createElement和show都是写操作都会触发回流,
      // 会加入到渲染队列中,等到全部执行完在显示,则没有动画效果,
      // 加上此行读取代码,阻断渲染队列
      this.$DIALOG.offsetWidth;
      this.show();
      // 基于事件委托实现关闭/确定/取消按钮的点击操作
      this.$DIALOG.addEventListener('click', ev => {
        let target = ev.target;
        // button 或者 i标签匹配 target.tagName
        if (/^(BUTTON|I)$/i.test(target.tagName)) {
          // 取消自动消失
          clearTimeout(this.$timer);
          this.hide(target.innerHTML === '确定' ? 'CONFIRM' : 'CANCEL');
        }
      });
      // 实现拖拽效果
      // 改变 this 指向为当前实例
      this.$HEADER.addEventListener('mousedown', this.down.bind(this));
    }
  }
  // proxy:就是alert执行的函数
  // =>插件封装的时候,如果需要传递多个配置项,我们一般都让其传递一个对象,
  // 而不是单独一项项让其传递,这样处理的好处:
  // 不需要考虑是否必传以及传递信息的顺序了、方便后期的扩展和升级...
  return function proxy(content, options = {}) {
    // 传参验证
    if (typeof content === 'undefined') {
      throw new Error("错误:提示内容必须传递!");
    }
    if (options === null || typeof options !== "object") {
      throw new Error("错误:参数配置项必须是一个对象!");
    }
    // 参数默认值和替换 (Object.assign合并对象)
    options = Object.assign({
      title: '系统温馨提示',
      confirm: false,
      handled: null
    }, options);
    return new Dialog(content, options);
  }
})();

使用

<button id="btn">点我有惊喜</button>
<script src="dialog-plugin.js"></script>
<script>
  btn.onclick = function () {
    alert(`
        用户名:<input type='text' id='AA'/>
        <br>
        密码:<input type='password' id='BB'/>
      `,
      {
        title: '用户登录',
        confirm: true,
        handled: lx => {
          if (lx !== 'CONFIRM') return;
          let AA = document.getElementById('AA');
          let BB = document.getElementById('BB');
          console.log(AA.value, BB.value);
        }
      });
  };
</script>

图片瀑布流

瀑布流,又称瀑布流式布局,是比较流行的一种网站页面布局,视觉表现为参差不齐的多栏布局,随着页面滚动条向下滚动,这种布局还会不断加载数据块并附加至当前尾部;

制作思路

  1. 首先第一步,仔细观察瀑布流图片,会发现他们都是定宽不定高的,既然定宽,那么一共显示几列也就能够计算出来,如下图所示:

  2. 列数出来之后,用一个数组来保存一行的高度;举例说明:按照 4 列来算,一开始一张图片都没有放,每一列的高度都为 0,所以数组里面是 40

  3. 接下来放入第一张图片,找数组里面最小的,目前都是 0,就放在第一列,放完之后需要更新数组里面的最小值;

  4. 然后依此类推,找数组最小的,会找到第二个 0,往第二列放入图片,更新数组,找到第三个 0,往第三列放入图片,更新数组…;

  5. 目前第一行满了,该放在第二行了,实际上和上面的算法是一样的,找数组的最小值即可,哪个最小就放在哪一列,放完之后更新数组;

  6. 计算公式:

    • 新的高度的计算公式:这一列新的高度 = 这一列高度(数组里面存储的有) + 间隙 + 新的图片高度
    • 然而这只是计算了 top 值,还有 left 值需要计算,每张图片的 left 值只和该图片所在的列有关;

代码附件下载

五子棋游戏

前置知识

  1. HTML5 新增查询 API

    1. querySelector:找到满足条件的第一个元素,参数是 css 选择器
    2. querySelectorAll:找到满足条件的所有个元素,参数是 css 选择器
  2. clientWidth:表示元素的内部宽度,该属性包括内边距 padding,但不包括边框 border、外边距 margin 和垂直滚动条;

制作思路

  1. 棋盘的绘制

    • 绘制棋盘,其实就是一个 14 行 x 14 列table 表格;
    • 每个 td 会记录当前的格子是第几行第几列,通过自定义属性来进行记录,data-row 记录行,data-line 记录列;
  2. 确定落子的坐标

    • 当用户在棋盘上点击鼠标时,通过 e.target.dataset 能够获取到用户点击的是哪一个 td
    • 再从 td 上获取位置信息;
  3. 用户点击了哪一个 td 后,需要判断落子应该在什么位置

    1. 通过 e.offsetX、e.offsetY,就可以获取到事件发生时鼠标相对于事件源元素的坐标,所谓事件源元素就是绑定事件的那个元素;
    2. 采用下面的方式来进行评判
    3. 首先计算出一个格子的宽高,然后用户点击的 e.offsetX 小于格子宽度的一半,e.offsetY 小于格子高度的一半,则是左上区域;e.offsetX 小于格子宽度的一半但是 e.offsetY 大于格子高度的一半,则是左下,依此类推。
      //定位落子的位置是在四个角的哪一个
      var positionX = e.offsetX > tdw / 2;
      var positionY = e.offsetY > tdw / 2;
      // 生成点击的坐标点,包含 3 个属性
      // x 坐标、y 坐标和 c 落子方
      var chessPoint = {
        x: positionX ? parseInt(temp.line) + 1 : parseInt(temp.line),
        y: positionY ? parseInt(temp.row) + 1 : parseInt(temp.row),
        c: whichOne
      }
      
    4. 这里重新组装了格子信息,得到类似于下面的对象。
      {x: 0, y: 0, c: "white"} // 第一个格子左上
      {x: 1, y: 0, c: "black"} // 第一个格子右上
      {x: 0, y: 1, c: "white"} // 第一个格子左下
      {x: 1, y: 1, c: "white"} // 第一个格子右下
      
  4. 绘制棋子

    1. 绘制棋子其实就是在 td 单元格里面添加一个 div,例如第一行第一个棋子:

      <td data-row="0" data-line="0">
        <div style="" class="white" data-row="0" data-line="0"></div>
      </td>
      
    2. 需要注意的是每一行和每一列的最后两个棋子共用一个单元格,例如第一行最后两个棋子:

      <td data-row="0" data-line="13">
        <!-- 最后一个棋子 -->
        <div style="left: 50%;" class="black" data-row="0" data-line="14"></div>
        <!-- 倒数第二个棋子 -->
        <div style="" class="white" data-row="0" data-line="13"></div>
      </td>
      
    3. 根据上面的描述得知,最右下角的格子,会放 4div,如下:

      <td data-row="13" data-line="13">
        <div style="" class="white" data-row="13" data-line="13"></div>
        <div style="left: 50%;" class="black" data-row="13" data-line="14"></div>
        <div style="top: 50%;" class="white" data-row="14" data-line="13"></div>
        <div style="top: 50%; left: 50%;" class="black" data-row="14" data-line="14"></div>
      </td>
      
  5. 胜负判定

    1. 具体的方案就是每一次落子都将这个棋子的坐标对象存储入数组,然后每次落子遍历数组进行判断即可;
    2. 核心代码块:
      // 检查横着有没有连着的 5 个
      chess2 = chessArr.find(function (item) {
        return curChess.x === item.x + 1 && item.y === curChess.y && item.c === curChess.c;
      })
      chess3 = chessArr.find(function (item) {
        return curChess.x === item.x + 2 && item.y === curChess.y && item.c === curChess.c;
      })
      chess4 = chessArr.find(function (item) {
        return curChess.x === item.x + 3 && item.y === curChess.y && item.c === curChess.c;
      })
      chess5 = chessArr.find(function (item) {
        return curChess.x === item.x + 4 && item.y === curChess.y && item.c === curChess.c;
      })
      if (chess2 && chess3 && chess4 && chess5) {
        end(curChess, chess2, chess3, chess4, chess5);
      }
      
      
      // 检查竖着有没有连着的 5 个
      chess2 = chessArr.find(function (item) {
        return curChess.x === item.x && item.y + 1 === curChess.y && item.c === curChess.c;
      })
      chess3 = chessArr.find(function (item) {
        return curChess.x === item.x && item.y + 2 === curChess.y && item.c === curChess.c;
      })
      chess4 = chessArr.find(function (item) {
        return curChess.x === item.x && item.y + 3 === curChess.y && item.c === curChess.c;
      })
      chess5 = chessArr.find(function (item) {
        return curChess.x === item.x && item.y + 4 === curChess.y && item.c === curChess.c;
      })
      if (chess2 && chess3 && chess4 && chess5) {
        end(curChess, chess2, chess3, chess4, chess5);
      }
      
      // 检查斜线 1 有没有连着的 5 个
      chess2 = chessArr.find(function (item) {
        return curChess.x === item.x + 1 && item.y + 1 === curChess.y && item.c === curChess.c;
      })
      chess3 = chessArr.find(function (item) {
        return curChess.x === item.x + 2 && item.y + 2 === curChess.y && item.c === curChess.c;
      })
      chess4 = chessArr.find(function (item) {
        return curChess.x === item.x + 3 && item.y + 3 === curChess.y && item.c === curChess.c;
      })
      chess5 = chessArr.find(function (item) {
        return curChess.x === item.x + 4 && item.y + 4 === curChess.y && item.c === curChess.c;
      })
      if (chess2 && chess3 && chess4 && chess5) {
        end(curChess, chess2, chess3, chess4, chess5);
      }
      
      // 检查斜线 2 有没有连着的 5 个
      chess2 = chessArr.find(function (item) {
        return curChess.x === item.x - 1 && item.y + 1 === curChess.y && item.c === curChess.c;
      })
      chess3 = chessArr.find(function (item) {
        return curChess.x === item.x - 2 && item.y + 2 === curChess.y && item.c === curChess.c;
      })
      chess4 = chessArr.find(function (item) {
        return curChess.x === item.x - 3 && item.y + 3 === curChess.y && item.c === curChess.c;
      })
      chess5 = chessArr.find(function (item) {
        return curChess.x === item.x - 4 && item.y + 4 === curChess.y && item.c === curChess.c;
      })
      if (chess2 && chess3 && chess4 && chess5) {
        end(curChess, chess2, chess3, chess4, chess5);
      }
      

代码附件下载

拖拽(重力加速度)

示例代码

HTML
CSS
JavaScript
<div id='demo'></div>
div {
  position: absolute;
  left: 0px;
  top: 0px;
  background: orange;
  width: 100px;
  height: 100px;
}
var oDiv = document.getElementById('demo');
var lastX = 0;
var lastY = 0;
var iSpeedX = 0;
var iSpeedY = 0;

oDiv.onmousedown = function (e) {
  clearInterval(this.timer);
  var event = event || e;

  // 点击位置,距离盒子左上角的距离
  var disX = event.clientX - this.offsetLeft;
  var disY = event.clientY - this.offsetTop;
  var self = this;

  document.onmousemove = function (e) {
    var event = event || e;

    // 计算此时盒子左上角的坐标
    var newLeft = event.clientX - disX;
    var newTop = event.clientY - disY;

    // 计算拖动距离,作为 x、y 方向的速度
    iSpeedX = newLeft - lastX;
    iSpeedY = newTop - lastY;

    // 记录拖动停止位置
    lastX = newLeft;
    lastY = newTop;

    self.style.left = newLeft + 'px';
    self.style.top = newTop + 'px';
  }
  document.onmouseup = function () {
    document.onmouseup = null;
    document.onmousemove = null;
    startMove(self, iSpeedX, iSpeedY);
  }
}

function startMove(dom, iSpeedX, iSpeedY) {
  clearInterval(dom.timer);
  var g = 3;
  dom.timer = setInterval(function () {
    iSpeedY += g;
    var newTop = dom.offsetTop + iSpeedY;
    var newLeft = dom.offsetLeft + iSpeedX;

    // 容器底部碰撞检测
    if (newTop >= document.documentElement.clientHeight - dom.clientHeight) {
      iSpeedY *= -1;
      iSpeedY *= 0.8;
      iSpeedX *= 0.8;
      newTop = document.documentElement.clientHeight - dom.clientHeight;
    }

    // 容器顶部碰撞检测
    if (newTop <= 0) {
      iSpeedY *= -1;
      iSpeedY *= 0.8;
      iSpeedX *= 0.8;
      newTop = 0;
    }

    // 容器右侧碰撞检测
    if (newLeft >= document.documentElement.clientWidth - dom.clientWidth) {
      iSpeedX *= -1;
      iSpeedY *= 0.8;
      iSpeedX *= 0.8;
      newLeft = document.documentElement.clientWidth - dom.clientWidth;
    }

    // 容器左侧碰撞检测
    if (newLeft <= 0) {
      iSpeedX *= -1;
      iSpeedY *= 0.8;
      iSpeedX *= 0.8;
      newLeft = 0;
    }

    // 运动速度绝对值小于 1,直接停止
    if (Math.abs(iSpeedX) < 1) iSpeedX = 0;
    if (Math.abs(iSpeedY) < 1) iSpeedY = 0;

    if (iSpeedX == 0 && iSpeedY == 0 && newTop == document.documentElement.clientHeight - dom.clientHeight) {
      clearInterval(dom.timer);
      console.log('over')
    } else {
      dom.style.top = newTop + 'px';
      dom.style.left = newLeft + 'px';
    }
  }, 30);
}

代码附件下载

打赏作者
您的打赏是我前进的动力
微信
支付宝
评论

中午好👏🏻,我是 ✍🏻   疯狂 codding 中...

粽子

这有关于前端开发的技术文档和你分享。

相信你可以在这里找到对你有用的知识和教程。

了解更多

目录

  1. 1. 放大镜
    1. 1.1. 前置知识
    2. 1.2. 具体实现
  2. 2. 模态框
    1. 2.1. 模态框封装
    2. 2.2. 使用
  3. 3. 图片瀑布流
    1. 3.1. 制作思路
    2. 3.2. 代码附件下载
  4. 4. 五子棋游戏
    1. 4.1. 前置知识
    2. 4.2. 制作思路
    3. 4.3. 代码附件下载
  5. 5. 拖拽(重力加速度)
    1. 5.1. 示例代码
    2. 5.2. 代码附件下载