quiz icon indicating copy to clipboard operation
quiz copied to clipboard

DOM基础测试27

Open zhangxinxu opened this issue 6 years ago • 22 comments

题目如下,入门级难度:

使用原生JS代码实现。

大家提交回答的时候,注意缩进距离,起始位置从左边缘开始;另外,github自带代码高亮,所以请使用下面示意的格式。

 ```js
// 你的JS代码写在这里
 ```

第二小题细节补充:

网页默认固定是HTML或者body,但有时候也可能是DIV,因为布局需要。和题1唯一不同就是容器变了。不要想太多。

zhangxinxu avatar Jan 23 '19 10:01 zhangxinxu

第一题:(Throttle是一个节流函数)

(function() {
  let backTop = document.getElementById('backTop'),
  scrollTop = 0,
  windowHeight = window.innerHeight || document.documentElement.clientHeight;
  let scroll = Throttle((e) => {
    scrollTop = document.documentElement.scrollTop || window.pageYOffset || 
    document.body.scrollTop;
           if (scrollTop >= windowHeight) {
             backTop.classList.add('show');
           } else {
             backTop.classList.remove('show');
            }
        }, 300)
    document.addEventListener('scroll', scroll);
  })();

第二题:(content是高度小于屏幕高度的元素overflow:hidden; wrapper是content的子元素overflow:auto;)

(function() {
  let backTop = document.getElementById('backTop'),
  content = document.getElementById('content'),
  wrapper = document.getElementById('wrapper'),
  scrollTop = 0,
  windowHeight = content.innerHeight || content.clientHeight;
  let scroll = Throttle((e) => {
    scrollTop = wrapper.scrollTop;
    if (scrollTop >= windowHeight) {
      backTop.classList.add('show');
    } else {
      backTop.classList.remove('show');
    }
  }, 300);
  wrapper.addEventListener('scroll', scroll);
})();

Throttle源码:

function Throttle(fn, delay) {
  let last = 0,
  timer = null;
  return function() {
    let context = this,
    args = arguments,
    now = +new Date();
    if (now - last < delay) {
      clearTimeout(timer)
      timer = setTimeout(function() {
         last = now;
         fn.apply(context, args);
      }, delay)
    } else {
      last = now;
     fn.apply(context, args);
    }
  }
}

xiongchenf avatar Jan 23 '19 10:01 xiongchenf

一个通吃两道小题的方案

JavaScript(ES6,需兼容自行 babel)

{
	const
		$back_to_top = document.getElementById('backTop'),
		$parent = $back_to_top.parentElement;
	
	const
		getScrollTop = $parent === document.body ?
			() => window.scrollY :
			() => $parent.scrollTop,
		getViewportHeight = $parent === document.body ?
			() => window.innerHeight :
			() => $parent.offsetHeight,
		scrollBackToTop = $parent === document.body ?
			() => window.scrollY = 0 :
			() => $parent.scrollTop = 0;
	
	$back_to_top.addEventListener('click', scrollBackToTop);

	const wheelEventHandler = () => $back_to_top.classList[
		getScrollTop() > getViewportHeight() ? 'remove' : 'add'
	]('hidden');

	// TODO: 节流函数,减少事件触发次数
	// 要用的话直接 underscore 或者用原生的 setInterval 实现一个就行
	const throttle = (fn, interval) => fn;

	$parent.addEventListener('wheel', throttle(wheelEventHandler, 100));
	$parent.addEventListener('scroll', throttle(wheelEventHandler, 100));

	document.addEventListener('DOMContentLoaded', wheelEventHandler);
}

CSS(也可以不用,只是为了方便用 class 管理样式)

#backTop {
	display: block;
	position: fixed;
	top: 0;
	/* 自定义样式 */
}

#backTop.hidden {
	visibility: hidden;
}

WangNianyi2001 avatar Jan 23 '19 11:01 WangNianyi2001

css

  .hidden{
      display: none;
  }
  #container{
      overflow:auto;
      height: 300px;
  }
  #backTop{
      /* 若容器是html的时候#backTop是fixed定位 */
      /* position: fixed; */
      /* 若容器是div的时候#backTop是absolute定位 */
      position: absolute;
      right:30px;
      margin-top:200px;
  }
  /* 辅助元素 */
  .auxiliary{
      height: 800px;
  }

html

<!-- 当容器是html的时候 -->
<!-- <a 
    class="hidden"
    id="backTop"
    href="#" 
>
    返回顶部
</a>
<div 
    class="auxiliary"
>
</div> -->  

<!-- 当容器是div的时候 -->
<div 
    id="container"
>
  <a 
      class="hidden"
      id="backTop"
      href="#" 
  >
      返回顶部
  </a>
  <div 
      class="auxiliary"
  >
  </div>     
</div>

js

/* 滚动元素是window时 */
/* toggleBackTop(window,document.querySelector("#backTop")); */
/* 滚动元素是div时 */
toggleBackTop(document.querySelector("#container"),document.querySelector("#backTop"));

function toggleBackTop(container,backTopDom){
    container.addEventListener("scroll",function(){
        let containerHeight,
            scrollHeight;
        if(this instanceof Window){
            containerHeight = 
                document.documentElement.clientHeight
                ||
                document.body.clientHeight;
        }else{
            containerHeight = this.clientHeight;
        }
        scrollHeight = this.scrollY||this.scrollTop||0;
        backTopDom.classList.toggle("hidden",scrollHeight<containerHeight);
    })
}

liyongleihf2006 avatar Jan 23 '19 11:01 liyongleihf2006

借用一楼的Throttle节流函数。

(function (doc, con) {
    var backTop = doc.getElementById('backTop');
    var body = con || doc.documentElement;
    var setVisible = Throttle(function () {
        var h = body.clientHeight;
        var scrollTop = body.scrollTop;
        if (scrollTop >= h) {
            backTop.removeAttribute('hidden');
        } else {
            backTop.setAttribute('hidden', true)
        }
    }, 300)
    var content = con||doc;
    content.addEventListener('scroll', setVisible, false);
    window.addEventListener('resize', setVisible, false);//改变窗口大小也需要监听
})(document)

如果滚动容器是div

var div = document.getElementById('wrap');
(function (doc, con) {
    //...同上
})(document,div)

XboxYan avatar Jan 23 '19 12:01 XboxYan

对于第一题,分析提议需要注意以下几点:

  1. scroll event作为高触发事件,回调函数里面进行高损耗性能操作我们需要throttle,而throttle无外乎:requestAnimationFrame(),setTimeout() 或者CustomEvent
  2. 兼容PC端和移动端,使用window.pageYOffset而不是window.scrollY(处于兼容性考虑,其实目前应该所有主流浏览器都支持这两个属性对)。另外我们需要修改Mobile浏览器的默认layout viewport等于ideal viewport的大小(<meta name="viewport" content="width=device-width,initial-scale=1.0">
  3. 获取viewport高度时,我采用了document.documentElement.clientHeight,而没有采用window.innerHeight,是因为我不希望加上可能出现的x轴scrollbar的高度
  4. 是否考虑浏览器zoom in/out 操作,因为zoom操作很显然会改变浏览器的visual viewport dimension.理论上来讲,当用户scroll up之后再zoom in(放大), window.scrollXOffsetwindow.scrollYOffset应该会变化。但是browser本身对这个操作进行了内部处理,确保之前在可见视框顶部的元素仍然出现在顶部(尽管位置不一定是完美的),所以仍然会触发scroll event,也就是说我们不需要考虑zoom可能产生的后果。
#backTop {
  position: fixed;
  bottom: 10px;
  right: 10px;
}
let backTop = document.getElementById('backTop');
let ticking = false;
window.addEventListener('scroll', function(event) {
  if (!ticking) {
    window.requestAnimationFrame(function() {
      if(window.pageYOffset > document.documentElement.clientHeight) backTop.hidden = false;
      else backTop.hidden = true
      ticking = false;
    });

    ticking = true;
  }
})

对于第二题: 我没太懂补充的含义,我就按照自己理解来做了. 题意理解: body高度固定,div作为滚动容器, backTop 在div容器之外.

  1. 判断是否超过一页,使用的element.scrollTopelement.clientHeight,仍然是高度不算scrollbar。
  2. 另外backTop直接就使用click来处理了,在移动端可以添加touch事件。
#backTop {
  position: fixed;
  bottom: 10px;
  right: 10px;
}
#container {
  position: relative;
  margin: 20px;
  width: 500px;
  height: 500px;
  border: 1px solid black;
  overflow: scroll;
}
let backTop = document.getElementById('backTop');
let container = document.getElementById('container');
let ticking = false;
container.addEventListener('scroll', function(event) {
  if (!ticking) {
    window.requestAnimationFrame(function() {
      if(container.scrollTop > container.clientHeight) backTop.hidden = false;
      else backTop.hidden = true
       ticking = false;
    });

    ticking = true;
  }
})

backTop.addEventListener('click', function() {
  container.scrollTop = 0;
})

BruceYuj avatar Jan 23 '19 12:01 BruceYuj

html,body{
        height:100%
}
<script>
    const h = window.innerHeight // 一屏的高度
    document.onscroll = () => {
      fn()
    }
      var fn = dealwith( () => {
        if (document.documentElement.scrollTop > h) {
          document.querySelector('#backTop').removeAttribute('hidden')
        } else {
          document.querySelector('#backTop').setAttribute('hidden', '')
        }
      }) 
    // 节流函数
    function dealwith (fn) {
      var timer
      var lastTime = new Date()
      return function () {
        var now = new Date()
        clearTimeout( timer )
        if ( now - lastTime < 500 ) {
          timer = setTimeout( () => {
            fn()
            lastTime = now
          }, 300)
        } else {
          fn()
          lastTime = now
        }
      }
    }
  </script>

第二题,有点难理解,而且不知道具体的需求 另外,请教大佬,这个要兼容移动端是啥意思啊?移动端浏览器跟pc不是只有屏幕的尺寸有问题嘛,内核啥的难道也有区别吗?

Valar103769 avatar Jan 23 '19 12:01 Valar103769

html,body{
        height:100%
}
<script>
    const h = window.innerHeight // 一屏的高度
    document.onscroll = () => {
      fn()
    }
      var fn = dealwith( () => {
        if (document.documentElement.scrollTop > h) {
          document.querySelector('#backTop').removeAttribute('hidden')
        } else {
          document.querySelector('#backTop').setAttribute('hidden', '')
        }
      }) 
    // 节流函数
    function dealwith (fn) {
      var timer
      var lastTime = new Date()
      return function () {
        var now = new Date()
        clearTimeout( timer )
        if ( now - lastTime < 500 ) {
          timer = setTimeout( () => {
            fn()
            lastTime = now
          }, 300)
        } else {
          fn()
          lastTime = now
        }
      }
    }
  </script>

第二题,有点难理解,而且不知道具体的需求 另外,请教大佬,这个要兼容移动端是啥意思啊?移动端浏览器跟pc不是只有屏幕的尺寸有问题嘛,内核啥的难道也有区别吗?

我记得 移动端对 scroll 事件有优化,当在滚动过程中,不会执行 scroll 事件绑定的函数;当滚动停止的时候才会执行。

ghost avatar Jan 23 '19 12:01 ghost

html,body{
        height:100%
}
<script>
    const h = window.innerHeight // 一屏的高度
    document.onscroll = () => {
      fn()
    }
      var fn = dealwith( () => {
        if (document.documentElement.scrollTop > h) {
          document.querySelector('#backTop').removeAttribute('hidden')
        } else {
          document.querySelector('#backTop').setAttribute('hidden', '')
        }
      }) 
    // 节流函数
    function dealwith (fn) {
      var timer
      var lastTime = new Date()
      return function () {
        var now = new Date()
        clearTimeout( timer )
        if ( now - lastTime < 500 ) {
          timer = setTimeout( () => {
            fn()
            lastTime = now
          }, 300)
        } else {
          fn()
          lastTime = now
        }
      }
    }
  </script>

第二题,有点难理解,而且不知道具体的需求 另外,请教大佬,这个要兼容移动端是啥意思啊?移动端浏览器跟pc不是只有屏幕的尺寸有问题嘛,内核啥的难道也有区别吗?

我记得 移动端对 scroll 事件有优化,当在滚动过程中,不会执行 scroll 事件绑定的函数;当滚动停止的时候才会执行。

确切的说是 ios 的机型

ghost avatar Jan 23 '19 12:01 ghost

        html,
        body {
            width: 100%;
            height: 100%;
            padding: 0;
            margin: 0;
        }
        #container {
            width: 100%;
            height: 100%;
            overflow: scroll;
        }

    <div id="container">
        <a href="#" id="backTop" hidden>返回顶部!</a>
        <div class="big"></div>
    </div>

    let backTop = document.getElementById('backTop')
    let viewportHeight = document.documentElement.clientHeight || document.body.clientHeight
    window.addEventListener('scroll', function(){
        let scrollTop = document.documentElement.scrollTop || document.body.scrollTop
        if(scrollTop >= viewportHeight){
           if(backTop.hasAttribute('hidden')){
               backTop.removeAttribute('hidden')
           }
        }else if(!backTop.hasAttribute('hidden')){
            backTop.setAttribute('hidden', '')
        }
    }, false)
    let container = document.getElementById('container')
    container.addEventListener('scroll', function(){
        let scrollTop = this.scrollTop
        if(scrollTop >= viewportHeight){
            if(backTop.hasAttribute('hidden')){
                backTop.removeAttribute('hidden')
            }
        }else if(!backTop.hasAttribute('hidden')){
            backTop.setAttribute('hidden', '')
        }
    }, false)

技术比较菜,不太懂 pc 和移动端关于兼容这块有什么具体的区别, 也没体会出上面使用节流函数的必要性是什么,我觉得没必要用防抖节流来控制啊

supperGod avatar Jan 23 '19 13:01 supperGod

/* 
* 调用:
*  backTop({
*   wrapEl: el, // 选传
*   backTopEl: el
* })
**/
function backTop (obj) {
        var wrapEl = obj.wrapEl || window, // 外层元素
            backTopEl = obj.backTopEl, // 返回顶部
            wH, // 外层元素高度
            scrollTop, // 滚动高
            timer1, timer2; // 定时器
        (wrapEl===document.body || wrapEl===document.documentElement) && (wrapEl = window);
        wrapEl===window && (wH=document.documentElement.clientHeight,1) || (wH=wrapEl.clientHeight); 
        if (backTopEl) {
            wrapEl.onscroll = function () {
                clearTimeout(timer2);
                timer2 = setTimeout(function () { // 节流
                    scrollTop = wrapEl===window ? (document.documentElement.scrollTop || document.body.scrollTop) : (wrapEl.scrollTop);
                    (scrollTop-wH>0) ? backTopEl.removeAttribute("hidden") : backTopEl.setAttribute("hidden", "hidden");
                }, 200);
            }
            window.onresize = function () {
                clearTimeout(timer1);
                timer1 = setTimeout(function () {
                    wrapEl===window && (wH=document.documentElement.clientHeight,1) || (wH=wrapEl.clientHeight); 
                }, 200);
            }
        }
    }

LittleOtter avatar Jan 24 '19 02:01 LittleOtter

  let backTop =  document.getElementById('backTop')
  let scrollFunction = (box) => {
    box.onscroll = () => {
      let scrollT = box.scrollTop || document.documentElement.scrollTop || document.body.scrollTop
      let clientH = box.clientHeight || document.documentElement.clientHeight
      if (scrollT > clientH) {
        backTop.style.display = 'block'
      } else {
        backTop.style.display = 'none'
      }
    }
  }
  scrollFunction(window) // 第一种
  let divBox =  document.getElementById('box')
  scrollFunction(divBox) // 第二种

按照自己的理解初步实现了功能,细节方面还存在比较多的问题,请大家指正

miaomiaolin avatar Jan 24 '19 02:01 miaomiaolin

第一题与第二题的函数

function scroll(node){
  let parent = document.querySelector(node)
  let h1,scrolltop ;
  if(node!="body"){
    h1 = parent.offsetHeight
     parent.onscroll = function(){
       scrolltop = parent.scrollTop
       console.log(scrolltop)
       checkHeight(h1,scrolltop)
     }
  }else{
    h1 = document.documentElement.clientHeight
    window.onscroll=function(){
      scrolltop = document.documentElement.scrollTop||document.body.scrollTop;
      console.log(scrolltop)
       checkHeight(h1,scrolltop)
    }
  }
}
// 比较检查
function checkHeight(h1,h2){
  let backtop = document.querySelector("#backTop")
  if(h1>h2){
    backtop.style.display="none"
  }else{
    backtop.style.display="block"
  }
}

这里进行调用

//第一题
scroll("body")
//第二题是div盒子,盒子要写上overflow-y:scroll; 此盒字id为scrollBox
scroll("#scrollBox")

sghweb avatar Jan 24 '19 03:01 sghweb

html

<body id="wrap" style="height: 1800px;">
  <a href="#" id="backTop" style="position: fixed;top: 20px;right: 20px;" hidden>返回顶部!</a>
  <a href="#" id="info" style="position: fixed;top:20px;left: 20px;"></a>
  <div class="box" style="height: 200px;overflow: auto; width:200px;background-color: #ccc;">
    <div class="con" style="height: 1800px">
    </div>
  </div>
</body>

js

function scroller(param) {
		let {scrollDom, distance, backDom} = param
		scrollDom = scrollDom || window
		backDom = backDom || document.querySelector('#backTop')

		let isDoc = scrollDom.nodeName === undefined || scrollDom.nodeName === 'HTML'
		if (isDoc) {
			scrollDom = window
		}
		scrollDom.addEventListener('scroll', () => {
			let scrollTop
			if (isDoc) {
				scrollTop = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
				distance = distance || window.screen.height
			} else {
				scrollTop = scrollDom.scrollTop
				distance = distance || scrollDom.clientHeight
			}
			document.querySelector('#info').innerHTML = scrollTop + ',' + distance
			if (scrollTop > distance) {
				backDom.removeAttribute('hidden')
			} else {
				backDom.setAttribute('hidden', 'hidden')
			}
		}, false)
	}

调用JS

	new scroller({
		scrollDom: document.querySelector('.box'),
		distance: 10,
		backDom: document.querySelector('#backTop')
	})
	new scroller({scrollDom: document.querySelector('html')})
	//new scroller({ distance: 100,})

bb595700239 avatar Jan 24 '19 03:01 bb595700239

第一题

整体思路:

  1. 需要用节流函数来控制scroll触发次数; 2. window.resize处理; 3. 滚动到顶部的按钮出现有一个阈值来控制

css

#scrollTop {
  display: block;
  visibility: hidden;
  position: fixed;
  bottom: 20px;
  right: 0px;
}

#scrollTop.show {
  visibility: visible
}
;(function () {
  function fn() {
    let $scrollTop = document.getElementById('scrollTop')
    let top = document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop
    top > 0 ? $scrollTop.classList.add('show') : $scrollTop.classList.remove('show')
  }

  // 窗口resize需要重新计算各高度
  function resizeFn() {
    // 实际内容高度
    const totalHeight = document.documentElement.offsetHeight || document.body.offsetHeight
    // 可视区域高度
    const windowHeight = window.innerHeight || document.documentElement.clientHeight;
    let scrollFn = _.throttle(fn, 100);
    // SCROLL_THRESHOLD是出现滚动到顶部的阈值
    const SCROLL_THRESHOLD = 40;
    if (totalHeight - windowHeight > SCROLL_THRESHOLD) {
      window.removeEventListener('scroll', scrollFn);
      window.addEventListener('scroll', scrollFn);
    }
  }

  window.addEventListener('resize', _.throttle(resizeFn, 100));
  resizeFn();
  // 初始需要执行是因为部分浏览器刷新页面会定位到之前滚动的位置
  fn()
})()
第二题

整体思路:

  1. 需要用节流函数来控制scroll触发次数; 2. 滚动到顶部的按钮出现有一个阈值来控制; 3. 回到顶部的样式要利用margin-top来实现类似fixed的效果

html

<div id="container">
   <div id="body">
   </div>
</div>
<a href="" id="scrollTop">回到顶部</a>

css

#scrollTop {
  visibility: hidden;
  position: absolute;
  right: 10px;
  margin-top: calc(20px - 200px); /*200px 是container的最大高度*/
}

#scrollTop.show {
  visibility: visible
}

js

const $container = document.getElementById('container')
const $body = document.getElementById('body')
const $topBtn = document.getElementById('scrollTop')
const containerHeight = $container.clientHeight;
const bodyHeight = $body.offsetHeight;
// SCROLL_THRESHOLD是出现滚动到顶部的阈值
const SCROLL_THRESHOLD = 40;
if (bodyHeight - containerHeight > SCROLL_THRESHOLD) {
  function scrollFn() {
    $container.scrollTop > 0 ? $topBtn.classList.add('show') : $topBtn.classList.remove('show')
  }
  if (bodyHeight > containerHeight) {
    $container.addEventListener('scroll', _.throttle(scrollFn, 100))
  }
}

shirleyMHao avatar Jan 24 '19 03:01 shirleyMHao

第 1 题和第 2 题,两大问题,一个对策。已封装成组件,只需为构造函数传入需要“返回顶部”功能的容器元素即可,默认为 body 元素。

在线演示

.back-top {
  position: fixed;
  margin-top: -15px;
  margin-left: -15px;
  transform: translate(-100%, -100%);
  white-space: nowrap;
}
/**
 * 返回顶部
 * @descr: 给定一个容器,当滚动高度超出1倍的容器高度,显示返回顶部,否则隐藏
 * @param {object HTMLElement} [elm] - HTML元素,可选,默认为 body 元素
 */
function ScrollBackTop(elm) {
  var that = this;
  var timer = null;

  this.elm = elm || document;
  this.elmBox = elm || document.documentElement;
  this.render();

  // 监听容器 scroll 事件
  this.elm.addEventListener('scroll', function() {
    clearTimeout(timer);
    timer = setTimeout(function() {
      that.scrolling();
    }, 20);
  }, false);

  // 监听视窗尺寸改变
  window.addEventListener('resize', function() {
    clearTimeout(timer);
    timer = setTimeout(function() {
      that.scrolling();
    }, 20);
  }, false);
}

// 生成按钮
ScrollBackTop.prototype.render = function() {
  var that = this;
  var btn = document.createElement('a');
  var text = document.createTextNode('返回顶部↑');

  btn.className = 'back-top';
  btn.href = '#';
  btn.hidden = true;

  // 监听点击
  btn.addEventListener('click', function(event) {
    if (that.elm === document) {
      window.scrollTo(0, 0);  // 兼容移动端
    } else {
      that.elmBox.scrollTop = 0;
    }

    event.preventDefault();
    return false;
  }, false);

  btn.appendChild(text);
  (this.elm === document ? document.body : this.elm).appendChild(btn);

  this.btn = btn;
}

ScrollBackTop.prototype.scrolling = function() {
  var elmBox = this.elmBox;
  var scroll_y = this.elm === document ? window.pageYOffset : elmBox.scrollTop;
  var offset_x = elmBox.offsetLeft;
  var offset_y = elmBox.offsetTop;
  var x = elmBox.clientWidth;
  var y = elmBox.clientHeight;

  if (scroll_y >= y) {
    this.btn.hidden = false;

    // 按钮的显示位置
    this.btn.style.top = offset_y + y + 'px';
    this.btn.style.left = offset_x + x + 'px';
  } else {
    this.btn.hidden = true;
  }
}

new ScrollBackTop();  // 整个文档返回顶部的场景
new ScrollBackTop(document.getElementById('divBox'));  // 某个 div 容器的场景

wingmeng avatar Jan 24 '19 04:01 wingmeng

var viewHeight = -1; // 一屏的高度
var backTop = document.getElementById('backTop');
var lastModifiedBackTopHidden = true; // 上次缓存的结果,默认进入页面为隐藏状态

// 尺寸变化监听
function resizeObserver() {
    var currentEl = this,
        isDocumentNode = currentEl instanceof Document;

    // 节流方法
    window.requestAnimationFrame(function () {
        viewHeight = isDocumentNode ? document.documentElement.clientHeight : Math.min(document.documentElement.clientHeight, currentEl.clientHeight)
    });

}

// 通用监听
function observer() {

    var currentEl = this,
        isDocumentNode = currentEl instanceof Document;

    // 偏移位置
    var offset = isDocumentNode ? window.pageYOffset : currentEl.scrollTop

    // 获取一屏的高度 只赋值一次
    if (viewHeight < 0) {
        resizeObserver.call(currentEl)
    }

    // 节流方法
    window.requestAnimationFrame(function () {
        var backTopHidden = offset > viewHeight;
        if (lastModifiedBackTopHidden != backTopHidden) {
            // 将上次比较的结果缓存,减少设置hidden的频次
            lastModifiedBackTopHidden = backTopHidden;
            backTop.hidden = !backTopHidden
        }
    });
}

// 第一题
document.addEventListener('scroll', observer)

// 第二题
document.getElementsByClassName('placeholder')[0].addEventListener('scroll', observer)

// resize 事件监听 当发生 resize 时,会导致一屏的高度变化
document.addEventListener('resize', resizeObserver);

// 初次加载页面时,需要判断滚动条是否在中间状态
document.addEventListener('DOMContentLoaded', observer);

代码片段:https://jsbin.com/katonod/2/edit?html,css,js,output

ghost avatar Jan 24 '19 05:01 ghost

第一题

var topBtn = document.getElementById('bakcTop');
    window.onscroll=function(){
        var top = document.documentElement.scrollTop || document.body.scrollTop;
        var height = document.documentElement.clientHeight;
        if(top>=height){
            topBtn.style.display="block"
        }else{
            topBtn.style.display="none"
        }
        }

第二题

var contanier = document.querySelectorAll(".contanier")[0];
        var topBtn= document.getElementById("backTop")
        contanier.onscroll=function(){
            var height = contanier.clientHeight;
            var top = contanier.scrollTop;
            if(top>height){
                topBtn.style.display="block"
            }else{
                topBtn.style.display="none"
            }
        }

liulyu avatar Jan 24 '19 06:01 liulyu

  1. body
document.body.onscroll = function(){
    var windowHeight = document.body.clientHeight;
    var scrollTop = document.body.scrollTop;
    document.getElementById("backTop").hidden = scrollTop > windowHeight?false:true;
};
  1. div
var contentBox = document.getElementById("content");
contentBox.onscroll = function(e){
    var boxHeight = parseFloat(window.getComputedStyle(contentBox).height);
    var scrollTop = e.target.scrollTop;
    document.getElementById("backTop").hidden = scrollTop > boxHeight?false:true;
};

本地console的时候确实发现滚动事件触发频率很高,看到评论才发现还需要节流等等。。

boysama avatar Jan 24 '19 07:01 boysama

基于CSS世界有6.4节 和 https://www.zhangxinxu.com/wordpress/2018/10/scroll-behavior-scrollintoview-平滑滚动

//随便创建个可以滚动的东西
const creatBody = (id, css) => {
    const
        div = document.createElement('div'),
        ul = document.createElement('ul'),
        uid = id || 'one',
        cid = uid + '_fisrt'

    for (let i = 0; i < 30; i++) {
        let t = document.createElement('li')
        if (i == 0) {
            t.id = cid
        }
        t.innerHTML = `没什么意义的列表`
        t.style.padding = '20px'
        ul.appendChild(t)
    }
    const _css = css ? css : {
        overflow: 'scroll',
        height: '500px',
        'scroll-behavior': "smooth",
    }
    ul.id = uid
    addCSS({ position: 'relative' }, div)
    addCSS(_css, ul)
    div.appendChild(ul)
    document.body.appendChild(div)
    return ul
}

//创建回到顶部的按钮
const creatHandle = (el, html) => {
    let handle = document.createElement('a')
    handle.innerHTML = '回到顶部'

    const css = {
        position: html ? 'fixed' : 'absolute',
        top: '70%',
        right: '50px',
        cursor: 'pointer',
        border: '1px solid red',
        display: 'none'
    }
    addCSS(css, handle)
    //
    if (typeof window.getComputedStyle(document.body).scrollBehavior == 'undefined') {
        // 传统的JS平滑滚动处理代码...
        console.log('Hellow Edge/IE')
        console.log(el)
        handle.onclick = backTop(el)
    } else {
        handle.href = '#' + (html ? el.id : el.childNodes[0].id)
    }

    if (html)
        document.body.appendChild(handle)
    else
        el.appendChild(handle)

    return handle
}

//js滚动动画,在edge试了下能用
const backTop = (el) => {
    return e => {
        let target
        if (el.scrollTop) {
            target = el
        } else if (document.documentElement.scrollTop) {
            target = document.documentElement
        } else if (document.body.scrollTop) {
            target = document.body
        }

        console.log("开始滚了")
        cancelAnimationFrame(timer);
        let timer = requestAnimationFrame(function fn() {
            if (target.scrollTop > 0) {
                target.scrollTop = target.scrollTop - 20;
                timer = requestAnimationFrame(fn);
            } else {
                cancelAnimationFrame(timer);
            }
        });
    }
}
//滚动事件
const showBackTop = (backTop) => {

    return (el) => {
        const
            target = el.srcElement,//documentElement返回html根元素,再后面的是兼容edge
            top = target.scrollTop || document.documentElement.scrollTop || window.pageYOffset || document.body.scrollTop,
            cHeihgt = target.clientHeight || target.documentElement.clientHeight,
            r = Math.floor(top / cHeihgt * 10)
        // console.log(r)
        // console.log('top:' + top)
        if (r < 3 && backTop.style.display !== 'none') {
            console.log('none')
            addCSS({ display: 'none' }, backTop)
        } else if (r >= 3 && backTop.style.display === 'none') {
            console.log('该出现backTop了')
            addCSS({ display: 'block' }, backTop)
        }
    }

}

//增加css
const addCSS = (obj, target) => {
    //兼容下ie
    if (!Object.entries) {
        Object.entries = (obj) => {
            let arr = [];
            for (let key of Object.keys(obj)) {
                arr.push([key, obj[key]]);
            }
            return arr;
        }
    }

    for (let [key, values] of Object.entries(obj)) {
        // console.log('key: ' + key + ' values: ' + values)
        target.style[key] = values
    }
}



addCSS({ 'scroll-behavior': "smooth" }, document.querySelector('html'))
const one = creatBody('one')
const two = creatBody(two, { padding: '50px' })
const h1 = creatHandle(one, false)
const h2 = creatHandle(two, true)

window.onscroll = showBackTop(h2)
one.onscroll = showBackTop(h1)

odex21 avatar Jan 24 '19 13:01 odex21

偷个懒写在一起啦

<style>
        * {
            margin: 0;
            padding: 0;
        }

        body {
            min-height: 100vh;
            height: 3000px;
        }

        #backTop{
            position: fixed;
            bottom: 10px;
            right: 10px;
        }


        #bodyDiv {
            height: 500px;
            overflow: auto;
        }
        .content {
            height: 3000px;
            background-color: #ddd;
        }

        .content2 {
            height: 1500px;
            background-color: silver;
        }
        .body-border{
            position: relative;
        }
        #backTop2{
            position: absolute;
            bottom: 10px;
            right: 30px;
        }
    </style>
<body>
<div class="body-border">
    <div id="bodyDiv">
        <div class="content2">bodyDiv</div>
    </div>
    <a href="#" id="backTop2" onclick="backTop2()" hidden>返回顶部2</a>
</div>
<div class="content">body content</div>
<a href="#" id="backTop" onclick="backTop()" hidden>返回顶部</a>
</body>
 var divBody = document.getElementById('bodyDiv')
    var isHiddenBody = document.getElementById('backTop').getAttribute('hidden');
    var isHiddenBody2 = document.getElementById('backTop2').getAttribute('hidden');

    window.addEventListener('scroll', bodyScroll);
    divBody.addEventListener('scroll', divScroll);

    function bodyScroll (e) {
        var windownHeight = window.screen.availHeight;
        if (document.body.scrollHeight > windownHeight) {
            var top = document.body.scrollTop || document.documentElement.scrollTop;
            if (top >= windownHeight && isHiddenBody !== null) {
                document.getElementById('backTop').removeAttribute('hidden');
                isHiddenBody = null
            } else if (top < windownHeight && isHiddenBody === null) {
                document.getElementById('backTop').setAttribute('hidden', '');
                isHiddenBody = ''
            }
        }
    }

    function divScroll (e) {
        var divHeight = divBody.offsetHeight;
        if (divBody.scrollHeight > divHeight) {
            var top = divBody.scrollTop;
            if (top >= divHeight && isHiddenBody2 !== null) {
                document.getElementById('backTop2').removeAttribute('hidden');
                isHiddenBody2 = null
            } else if (top < divHeight && isHiddenBody2 === null) {
                document.getElementById('backTop2').setAttribute('hidden', '');
                isHiddenBody2 = ''
            }
        }
    }

    function backTop () {
        document.getElementsByTagName('body')[0].scrollTop = 0;
    }

    function backTop2 () {
        document.getElementById('bodyDiv').scrollTop = 0;
    }

momoxiaoqing avatar Jan 25 '19 13:01 momoxiaoqing

(function () {
    function checkIsDom(obj) {
        return obj && obj.nodeType && obj.nodeType === 1 && typeof obj.nodeName === 'string';
    }

    function ScrollTopCheck(container, target) {
        if (!(this instanceof ScrollTopCheck)) {
            return new ScrollTopCheck(container, target)
        }
        this.isDom = checkIsDom(container);
        if (!(this.isDom || container === window) || !checkIsDom(target)) {
            throw Error('container and target is required')
        }
        this.container = container;
        this.target = target;
        this.containerHeught = this.isDom
            ? parseInt(window.getComputedStyle(container).height)
            : window.innerHeight;
        this.init();
    }

    var fn = ScrollTopCheck.prototype;
    fn.init = function () {
        this.container.addEventListener('scroll', this.scroll.bind(this));
    };

    fn.scroll = function (e) {
        var hasScrollHeight = this.isDom
            ? this.container.scrollTop
            : this.container.scrollY;
        if (hasScrollHeight > this.containerHeught) {
            this.removeHidden()
        } else {
            this.addHidden();
        }
    };

    fn.addHidden = function () {
        this.target.setAttribute('hidden', 'true')
    };

    fn.removeHidden = function () {
        this.target.removeAttribute('hidden')
    };
    window && (window.ScrollTopCheck = ScrollTopCheck)
})();

//  说明:使用方法,直接在window上调用ScrollTopCheck函数或者new ScrollTopCheck(),两个必穿参数,
//  第一个是容器,比如windo或者div,第二个为需要隐藏的标签,暂且都为必传。
//  暂为适配移动端。
//  写成构造函数的形式,感觉之后如果增加逻辑比较好扩展一些,条理也更加清楚,
//  放在一个自执行函数里,避免了过多的变量造成全局的污染

wind1996 avatar Jan 25 '19 19:01 wind1996

效果预览:https://output.jsbin.com/lezirisahu#

html有两个注意点: 第一页面要有 <!DOCTYPE html> 声明; 第二禁止页面缩放:<meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">

body,html {
    scroll-behavior: smooth;
}

.btn {
    position: fixed;
    bottom: 0;
    right: 0;
    width: 100px;
    height: 100px;
    background: yellow;
    border-radius: 50%;
    line-height: 100px;
    color: gray;
    text-align: center;
    text-decoration: none;
}
<body style="height:5000px;"> 
  <a hidden  id="backTop" href="#" class="btn">返回顶部</a> 
  <div id="box" style="background:pink;height:100px;overflow: scroll;">
   我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div我这个div
  </div> 
function scroll(dom) {
  var boxHeight = '',
  scrollTop = '';
  dom.onscroll = function() {
    if (dom.nodeName == 'BODY') {
      boxHeight = document.documentElement.clientHeight || dom.clientHeight;
      scrollTop = document.documentElement.scrollTop || dom.scrollTop;
    } else {
      boxHeight = dom.clientHeight;
      scrollTop = dom.scrollTop;
    }
    if (scrollTop > boxHeight) {
      backTop.removeAttribute('hidden');
    } else {
      backTop.setAttribute('hidden', '');
    }
  };
}
var backTop = document.getElementById('backTop'),
boxContent = document.getElementById('box');
// body滚动调用
scroll(document.body);
// div滚动调用
scroll(boxContent);

popeyesailorman avatar Jan 26 '19 01:01 popeyesailorman

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
  <style>
    body {
      height: 2000px;
      background-color: rgba(0,0,0,0.05);
    }
    #wrapper {
      overflow: scroll;
      background-color: rgba(0,0,0,0.05);
      width: 500px;
      height: 500px;
      position: relative;
    }
    #content {
      height: 1000px;
      background-color: rgba(0,0,0,0.05);
    }
    #backTop1, #backTop2 {
      position: sticky;
      left: 0;
      top: 200px;
    }
  </style>
</head>
<body>
  Lorem ipsum dolor sit amet consectetur adipisicing elit. Delectus officia earum expedita, iure qui aut, optio aspernatur, tenetur ad dolorum consequatur saepe excepturi itaque nesciunt molestias distinctio amet exercitationem hic.
  <a href="#" id="backTop1" hidden>返回顶部↑</a>
  <div id="wrapper" >
    <div id="content"></div>
    <a href="#" id="backTop2" hidden>返回顶部↑</a>
  </div>
</body>
<script>
  let backTop1 = document.getElementById('backTop1')
  let backTop2 = document.getElementById('backTop2')
  let wrapper = document.getElementById('wrapper')
  let scorllTop = 0
  let windowHeight = window.innerHeight || window.outerHeight || document.documentElement.clientHeight
  let wrapperHeight = wrapper.innerHeight || wrapper.clientHeight

  function throttle (func, time) {
    let memo = null
    return function (...args) {
      if (!memo) {
        memo = setTimeout(() => {
          func(...args)
          memo = null
        }, time)
      }
    }
  }
  // 当容器为 window
  document.addEventListener('scroll', throttle(() => {
    scrollTop = document.documentElement.scrollTop
    if(windowHeight < scrollTop) {
      backTop1.removeAttribute('hidden')
    } else {
      backTop1.setAttribute('hidden', true)
    }
  }, 100))

  // 当容器为某个元素
  wrapper.addEventListener('scroll', throttle(() => {
    scrollTop = wrapper.scrollTop
    console.log(scorllTop)
    if(wrapperHeight < scrollTop) {
      backTop2.removeAttribute('hidden')
    } else {
      backTop2.setAttribute('hidden', true)
    }
  }, 100))

  document.addEventListener('resize', () => {
    windowHeight = window.innerHeight || window.outerHeight || document.documentElement.clientHeight
    wrapperHeight = wrapper.innerHeight || wrapper.clientHeight
  })
</script>
</html>

zer0fire avatar Aug 12 '20 09:08 zer0fire