模拟win7拖动对话框效果

200 阅读9分钟

  win7下拖动一个状态栏,可以看到整个对话框的边框变成了玻璃,待拖动到指定位置后松手,对话框就过去了

drag

我们在网页中实现一下:

drag

要点有两个:

  • 对话框可以随意拖动
  • 拖动时的玻璃边框

如何实现

  首先创建index.html,假设桌面背景为类名是glass的DIV,我们的对话框叫dialog,里面放个图片做区分。因为移动时要有一个透明玻璃边框,所以我们用replace来当边框

<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
  <head>
    <title>glass</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
  </head>
  <body>
    <div class="glass">
      <div class="dialog">
        <img class="img" src="../../../assets/images/spider.jpg">
      </div>
      <div class="replace"></div>
    </div>
  </body>
</html>

  移动一个DIV块很简单,获取替代dialog的replace的距离窗口左边缘和上边缘的距离,再获取鼠标点击的坐标,进而计算出dialog应该在的位置,移动过去就行了。代码如下:

let finalLeft; // replace盒子左边框距离左窗口的距离
let finalHeight; // replace盒子上边框距离上窗口的距离

function drag() {
  let center = document.querySelector('.replace');
  center.onmousedown = client; // 获取中间盒子,按下鼠标时添加事件
}

function client(eve) {
  let _eve = eve || window.event; // 兼容性
  let replace = document.querySelector('.replace');
  let disX = _eve.clientX - replace.offsetLeft; // 鼠标点击位置跟replace块的左边框距离
  let disY = _eve.clientY - replace.offsetTop; // 鼠标点击位置跟replace块的上边框距离
  document.onmousemove = function (event) {
    let _event = event || window.event;
    move(_event, disX, disY);
  };
  document.onmouseup = function () {
    stop();
    document.onmousemove = null;
    document.onmouseup = null; // 释放鼠标时清空事件
  };
}

function move(eve, posx, posy) {
  let _eve = eve || window.event;
  let replace = document.querySelector('.replace');
  finalLeft = _eve.clientX - posx; // replace盒子左边框距离左窗口的距离
  finalHeight = _eve.clientY - posy; // replace盒子上边框距离上窗口的距离
  let diffWidth = document.documentElement.clientWidth - replace.offsetWidth || document.body.clientWidth - replace.offsetWidth; // 兼容性获取当前窗口的宽度-replace盒子的宽度
  let diffHeight = document.documentElement.clientHeight - replace.offsetHeight || document.body.clientWidth - replace.offsetWidth; // 兼容性获取当前窗口的高度-replace盒子的高度
  if (finalLeft <= 0) {
    finalLeft = 0;
  }
  if (finalHeight <= 0) {
    finalHeight = 0;
  }
  if (finalLeft >= diffWidth) {
    finalLeft = diffWidth;
  }
  if (finalHeight >= diffHeight) {
    finalHeight = diffHeight;
  } // 使盒子不超出窗口
  replace.style.left = finalLeft + 'px';
  replace.style.top = finalHeight + 'px';
}
function stop() { // 移动停止后将dialog移动过去
  document.querySelector('.dialog').style.left = finalLeft + 'px';
  document.querySelector('.dialog').style.top = finalHeight + 'px';
}
window.onload = drag;

移动对话框没问题,玻璃边框呢?主要靠mix-blend-mode这个CSS属性

* {
  margin: 0;
  padding: 0;
}
html, body {
  width: 100%;
  height: 100%;
}
.glass {
  width: 100%;
  height: 100%;
  background: url(../../../assets/images/ironman.jpg) no-repeat 100% 100%;
}
.img {
  width: 100%;
  height: 100%;
}
.replace, .dialog {
  position: absolute;
  top: 0px;
  left: 0px;
  width: 300px;
  height: 300px;
  user-select: none;
  padding: 10px;
}
.replace::after {
  content: '';
  position: absolute;
  left: 0;
  top: 0;
  width: 300px;
  height: 300px;
}
.replace:active::after {
  border: 10px solid #fff;
  mix-blend-mode: difference;
}

知识点

  1. offsetHeight/offsetWidth, offsetTop/offsetLeft
  2. clientHeight/clientWidth
  3. scrollHeight/scrollWidth
  4. scrollTop/scrollLeft
  5. 获取鼠标的坐标
  6. 混合模式mix-blend-mode

偏移量(offset dimension)

  1. offsetHeight: 元素在垂直方向上占用的空间大小(px)。W3C标准盒模型都包含content、border和padding。但是IE盒模型设置width和height后,最终内容区域大小跟border和padding有关。
  2. offsetWidth: 元素水平方向的空间大小(px)。

offsetHeight = height + border + padding,包括滚动条 offsetWidth = width + border + padding

  1. offsetLeft: 返回当前元素外边框相对于其 offsetParent 元素的左内边框的距离(px)。
  2. offsetTop: 返回当前元素外边框相对于其 offsetParent 元素的上内边框的距离(px)。

只读属性,offsetParent的选取方式: 1.距该元素最近的position不为static的祖先元素; 2.如果没有定位的元素,则 offsetParent 为最近的 table, table cell 或根元素。

  1. 这些值都是只读的,并且是number类型

screen

  如下,content父元素有定位元素,则offsetTop返回content的上外边框(border外部)相对于wrapper的上内边框(border内部)距离,即content的marginTop+wrapper的paddingTop,是20px   wrapper父元素中没有定位元素,则offsetTop与offsetLeft返回相对于body的距离,即wrapper的marginTop和marginLeft

<body>
  <div class="wrapper">
    <div class="content">
      content
    </div>
  </div>
</body>
* {
  margin: 0;
  height: 0;
}
.wrapper {
  position: relative;
  margin: 100px 100px 0;
  padding: 10px;
  width: 600px;
  height: 400px;
  border: 5px solid black;
  overflow: auto;
  background: antiquewhite;
  /* box-sizing: border-box; */
}
.content {
  margin: 10px;
  padding: 10px;
  width: 300px;
  height: 200px;
  border: 5px solid black;
  background: aquamarine;
  /* box-sizing: border-box; */
}

offsetPosition

客户区大小(client dimension)

  1. clientWidth 元素的内容(content)和内边距(padding)占据的宽度。
  2. clientHeight 元素的内容(content)和内边距(padding)占据的高度。
  3. 这两个属性也受盒模型影响。

滚动大小(scroll dimension)

  1. scrollHeight 没有滚动条时的元素内容总高度(content + padding)
  2. scrollWidth 没有滚动条时的元素内容总宽度(content + padding)
  3. scrollTop 被隐藏在内容上侧的像素数
  4. scrollLeft 被隐藏在内容左侧的像素数
  5. scroll的空间占用的是父元素的空间,每个浏览器的默认宽度不同,chrome默认是17px宽
  6. 设置scrollscrollTop和scrollLeft可以改变滚动条的位置,但是要给内容的父元素设置,即滚动条所占空间的所有者。

scroll

  页面内没有任何可滑动元素时,根元素的scrollHeight = 根元素的高度,scrollWidth = 根元素的宽度

当页面内元素内部没有可滑动元素时,scrollHeight/scrollWidth = content + padding

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
  <style>
    * {
      margin: 0;
      height: 0;
    }
    .wrapper {
      margin: 100px 100px 0;
      padding: 10px;
      width: 600px;
      height: 400px;
      border: 5px solid black;
      background: antiquewhite;
    }
  </style>
</head>
<body>
  <div class="wrapper">
  </div>
</body>
</html>

当页面内元素内部有可滑动元素时,scrollHeight = content + padding = 内部元素content的content + padding + border + margin + wrapper的padding = 670px scrollWidth = content + padding = wrapper的padding + wrapper的width - 滚动条宽度17px(chrome默认宽度) = 603px

<body>
  <div class="wrapper">
    <div class="content">
      content
    </div>
  </div>
</body>
* {
  margin: 0;
  height: 0;
}
.wrapper {
  margin: 100px 100px 0;
  padding: 10px;
  width: 600px;
  height: 400px;
  border: 5px solid black;
  overflow: auto;
  background: antiquewhite;
}
.content {
  margin: 10px;
  padding: 10px;
  width: 400px;
  height: 600px;
  border: 5px solid black;
  background: aquamarine;
}

scroll

混合模式mix-blend-mode

mode 模式 Description
normal 正常 直接返回结果色
multiply 正片叠底 查看每个通道中的颜色信息,并将基色与混合色复合
screen 滤色 与“正片叠底”相反,查看每个通道的颜色信息,将图像的基色与混合色结合起来产生比两种颜色都浅的第三种颜色
overlay 叠加 把图像的基色与混合色相混合产生一种中间色。
darken 变暗 查看每个通道中的颜色信息,并选择基色或混合色中较暗的颜色作为结果色
lighten 变亮 查看每个通道中的颜色信息,并选择基色或混合色中较亮的颜色作为结果色
color-dodge 颜色减淡 查看每个通道中的颜色信息,并通过减小对比度使基色变亮以反映混合色。与黑色混合则不发生变化
color-burn 颜色加深 查看每个通道中的颜色信息,并通过增加对比度使基色变暗以反映混合色,如果与白色混合的话将不会产生变化
hard-light 强光 产生一种强光照射的效果。如果混合色比基色更亮一些,那么结果色将更亮;如果混合色比基色更暗一些,那么结果色将更暗
soft-light 柔光 产生一种柔光照射的效果。如果混合色比基色更亮一些,那么结果色将更亮;如果混合色比基色更暗一些,那么结果色将更暗,使图像的亮度反差增大
difference 差值 查看每个通道中的颜色信息,从图像中基色的亮度值减去混合色的亮度值,如果结果为负,则取正值,产生反相效果
exclusion 排除 与“差值”模式相似,但是具有高对比度和低饱和度的特点。比用“差值”模式获得的颜色要柔和、更明亮一些
hue 色相 选择基色的亮度和饱和度值与混合色进行混合而创建的效果,混合后的亮度及饱和度取决于基色,但色相取决于混合色
saturation 饱和度 在保持基色色相和亮度值的前提下,只用混合色的饱和度值进行着色。基色与混合色的饱和度值不同时,才使用混合色进行着色处理。若饱和度为0,则与任何混合色叠加均无变化。当基色不变的情况下,混合色图像饱和度越低,结果色饱和度越低;混合色图像饱和度越高,结果色饱和度越高
color 颜色 引用基色的明度和混合色的色相与饱和度创建结果色。它能够使用混合色的饱和度和色相同时进行着色,这样可以保护图像的灰色色调,但结果色的颜色由混合色决定。颜色模式可以看作是饱和度模式和色相模式的综合效果,一般用于为图像添加单色效果
luminosity 亮度 能够使用混合色的亮度值进行着色,而保持基色的饱和度和色相数值不变。其实就是用基色中的“色相”和“饱和度”以及混合色的亮度创建结果色
分类名称 darken,multiply,color-burn 介绍
降暗混合模式 亮度 减色,滤掉图像中高亮色,使图像变暗
加亮混合模式 screen,lighten,color-dodge 加色模式,滤掉图像中暗色,使图像变亮
融合混合模式 overlay,soft-light,hard-light 不同程度的图层融合
变异混合模式 difference,exclusion,hard-light 用于制作各种变异的图层混合
色彩叠加混合模式 hue,saturation,color,luminosity 根据图层的色相,饱和度等基本属性,完成图层融合

参考资料

《JavaScript高级程序设计》
《CSS混合模式mix-blend-mode/background-blend-mode简介》