zhangxinxu/quiz

DOM基础测试30

Opened this issue · 31 comments

本期小测题目如下:

.slider {
    padding: 5px 0;
    position: relative;
    margin: 30px 10%;
    --percent: 0;
}
.slider-track {
    display: block;
    width: 100%; height: 6px;
    background-color: lightgray;
    border: 0; padding: 0;
}
.slider-track::before {
    content: '';
    display: block;
    height: 100%;
    background-color: skyblue;
    width: calc(1% * var(--percent));
}
.slider-thumb {
    position: absolute;
    width: 16px; height: 16px;
    border: 0; padding: 0;
    background: #fff;
    box-shadow: 0 0 0 1px skyblue;
    border-radius: 50%;
    left: calc(1% * var(--percent)); top: 0;
    margin: auto -8px;
}

<div class="slider">
  <button class="slider-track"></button>
  <button class="slider-thumb"></button>
</div>

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

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

另外,务必提供在线demo,方便验证与评分。jsbin.com jsfiddle.net codepen.io或者国内类似站点都可以,也可以使用github page。

没有可访问demo积分-1分哟。

-----其他-----

本期小测答疑直播4月13日周六上午10:00,预计半小时左右。直播地址:https://live.bilibili.com/21193211

首位答题者会额外2积分。

感谢您的参与!

赶了个早。
demo

<h3>slider1</h3>
<div class="slider" tabindex="0" style="--percent:50">
    <button class="slider-track" tabindex="-1"></button>
    <button class="slider-thumb" tabindex="-1"  data-title="50"></button>
</div>
<h3>slider2</h3>
<div class="slider" tabindex="0" style="--percent:20">
    <button class="slider-track" tabindex="-1"></button>
    <button class="slider-thumb" tabindex="-1" data-title="20"></button>
</div>
/**借助之前的css-tips**/
.slider-thumb[data-title]:before{
    counter-reset: value var(--percent);
    content: counter(value);
}
html:hover .slider-thumb[data-title]:active:before,
html:hover .slider-thumb[data-title]:active:after,
.slider:focus .slider-thumb[data-title]:before,
.slider:focus .slider-thumb[data-title]:after {
    visibility: visible;
    transform: translate(-50%, -10px);
    opacity: 1;
}
//可能没看清题意,需要生成dom结构,现补一个(不过比较简单的结构平时都直接写在页面了)
var oSlider = document.createElement('div');
oSlider.className = 'slider';
oSlider.innerHTML = '<button class="slider-track"></button><button class="slider-thumb"></button>';
document.body.appendChild(oSlider);
oSlider.style.setProperty('--percent', 50);
;(function(){
    var slider = document.querySelectorAll('.slider');
    slider.forEach(function(el){
        el.addEventListener('mousedown',function(ev){
            const { left,width } = el.getBoundingClientRect();
            const percent = (ev.pageX-left)/width*100;
            render(el,percent);
            document.onmousemove = function(ev){
                const percent = (ev.pageX-left)/width*100;
                render(el,percent);
            }
            document.onmouseup = function(ev){
                document.onmousemove = null;
            }
        })
        el.addEventListener('keydown',function(ev){
            el.focus();
            let percent = Number(getComputedStyle(el).getPropertyValue('--percent'));
            switch (ev.keyCode) {
                case 37:
                    percent -= 1;
                    break;
                case 39:
                    percent += 1;
                    break;
                default:
                    break;
            }
            render(el,percent);
        })
    })
    function render(el,percent){
        percent = Math.min( Math.max(0,percent),100);
        el.style.setProperty('--percent', parseInt(percent,10));
    }
})()

> 在线 DEMO <

  1. 支持 PC 端和移动端;
  2. 支持缺省赋值(第 2 题);
  3. 支持键盘操作;
  4. 可使用 js 动态赋值(第 2 题);
  5. 可指定渲染容器,默认渲染到 body 标签最后(第 1 题);
  6. 支持点击“轨道”赋值定位(第 3 题);
  7. 支持滑块拖动赋值(第 4 题)。

使用方式示例

let myslider1 = new Slider({ el: '#box' });  // 指定容器
new Slider({ value: 50 });  // 缺省赋值
new Slider();  // 无参数(插入到 body 标签最后,赋值为 0)

myslider1.val(value);  // js 动态赋值

已封装的 Slider 组件

class Slider {
  constructor(opts = {}) {
    this.el = opts.el;
    this.value = opts.value || 0;
    this.slider = null;
    this.render();
    this.bindEvt();

    return {
      // 赋值方法
      val: (value) => {
        this.val(value);
      }
    }
  }

  // 渲染 DOM
  render() {
    const container = document.querySelector(this.el);
    const slider = document.createElement('div');

    this.slider = slider;

    // 有缺省值则赋值
    if (this.value) {
      this.val(this.value);
    }

    slider.className = 'slider';
    slider.innerHTML = (
      // 轨道无需获取焦点
      `<button class="slider-track" tabindex="-1"></button>
      <button class="slider-thumb"></button>`
    );

    if (container) {
      container.appendChild(slider);
    } else {
      // 若未指定容器,则在 body 标签最后插入 DOM 结构
      document.body.appendChild(slider);
    }
  }

  // 绑定事件
  bindEvt() {
    const { slider } = this;
    const slider_track = slider.querySelector('.slider-track');
    const slider_thumb = slider.querySelector('.slider-thumb');
    let readyMove = false;

    const startHandle = e => {
      if (e.target === slider_thumb) {
        e.stopPropagation();
        readyMove = true;
      }
    };

    const moveHandle = e => {
      if (readyMove) {
        this.computeVal(e);
      }
    };

    const endHandle = () => readyMove = false;

    // 点击监听
    slider.addEventListener('click', e => {
      // 点击轨道
      if (e.target === slider_track) {
        this.computeVal(e);
      }
    }, false);

    // 键盘监听
    slider.addEventListener('keydown', e => {
      // 滑块获得焦点
      if (document.activeElement === slider_thumb) {
        let value = this.val();
        
        switch(e.keyCode) {
          case 37:  // 左箭头
            value--;
            break;
          case 39:  // 右箭头
            value++;
            break;
        }

        this.val(value);
      }
    }, false);

    // 开始拖动
    slider.addEventListener('touchstart', startHandle);
    slider.addEventListener('mousedown', startHandle);

    // 拖动中
    window.addEventListener('touchmove', moveHandle);
    window.addEventListener('mousemove', moveHandle);

    // 拖动结束
    window.addEventListener('touchend', endHandle);
    window.addEventListener('mouseup', endHandle);
  }

  // 计算当前值
  computeVal(e) {
    const { width, left } = this.slider.getBoundingClientRect();
    let posX = e.pageX;

    if (e.touches) {  // 兼容移动端
      posX = e.touches[0].pageX;
    }

    this.val((posX - left) / width * 100);
  }

  // 赋值 & 取值
  val(value) {
    if (typeof value === 'undefined') {
      // 返回当前 slider 的 percent 值
      return this.slider.style.getPropertyValue('--percent').trim() || 0;
    }

    if (isNaN(value)) {  // 过滤非法字符
      return;
    }

    // 边界处理
    if (value < 0) {
      value = 0;
    } else if (value > 100) {
      value = 100;
    }

    this.slider.style.setProperty('--percent', value);
  }
}
    var $ = q => document.querySelector(q)
    var node = document.createElement('DIV')
    node.setAttribute('class', 'slider')
    node.innerHTML = `    <button class="slider-track"></button>
      <button class="slider-thumb"></button>`
    $('body').appendChild(node)
    setTimeout(() => {
      // window.addEventListener('click', e => e.stopPropagation())
      $('.slider-thumb').addEventListener('click', e => e.stopPropagation())
      $('.slider').style.setProperty('--percent', 50)
      document.querySelector('.slider').addEventListener('click', function(e) {
        console.log(e.pageX, e.clientX, e.offsetX)
        this.style.setProperty(
          '--percent',
          (e.offsetX / this.clientWidth) * 100,
        )
      })

      const dragHandle = function(e1) {
        var Length = e1.clientX - $('.slider').getBoundingClientRect().x
        console.log(e1.clientX, $('.slider'))

        Length <= 0 && $('.slider').style.setProperty('--percent', 0)
        Length >= $('.slider').clientWidth &&
          $('.slider').style.setProperty('--percent', 100)
        Length >= 0 &&
          Length <= $('.slider').clientWidth &&
          $('.slider').style.setProperty(
            '--percent',
            (Length / $('.slider').clientWidth) * 100,
          )
      }
      $('.slider').addEventListener('mousedown', function(e) {
        console.log('down')

        document.addEventListener('mousemove', dragHandle)
      })
      document.addEventListener('mouseup', function(e) {
        document.removeEventListener('mousemove', dragHandle)
      })
    }, 200)

code pen DEMO

性感代码在线预览

class Slider{
  constructor(v=0){
    this.isDraging = false
    this.init()
    this.bindClick()
    this.bindDrag()
    this.setPercent(v)
  }

  init(){
    let slider = document.createElement('div')
    slider.className = 'slider'
    slider.innerHTML = `<button class="slider-track"></button><button class="slider-thumb"></button>`
    document.body.appendChild(slider)
    this.slider = slider
  }

  bindClick(){
    this.slider.addEventListener('click', e => {
      if (this.isDraging) return
      let percent = Math.round(e.offsetX / this.slider.offsetWidth * 100)
      this.setPercent(percent)
    }, false)
  }

  bindDrag(){
    let start = 0
    let x = 0
    let thumb = this.slider.getElementsByClassName('slider-thumb')[0]
    let max = this.slider.offsetWidth

    function down(e){
      if (e.target.className === 'slider-thumb'){
        e.preventDefault()
        this.isDraging = true
        start = e.pageX
        x = this.getPercent()
      }
    }

    function move(e){
      if (!this.isDraging) return
      e.preventDefault()
      let now = Math.round((e.pageX - start) / max * 100) + x
      if (now < 0 ) now = 0
      else if (now > 100) now = 100
      this.setPercent(now)
    }

    function up(e){
      if (!this.isDraging) return
      // 在下次事件循环解锁click事件
      setTimeout(() => {
        this.isDraging = false
      }, 0)
    }
    window.addEventListener('mousedown', down.bind(this), false)
    window.addEventListener('mousemove', move.bind(this), false)
    window.addEventListener('mouseup', up.bind(this), false)
  }

  setPercent(v=0){
    if (typeof v !== 'number' || v > 100 || v < 0) throw new TypeError('param error')
    this.slider.style.setProperty('--percent', v)
  }

  getPercent(){
    return +window.getComputedStyle(this.slider, null).getPropertyValue('--percent')
  }

}

let s = new Slider(50)

在线预览

<div class="slider" data-percent="10">
    <button class="slider-track"></button>
    <button class="slider-thumb"></button>
</div>
<div class="slider" data-percent="30">
    <button class="slider-track"></button>
    <button class="slider-thumb"></button>
</div>
<div class="slider" data-percent="40">
    <button class="slider-track"></button>
    <button class="slider-thumb"></button>
</div>
    (function(){
        let aSlider = document.querySelectorAll(".slider");
        [...aSlider].forEach((item, index) => {
            let initPercent = item.dataset.percent;
            setPercent(item, initPercent);
            item.onmousedown = function(e){
                let {left, width} = item.getBoundingClientRect();
                let percent = (e.pageX - left) / width * 100;
                setPercent(item, percent);
                document.onmousemove = function(e){
                    let percent = (e.pageX - left) / width * 100;
                    setPercent(item, percent);
                };
                document.onmouseup = function(e){
                    document.onmousemove = document.onmouseup = null;
                };
            }
        });

        function setPercent(el, percent){
            percent = Math.min(Math.max(0, percent), 100);
            el.style.setProperty("--percent", percent);
        }
    })()

demo

let silderDom = document.createElement('div');
div.setAttribute('class', 'slider');
div.style.setProperty('--percent', '50');
let trackDom = document.createElement('button');
let thumbDom = document.createElement('button');
button1.setAttribute('class', 'slider-track');
button2.setAttribute('class', 'slider-thumb');
div.appendChild(button1);
div.appendChild(button2);
document.body.appendChild(div);

trackDom.addEventListener('click', function(event) {
    let { width: trackDomWidth, left: trackDomLeft } = trackDom.getBoundingClientRect();
    let { offsetX } = event;
    let percent = offsetX / trackDomWidth;
    silderDom.style.setProperty('--percent', percent * 100);
});
let startX;
let thumbDomMove = function(event) {
        let { width: trackDomWidth, left: trackDomLeft } = trackDom.getBoundingClientRect();
        let endX = Math.min(Math.max(event.pageX, trackDomLeft), trackDomLeft + trackDomWidth);
        let percent = Number(silderDom.style.getPropertyValue('--percent'));
        let newPercent = percent + (endX - startX) / trackDomWidth * 100;
        newPercent = Math.min(Math.max(newPercent, 0), 100);
        silderDom.style.setProperty('--percent', newPercent);
        startX = endX;
    };
document.addEventListener('mousedown', function(event) {
    let target = event.target;
    if (target !== thumbDom) return;
    startX = event.pageX;
    document.addEventListener('mousemove', thumbDomMove);
});
document.addEventListener('mouseup', function() {
    document.removeEventListener('mousemove', thumbDomMove);
});

Code Pen 在线demo

(function(window, document) {
  // 第一题
  var body = document.body;
  var slider = createElement("div", "slider");
  var track = createElement("div", "slider-track");
  var thumb = createElement("div", "slider-thumb");

  slider.appendChild(track).appendChild(thumb);
  body.appendChild(slider);

  // 第二题
  slider.style.setProperty("--percent", 50);

  // 第三题, 第四题
  var isTouch = "ontouchstart" in window;
  var mouseDownEvent = isTouch ? "touchstart" : "mousedown";
  var mouseMoveEvent = isTouch ? "touchmove" : "mousemove";
  var mouseUpEvent = isTouch ? "touchend" : "mouseup";
  var sliderWidth;

  slider.addEventListener(
    mouseDownEvent,
    function(e) {
      var target = e.target || srcElement;
      var className = target.className;

      sliderWidth = slider.offsetWidth;
      switch (className) {
        // 第三题
        case "slider-track":
          mouseMove(e);
          break;
        // 第四题
        case "slider-thumb":
          // 使用window绑定事件,因为鼠标离开后有问题
          document.addEventListener(mouseMoveEvent, mouseMove);
          document.addEventListener(mouseUpEvent, mouseUp);
          break;
        default:
          console.log(e);
      }
    },
    false
  );

  // 滑块移动到指定位置
  function mouseMove(e) {
    var pos = isTouch ? e.touches[0].clientX : e.clientX;
    setPercentByPosition(pos);
  }

  // 鼠标释放时,解除绑定事件
  function mouseUp(e) {
    document.removeEventListener(mouseMoveEvent, mouseMove);
    document.removeEventListener(mouseUpEvent, mouseUp);
  }

  // 根据鼠标位置设置百分比
  function setPercentByPosition(pos) {
    var distance = pos - slider.getBoundingClientRect().left;
    var percent = ~~(100 * distance / sliderWidth);
    slider.style.setProperty(
      "--percent",
      percent > 0 ? (percent < 100 ? percent : 100) : 0
    );
  }

  // 创建带class的元素
  function createElement(tagName, className) {
    var tag = document.createElement(tagName);
    tag.className = className;
    return tag;
  }
})(window, document);

>>demo<<
试着渲染1w个slider,发现chrome开控制台就会跳帧,firefox就不会...

var newEl=(tag,className)=>{
    var ele=document.createElement(tag)
    ele.className=className
    return ele
}
var setStylePercent=function(number){
    number=number>1&&1||number<0&&'0'||number.toFixed(4)
    this.style.setProperty('--percent',number*1e2)
}
var Slider=(initPer)=>{
    var eSlider=newEl('div','slider')
        ,eTrack=newEl('button','slider-track')
        ,eThumb=newEl('button','slider-thumb')
    eSlider.append(eTrack,eThumb)
    setStylePercent.call(eSlider,initPer)
    return eSlider
}
document.addEventListener('click',function(event){
    if(event.target.className=='slider-track'){
        eSlider=event.target.parentElement
        setStylePercent.call(eSlider,event.offsetX/eSlider.clientWidth)
        eSlider=null
    }
})
for(evt of [{xstart:'touchstart',xmove:'touchmove',xend:'touchend'},
 {xstart:'mousedown',xmove:'mousemove',xend:'mouseup'}])
  document.addEventListener(evt.xstart,function(event){
      if(event.target.className=='slider-thumb'){
          var eSlider=event.target.parentElement
          var {width,left}=eSlider.getBoundingClientRect()
          var moveFunc=function(event){
              setStylePercent.call(eSlider,((event.touches&&event.touches[0]||event).pageX-left)/width)
          }
          document.addEventListener(evt.xmove,moveFunc)
          document.addEventListener(evt.xend,()=>{
              document.removeEventListener(evt.xmove,moveFunc)
          })
      }
  })

window.addEventListener('DOMContentLoaded',() => {
    var a=document.createDocumentFragment()
    for(var i=0;i<1e4;i++)
        a.appendChild(Slider(.5))
    document.body.append(a)
})

在线demo

let slider = document.createElement('div')
slider.setAttribute('class', 'slider')
slider.innerHTML =
    `
        <button class="slider-track"></button>
        <button class="slider-thumb"></button>
      `
document.body.appendChild(slider)
slider.style.setProperty('--percent', 50)

const {
    left,
    width
} = slider.getBoundingClientRect()
// 鼠标按下时,判断target是否为thumb元素
let dragging = false
let thumb = slider.querySelector('.slider-thumb')

slider.addEventListener('click', (e) => {
    // 避免其click事件与子元素mousedown事件一起发送时,出现滑块微小移动现象
    // if (e.target === thumb) return
    if (dragging) {
        dragging = false
        return
    }
    slider.style.setProperty('--percent', (e.pageX - left) / width * 100)
})


let fnMove = (e) => {
    let diff = e.pageX - left
    let sMoveLength = Math.min(Math.max(0, diff), width)
    slider.style.setProperty('--percent', sMoveLength / width * 100)
}
thumb.addEventListener('mousedown', (e) => {
    dragging = true
    window.addEventListener('mousemove', fnMove)
})

window.addEventListener('mouseup', (e) => {
    window.removeEventListener('mousemove', fnMove)
})
// 1、
var oFragment = document.createDocumentFragment('div')
var div = document.createElement('div')
div.className = 'slider';
div.innerHTML = '<button class="slider-track"></button><button class="slider-thumb"></button>'
oFragment.appendChild(div)
document.body.appendChild(oFragment)

var slider = document.querySelector('.slider')
var trackWidth = parseFloat(window.getComputedStyle(slider).width)
var trackLeft = slider.getBoundingClientRect().left
var thumb = document.querySelector('.slider-thumb')
var isMove = false
function setTrackPos(percent){
    slider.style.setProperty('--percent',percent)
}
// 2、
setTrackPos('50')

// 3~4
slider.addEventListener('mousedown', function(event){
    event.preventDefault()
    if(event.target === thumb){
        event.stopPropagation()
        isMove = true
    }else{
        var percent = (event.offsetX/trackWidth) * 100
        setTrackPos(percent)
    }
})
window.addEventListener('mousemove', function(event){
    if(isMove){
        var moveX = event.pageX-trackLeft
        if(moveX >= 0 && moveX <= trackWidth){
            var percent = (moveX/trackWidth) * 100
            setTrackPos(percent) 
        }
    }
})
window.addEventListener('mouseup', function(event){
    isMove = false
})

demo

链接

 class Slider {
      constructor() {
        this.init();
        this.setPercent(66);
        this.touchHandler();
      }
      init() {
        var body = document.getElementsByTagName('body')[0];
        var ele = document.createElement('div');
        ele.setAttribute('class', 'slider');
        ele.innerHTML = `<button class="slider-track"></button><button class="slider-thumb"></button><div id="percent"></div>`;
        body.appendChild(ele);
        this.slider = document.getElementsByClassName('slider')[0];
        this.thumb = document.getElementsByClassName('slider-thumb')[0];
        this.width = this.slider.offsetWidth;
      }
      getPercent() {
        return getComputedStyle(this.slider).getPropertyValue('--percent');
      }
      setPercent(percent) {
        var element = document.getElementById('percent');
        this.slider.style.setProperty('--percent', percent);
        element.innerHTML = percent;
      }

      touchHandler() {
        function move(e) {
          let { left } = this.slider.getBoundingClientRect();
          this.setPercent(Math.round((e.pageX - left) / this.width * 100));
        }
        this.slider.onmousedown = function (e) {
          move.call(this, e);
          if (e.target !== this.thumb) return;
          this.slider.onmousemove = move.bind(this);
          this.slider.onmouseup = function () {
            this.slider.onmousemove = null;
          }.bind(this)
        }.bind(this)
      }
    }

    var test = new Slider();

Demo预览

听完讲解之后修正:

(function() {
  // 题一
  let body = document.body;
  let $slider = document.createElement("div");
  $slider.setAttribute("class", "slider");
  let $ele_1 = document.createElement("button");
  $ele_1.setAttribute("class", "slider-track");
  let $ele_2 = document.createElement("button");
  $ele_2.setAttribute("class", "slider-thumb");
  body.appendChild($slider);
  $slider.appendChild($ele_1);
  $slider.appendChild($ele_2);

  // 题二
  let $slider_styles = getComputedStyle($slider);
  $slider.style.setProperty("--percent", 50);

  // 题三
  $ele_1.addEventListener("click", function(e) {
    const { width } = $slider.getBoundingClientRect();
    const { offsetX } = e;
    const percent = offsetX / width;
    $slider.style.setProperty("--percent", percent * 100);
  });

  // 题四
  let startX;
  let moveFun = function(event) {
    const { width } = $slider.getBoundingClientRect();
    let percent = $slider_styles.getPropertyValue("--percent").trim();
    let currentX = event.pageX;
    let newPercent = Number(percent) + ((currentX - startX) / width) * 100;
    newPercent = Math.min(Math.max(0, newPercent), 100);
    $slider.style.setProperty("--percent", newPercent);
    startX = currentX;
  };
  document.addEventListener("mousedown", function(e) {
    if (e.target !== $ele_2) {
      return;
    }
    startX = e.pageX;
    document.addEventListener("mousemove", moveFun);
  });
  document.addEventListener("mouseup", function(e) {
    document.removeEventListener("mousemove", moveFun);
  });
})();

deom

题一

let slider = document.createElement('div')
let sliderTrack = document.createElement('button')
let sliderThumb = document.createElement('button')
slider.classList.add('slider')
sliderTrack.classList.add('slider-track')
sliderThumb.classList.add('slider-thumb')
slider.appendChild(sliderTrack)
slider.appendChild(sliderThumb)
document.body.appendChild(slider)

题二

let percent = 50
slider.style.setProperty('--percent', percent)

题三

sliderTrack.addEventListener('click',  function(e){
  percent = e.offsetX / sliderTrack.offsetWidth * 100
  slider.style.setProperty('--percent', percent)
})

题四

function thumbMove (e) {console.log(e)
  let currentPx = percent * sliderTrack.offsetWidth / 100
  let newPx = currentPx + e.movementX
  percent = newPx / sliderTrack.offsetWidth * 100
  percent < 0 && (percent = 0)
  percent > 100 && (percent = 100)
  slider.style.setProperty('--percent', percent)
}
sliderThumb.addEventListener('mousedown',function(e){
  document.addEventListener('mousemove', thumbMove)
}) 
document.addEventListener('mouseup', () =>{
  document.removeEventListener('mousemove', thumbMove)
  sliderThumb.blur()
})

在线 DEMO

      //   第一题
      function appendDOM(el) {
        const node = document.createElement('div')
        node.className = 'slider'
        node.innerHTML =
          '<button class="slider-track"></button><button class="slider-thumb"></button>'
        if (el) {
          document.querySelector(el).appendChild(node)
        } else {
          document.querySelector('body').appendChild(node)
        }
        return node
      }
      const element = appendDOM()

      //   第二题
      function setPercent(el, val) {
        el.style.setProperty('--percent', Math.min(100, Math.max(0, val)))
      }
      function setPercent50() {
        setPercent(element, 50)
      }

      //   第三题
      function addClick(el) {
        const handleClick = e => {
          const rect = el.getBoundingClientRect()
          let offsetWidth = ((e.clientX - rect.left) / rect.width) * 100
          setPercent(el, offsetWidth)
        }
        el.addEventListener('click', handleClick)
      }
      addClick(element)

      //   第四题
      function addDrag(el) {
        let status = false // 是否可拖动
        let startX = 0 // 记录最开始点击的X坐标
        let left = 0 // 记录当前已经移动的距离
        const getPercent = el => {
          return +window
            .getComputedStyle(el, null)
            .getPropertyValue('--percent')
        }
        const handleStart = e => {
          if (e.target.className.indexOf('slider-thumb') > -1) {
            status = true
            startX = e.clientX || e.touches[0].pageX
            left = getPercent(el)
          }
        }
        const handleMove = e => {
          if (status) {
            const rect = el.getBoundingClientRect()
            let endX = e.clientX || e.touches[0].pageX
            let offsetWidth = ((endX - startX) / rect.width) * 100 + left
            setPercent(el, offsetWidth)
          }
        }
        const handleEnd = e => {
          if (status) {
            status = false
          }
        }
        // pc端
        el.addEventListener('mousedown', handleStart)
        document.addEventListener('mousemove', handleMove)
        document.addEventListener('mouseup', handleEnd)
        // 移动端
        el.addEventListener('touchstart', handleStart)
        document.addEventListener('touchmove', handleMove)
        document.addEventListener('touchend', handleEnd)
      }
      addDrag(element)

Demo
https://codepen.io/crazyboy/pen/pBPKye
1554987682

(function () {
	function createElement(type, className) {
		var ele = document.createElement(type);
		ele.className = className;
		return ele;
	}
	function addHandler(element, type, handler) {
		if (element.addEventListener) {
			element.addEventListener(type, handler);
		} else if (element.attachEvent) {
			element.attachEvent('on' + type, handler);
		} else {
			element['on' + type] = handler;
		}
	}
	function getOffsetLeft(ele) {
		var eleLeft = 0;
		do {
			eleLeft += ele.offsetLeft;
			ele = ele.offsetParent;
		} while (ele);
		return eleLeft;

	}
	function setPercent(event) {
		var track = document.querySelector('.slider-track');
		var eventX = event.type.slice(0, 5) === 'touch' ? event.targetTouches[0].clientX : event.clientX;
		var percent = ((eventX - getOffsetLeft(track))  * 100 / track.offsetWidth).toFixed(2);
		if (0 <= percent && percent <= 100 ) {
			document.querySelector('.slider').style.setProperty('--percent', percent);
		}
	}
	var sliderElement = createElement('div', 'slider');
	var trackElement = createElement('button', 'slider-track');
	var thumbElement = createElement('button', 'slider-thumb');
	document.body.appendChild(sliderElement);
	sliderElement.appendChild(trackElement);
	sliderElement.appendChild(thumbElement);

	thumbElement.setAttribute('draggable', true);
	addHandler(trackElement, 'click', setPercent);
	addHandler(thumbElement, 'drag', setPercent);
	addHandler(thumbElement, 'touchmove', setPercent);
})();

在线DEMO

let sliderDom = document.createElement('div');
let slideTrackDom = document.createElement('div');
let sliderThumbDom = document.createElement('div');
sliderDom.className = 'slider';
slideTrackDom.className = 'slider-track';
sliderThumbDom.className = 'slider-thumb';
sliderDom.append(slideTrackDom);
sliderDom.append(sliderThumbDom);
document.body.append(sliderDom);

let slider = document.getElementsByClassName('slider')[0];
let sliderThumb = document.getElementsByClassName('slider-thumb')[0];
let sliderTrack = document.getElementsByClassName('slider-track')[0];
let maxX = getCssValue(slider, 'width');
setCssValue(slider, '--percent', 50);

function getCssValue(context, cssName) {
  let value = getComputedStyle(context).getPropertyValue(cssName);
  if (value.indexOf('px')) {
    return Number(value.slice(0, -2))
  } else {
    return value;
  }
}

function setCssValue(context, cssName, cssValue) {
  context.style.setProperty(cssName, cssValue);
}

function setPercentNum(percent) {
  percentNum.innerHTML = Math.ceil(percent).toFixed(0) + '%';
}

function down(e) {
  e = e || window.event;
  this.left = getCssValue(sliderThumb, 'left');
  this.startX = e.pageX;
  this._MOVE = move.bind(this);
  this._UP = up.bind(this);
  if (document.addEventListener) {
    document.addEventListener('mousemove', this._MOVE);
    document.addEventListener('mouseup', this._UP);
  } else {
    document.attachhEvent('onmousemove', this._MOVE);
    document.attachhEvent('onmouseup', this._UP);
  }
}

function move(e) {
  e = e || window.event;
  let startX = this.startX;
  let left = this.left;
  let moveX = left + e.pageX - startX;
  let curX = moveX < 0 ? 0 : (moveX > maxX ? maxX: moveX);
  let percent = curX / maxX * 100;
  setCssValue(this, '--percent', percent);
  setPercentNum(percent);
}

function up() {
  if (document.addEventListener) {
    document.removeEventListener('mousemove', this._MOVE);
    document.removeEventListener('mouseup', this._UP);
  } else {
    document.detachEvent('onmousemove', this._MOVE);
    document.detachEvent('onmouseup', this._UP);
  }
}

slider.addEventListener('mousedown', down);

slider.addEventListener('click', function(e) {
  e = e || window.event;
  if (e.target === sliderTrack) {
    let offsetX = e.offsetX;
    let percent = e.offsetX / maxX * 100;
    setCssValue(this, '--percent', percent);
    setPercentNum(percent);
  }
})

小demo

//1.添加dom结构
 var div=document.createElement('div');
 div.setAttribute('class','slider')
 var btn1=document.createElement('button');
 btn1.setAttribute('class','slider-track');
 var btn2=document.createElement('button');
 btn2.setAttribute('class','slider-thumb')
 var att=document.createAttribute("class");
 document.body.appendChild(div);
 div.appendChild(btn1);
 div.appendChild(btn2);
//2.给.slide设置--percent值为50,此时滑杆在定位中间位置
 div.style.setProperty("--percent",50);
//3.点击track轨道按钮,根据点击位置给slide设置--precent
 var width=div.offsetWidth;
//另一种方式:width=window.getComputedStyle(div,null).getPropertyValue('width'));
 var left=div.offsetLeft;
 btn1.addEventListener('click',e=>{
    var percent=(e.clientX-left)/width*100;
    div.style.setProperty("--percent",percent);
});
//4.拖动thumb滑杆按钮,根据点击位置给slide设置--precent
 btn2.addEventListener('mousedown',e=>{
  if(e.target==btn2){
    var percent=(e.clientX-left)/width*100;
    div.style.setProperty("--percent",percent);
    document.onmousemove = e=>{
      var percent=(e.clientX-left)/width*100;
      if(percent<0){
        percent=0;
      }
      if(percent>100){
        percent=100;
      }
      div.style.setProperty("--percent",percent);
    }
    document.onmouseup=e=>{
           document.onmousemove=null;
    }
 }
})

我是demo,快来点击我吧 ^ v ^

JS代码

// 问题1:在body标签最后插入DOM结构

var bodyTag = document.getElementsByTagName("body")[0];
var slider=document.createElement("div");
slider.setAttribute("class", "slider");
var track=document.createElement("button");
var thumb=document.createElement("button");
track.setAttribute("class", "slider-track");
thumb.setAttribute("class", "slider-thumb");
slider.appendChild(track);
slider.appendChild(thumb);
bodyTag.appendChild(slider);

// 问题2:设置 --percent的值为50

document.getElementById("question-two").childNodes[1].style.setProperty("--percent", 50);


//问题3:点击trace轨道按钮,根据点击位置给.slider元素设置 --percent

function trackClick(e){
  console.log( "trackClick" );
  // 点击轨道位置
  // offsetLeft  元素 相对于它的直接父元素 的 偏移量
  let thumbStart = thumb.offsetParent.offsetLeft;
  let thumbWidth = thumb.offsetParent.offsetWidth;
  // e.pageX 鼠标点击位置
  let newLeft = e.pageX - thumbStart;
  let percent = Math.ceil(newLeft/thumbWidth * 100);
  slider.style.setProperty("--percent", percent);
  console.log( percent );
  
}
 // 监听点击轨道
track.addEventListener('click', e =>trackClick(e), false);


//问题4:拖动thumb滑杆按钮,根据拖动位置给.slider元素设置 --percent

// 是否正在拖动
let isMoving;
 // 监听拖动
slider.addEventListener('mousedown', e => {
  isMoving = true;
}, false);


 // 监听拖动
slider.addEventListener('mousemove', e => {
  if(isMoving){
    trackClick(e);
  }
}, false);


 // 监听拖动
slider.addEventListener('mouseup', e => {
  isMoving = false;
  trackClick(e);
}, false);

我没有做过移动端,我只会pc端(流下弱者的眼泪)

jsbin

.slider {
    padding: 5px 0;
    position: relative;
    margin: 30px 10%;
    --percent: 0;
}

.slider-track {
    display: block;
    width: 100%;
    height: 6px;
    background-color: lightgray;
    border: 0;
    padding: 0;
}

.slider-track::before {
    content: '';
    display: block;
    height: 100%;
    background-color: skyblue;
    width: calc(1% * var(--percent));
}

.slider-thumb {
    position: absolute;
    width: 16px;
    height: 16px;
    border: 0;
    padding: 0;
    background: #fff;
    box-shadow: 0 0 0 1px skyblue;
    border-radius: 50%;
    left: calc(1% * var(--percent));
    top: 0;
    margin: auto -8px;
}
<div class="slider">
    <button class="slider-track"></button>
    <button class="slider-thumb"></button>
</div>
<div class="slider">
    <button class="slider-track"></button>
    <button class="slider-thumb"></button>
</div>
<div class="slider">
    <button class="slider-track"></button>
    <button class="slider-thumb"></button>
</div>
<button id="destory">销毁组件</button>
<button id="reinit">重新绑定组件</button>
<button id="destory1">只销毁第一个组件</button>
//第一题
const slider0 = document.querySelector(".slider"),
    style = slider0.style;
style.setProperty("--percent", "50");

//第二题
/***
 * 滑动条
 * @param doms 要初始化为滑动条的元素或元素类数组
 * 
 * @return {function} destory 销毁组件
 */
function SilderBar(doms) {

    if (!(this instanceof SilderBar)) {
        return new SilderBar(...arguments);
    }

    //无论传入的是单dom还是dom类数组,都先转化为数组
    if (doms instanceof NodeList) {
        doms = [...doms];
    } else if (doms instanceof Element) {
        doms = [doms];
    }

    //过滤掉已经被绑定了sliderbar的元素
    doms.filter(dom => {
        return !this.sliderbarDoms.has(dom);
    }).forEach(dom => {
        //将元素放到集合中
        this.sliderbarDoms.add(dom);
        //为每个dom绑定事件
        Object.keys(this.sliderbarDomEvents).forEach(key => {
            dom.addEventListener(key, this.sliderbarDomEvents[key])
        })
    })

    //若window上面没有绑定过sliderbar对应的事件那么绑定上,否则不再绑定
    if (!window.__sliderbar) {
        Object.keys(this.sliderbarWindowEvents).forEach(key => {
            window.addEventListener(key, this.sliderbarWindowEvents[key])
        })
        window.__sliderbar = true;
    }

    return () => {
        //解除传入的dom上面绑定的sliderbar事件
        doms.forEach(dom => {
            Object.keys(this.sliderbarDomEvents).forEach(key => {
                dom.removeEventListener(key, this.sliderbarDomEvents[key])
            })
            this.sliderbarDoms.delete(dom)
        })

        //当所有的sliderbar都销毁以后,将window上面的事件也删除掉
        if (!this.sliderbarDoms.size) {
            Object.keys(this.sliderbarWindowEvents).forEach(key => {
                window.removeEventListener(key, this.sliderbarWindowEvents[key])
            })
            delete window.__sliderbar;
        }
    }
}
Object.assign(SilderBar.prototype, {
    sliderbarDoms: new Set(),
    sliderbarActiveDoms: new Set(),
    sliderbarDomEvents: {
        mousedown: function (e) {
            SilderBar.prototype.doMove(e, this)
            SilderBar.prototype.sliderbarActiveDoms.add(this)
        }
    },
    sliderbarWindowEvents: {
        mousemove: (e) => {
            SilderBar.prototype.sliderbarActiveDoms.forEach(dom => {
                SilderBar.prototype.doMove(e, dom)
            })
        },
        mouseup: () => {
            SilderBar.prototype.sliderbarActiveDoms.clear()
        }
    },
    doMove: (e, dom) => {
        const position = SilderBar.prototype.getActivePercentagePosition(e, dom);
        dom.style.setProperty("--percent", position);
    },
    //获取激活的百分比位置
    getActivePercentagePosition: (e, dom) => {
        const mouseX = e.x,
            domRect = dom.getBoundingClientRect(),
            domX = domRect.x,
            domWidth = domRect.width;
        if (mouseX < domX) {
            return 0;
        }
        if (mouseX > domX + domWidth) {
            return 100;
        }
        return (mouseX - domX) / domWidth * 100;
    }
})

const sliders = document.querySelectorAll('.slider'),
    slider = document.querySelector('.slider');
let destory = SilderBar(sliders),
    destory1 = SilderBar(slider);
document.querySelector("#destory").addEventListener("click", function () {
    destory();
})
document.querySelector("#destory1").addEventListener("click", function () {
    destory1();
})
document.querySelector("#reinit").addEventListener("click", function () {
    destory = SilderBar(sliders);
})

预览

.slider {
    padding: 5px 0;
    position: relative;
    margin: 30px 10%;
    --percent: 50;
}
.slider-track {
    display: block;
    width: 100%; height: 6px;
    background-color: lightgray;
    border: 0; padding: 0;
}
.slider-track::before {
    content: '';
    display: block;
    height: 100%;
    background-color: skyblue;
    width: calc(1% * var(--percent));
}
.slider-thumb {
    position: absolute;
    width: 16px; height: 16px;
    border: 0; padding: 0;
    background: #fff;
    box-shadow: 0 0 0 1px skyblue;
    border-radius: 50%;
    left: calc(1% * var(--percent)); top: 0;
    margin: auto -8px;
}
<div class="slider">
  <button class="slider-track"></button>
  <button class="slider-thumb"></button>
</div>
;
(function() {
  const Utils = {
    isMobile: (() => {
      return /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(
        navigator.userAgent
      );
    })()
  };

  const slider = document.querySelector(".slider");
  const slider_track = slider.querySelector(".slider-track");
  const slider_thumb = slider.querySelector(".slider-thumb");
  let startMove = false;

  slider.addEventListener(Utils.isMobile ? "touchstart" : "mousedown", handleStart, false);
  document.addEventListener(Utils.isMobile ? "touchmove" : "mousemove", handleMove, false);
  document.addEventListener(Utils.isMobile ? "touchend" : "mouseup", handleEnd, false);

  function handleStart(evt) {
    if (evt.target === slider_thumb) {
      evt.stopPropagation();
      startMove = true;
    }
  }

  function handleMove(evt) {
    if (startMove) {
      render(slider, evt);
    }
  }

  function handleEnd(evt) {
    startMove = false;
  }

  slider.addEventListener(
    Utils.isMobile ? "touchend" : "click",
    function(evt) {
      // 点击轨道
      if (evt.target === slider_track) {
        render(this, evt);
      }
    },
    false
  );

  function render(el, evt) {
    const xAxis = Utils.isMobile
      ? evt.changedTouches[0].clientX
      : evt.pageX;
    const { left, width } = el.getBoundingClientRect();
    let percentage = ((xAxis - left) / width) * 100;
    percentage = Math.min(Math.max(0, percentage), 100);
    el.style.setProperty("--percent", percentage);
  }
})();

在线demo

window.onload = function () {
  // question1 start
  var sliderDiv = document.createElement("div");
  sliderDiv.className = "slider"
  sliderDiv.innerHTML = '<button class="slider-track"></button><button class="slider-thumb"></button>'
  document.body.append(sliderDiv)
  // question1 end
  
  // question2 start
  sliderDiv.style.setProperty('--percent', "50")
  // question2 end
  
  // question3 start
  var sliderTrack = document.querySelector('.slider-track');
  var moveTrack2Slide = function (event) {
    var offsetWidth = sliderTrack.offsetWidth;
    var percent = Math.min(100, Math.ceil(event.offsetX / offsetWidth * 100))
    sliderDiv.style.setProperty('--percent', percent + "")
  }
  sliderTrack.onclick = moveTrack2Slide;
  // question3 end
  
  // question4 start
  var sliderThumb = document.querySelector('.slider-thumb');
  sliderThumb.onmousedown = function (event) {
    sliderTrack.addEventListener("mousemove", moveTrack2Slide);
    sliderDiv.onmouseleave = function() {
      sliderTrack.removeEventListener("mousemove", moveTrack2Slide)
      sliderDiv.onmouseleave = null;
    }
    sliderDiv.onmouseup = function() {
      sliderTrack.removeEventListener("mousemove", moveTrack2Slide)
      sliderDiv.onmouseup = null;
    }
  }
  //question4 end
}

预览

(function () {
  
  //   1.
  var button_slider_track = document.createElement('button')
  button_slider_track.classList.add('slider-track')
  var button_slider_thumb = document.createElement('button')
  button_slider_thumb.classList.add('slider-thumb')
  var div_slider = document.createElement('div')
  div_slider.classList.add('slider')
  div_slider.appendChild(button_slider_track)
  div_slider.appendChild(button_slider_thumb)
  document.querySelector('body').appendChild(div_slider)
  // 2.
  var one_state;
  document.querySelector('#configToFifty').onclick = function () {
    one_state = !one_state
    if (one_state) {
      div_slider.style.setProperty('--percent', '50')
    } else {
      div_slider.style.setProperty('--percent', '0')
    }
  }
  // 3.
  // 注意 e.layerX 在chrome 和firefox 下的表现比较符合直觉, 在ie下要计算才可以得出正确的值
  button_slider_track.onclick = function (e) {
    var ratio = (e.offsetX)/this.clientWidth
    var result = ratio.toFixed(2)*100
    document.querySelector('.slider').style.setProperty('--percent', result)
  }
  //4.
  var button_slider_track_width = parseInt(getComputedStyle(button_slider_track).getPropertyValue('width'), 10)
  
  function mousedown(e) {
    var {left} = div_slider.getBoundingClientRect()
    document.onmousemove = function (e) {
      var button_slider_thumb_disLeft = parseInt(getComputedStyle(button_slider_thumb).getPropertyValue('left'), 10)
  
      var disRatio =  (e.pageX - left) / button_slider_track_width
      
      var disPercent = (disRatio.toFixed(2))*100
      disPercent = Math.min(disPercent, 100)
      disPercent = Math.max(disPercent, 0)
      div_slider.style.setProperty('--percent', disPercent)
    }
  }
  
  document.onmouseup = function () {
     document.onmousemove = null
  }
  button_slider_thumb.onmousedown = mousedown
  
}())

预览地址
复习一下dom元素大小知识

  • client
    image
  • getBoundingClientRect()
    image
var body = document.querySelector('body');
body.innerHTML = '<div class="slider"><button class="slider-track"></button><button class="slider-thumb"></button></div>';
var slider = document.querySelector('.slider'),
    track = document.querySelector('.slider-track'),
    thumb = document.querySelector('.slider-thumb');
slider.style = '--percent: 50';
track.addEventListener('click', function (e) {
    var percent = Math.round(e.offsetX / slider.clientWidth * 100);
    slider.style = '--percent: ' + percent;
}, false);
thumb.addEventListener('mousedown', function () {
    document.addEventListener('mousemove', move);
    document.addEventListener('mouseup', function () {
        document.removeEventListener('mousemove', move);
    });
}, false);

function move(e) {
    var left = slider.getBoundingClientRect().left;
    var percent = Math.round((e.pageX - left) / slider.clientWidth * 100);
    percent = Math.min( Math.max(0,percent),100);
    slider.style = '--percent: ' + percent;
}

https://codepen.io/mengxiaoixao/pen/OGRarL?editors=0010

/**** 第一题  ****/
let slider = document.createElement('div')
    slider.className = 'slider'
    slider.innerHTML = `<button class="slider-track"></button><button class="slider-thumb"></button>`
    document.body.appendChild(slider);
/**** 第二题  ****/
let sliderDom = document.getElementsByClassName('slider')[0];
sliderDom.style.setProperty('--percent','50');
/**** 第三题 ****/


let sliderTrack = 
document.getElementsByClassName('slider-track')[0],
sliderThumb = document.getElementsByClassName('slider-thumb')[0];
let enableChange = false;

sliderThumb.addEventListener('mousedown',changePosition,false)
document.addEventListener('mousemove',changePosition,false)
document.addEventListener('mouseup',changePosition,false)

function changePosition(evt){
  evt.stopPropagation();
  evt.type == 'mouseup' && (enableChange = false);
  evt.type == 'mousedown'&& (enableChange = true);
  if(!enableChange) return;
  let {left,width} = sliderDom.getBoundingClientRect();
  const percent = Math.min( Math.max(0,(evt.pageX-left)/width*100),100);
  sliderDom.style.setProperty('--percent',percent);
}

在线demo

.slider {
    padding: 5px 0;
    position: relative;
    margin: 30px 10%;
    --percent: 0;
}

.slider::before {
    content: attr(data-min);
}

.slider::after {
    content: attr(data-max);
}

button:focus {
    outline: none;
}

.slider-track {
    display: block;
    width: 100%;
    height: 6px;
    background-color: lightgray;
    border: 0;
    padding: 0;
}

.slider-track::before {
    content: '';
    display: block;
    height: 100%;
    background-color: skyblue;
    width: calc(1% * var(--percent));
}

.slider-thumb {
    position: absolute;
    width: 16px;
    height: 16px;
    border: 0;
    padding: 0;
    background: #fff;
    box-shadow: 0 0 0 1px skyblue;
    border-radius: 50%;
    left: calc(1% * var(--percent));
    top: 1.5em;
    margin: auto -8px;
    cursor: move;
}

.slider-thumb:hover::before,
.slider-thumb:focus::before {
    display: block;
    content: attr(data-value);
    transform: translateY(-120%);
}
class Slider {
    constructor(options = {}) {
        this.value = options.value || 0;
        this.min = options.min || 0;
        this.max = options.max || 100;
        this.percent = options.percent || 0;

        let containerId = options.containerId;
        containerId = containerId && containerId.indexOf('#') === 0 ? containerId.slice(1) : containerId;
        this.container = document.getElementById(containerId) || document.body;

        this.render();
        this.initWatchers();
        this.initOptions();
        this.initEvents();
    }
    get getValue () {
        return this.value;
    }
    render() {
        this.sliderElement = this.createSlider();
        this.trackElement = this.createTrack();
        this.thumbElement = this.createThumb();
        this.sliderElement.appendChild(this.trackElement);
        this.sliderElement.appendChild(this.thumbElement);
        this.container.appendChild(this.sliderElement);
    }
    initWatchers() {
        const self = this;
        this.watch('percent', function(percent) {
            self.value = Math.ceil((self.max - self.min) * percent / 100 + self.min);
            self.thumbElement.setAttribute('data-value', self.value);
        })
    }
    initOptions() {
        this.setSliderPercent(this.percent);
        this.sliderElement.setAttribute('data-min', this.min);
        this.sliderElement.setAttribute('data-max', this.max);
    }
    watch(prop, cb) {
        let value = this[prop];
        Object.defineProperty(this, prop, {
            get: function() {
                return value;
            },
            set: function(newValue) {
                value = newValue;
                cb(value);
            }
        })
    }
    initEvents() {
        const self = this;
        let sliderElement = this.sliderElement;
        let sliderOffsetLeft = sliderElement.offsetLeft;
        let trackElement = this.trackElement;
        let moveListener = false;
        sliderElement.addEventListener('mousedown', function(e) {
            let percent = (e.clientX - sliderElement.offsetLeft) / trackElement.clientWidth * 100;
            self.setSliderPercent(percent);

            moveListener = true;
        })

        document.addEventListener('mousemove', function(e) {
            if (moveListener) {
                let percent = (e.clientX - sliderOffsetLeft) / trackElement.clientWidth * 100;
                self.setSliderPercent(percent);
            }
        })

        document.addEventListener('mouseup', function(e) {
            moveListener = false;
        })
    }
    setSliderPercent(percent) {
        this.percent = Math.min(Math.max(percent, 0), 100);
        this.sliderElement.style.setProperty('--percent', this.percent);
    }
    createSlider() {
        let slider = document.createElement('div');
        slider.setAttribute('class', 'slider');
        return slider;
    }
    createTrack() {
        let track = document.createElement('button');
        track.setAttribute('class', 'slider-track');
        return track;
    }
    createThumb() {
        let thumb = document.createElement('button');
        thumb.setAttribute('class', 'slider-thumb');
        return thumb;
    }
}

var slider = new Slider();
var slider1 = new Slider({percent: 50});
var slider2 = new Slider({percent: 50, min: -100, max: 200});

Demo

// 第一题
const slider = document.createElement('div')
slider.setAttribute('class', 'slider')
const buttonTrack = document.createElement('button')
buttonTrack.setAttribute('class', 'slider-track')
const buttonThumb = document.createElement('button')
buttonThumb.setAttribute('class', 'slider-thumb')
slider.appendChild(buttonTrack)
slider.appendChild(buttonThumb)
document.body.appendChild(slider)
// 第二题
function setPercent(element, value) {
  if (value >= 100) {
    value = 100
  } else if (value <= 0) {
    value = 0
  }
  element.style.setProperty('--percent', value)
}

setPercent(slider, 50)
// 第三题
const buttonTrackWidth = getComputedStyle(buttonTrack).width.slice(0, -2)
buttonTrack.addEventListener('click', e => {
  setPercent(slider, Math.round((e.offsetX / buttonTrackWidth) * 100))
})
// 第四题
const sliderOffsetLeft = slider.offsetLeft
let start = false
const startFn = () => (start = true)
const endFn = () => (start = false)
const moveFn = e => {
  if (!start) return
  const clientX = e.touches ? e.touches[0].clientX : e.clientX
  setPercent(
    slider,
    Math.round(((clientX - sliderOffsetLeft) / buttonTrackWidth) * 100)
  )
}

buttonThumb.addEventListener('mousedown', startFn)
buttonThumb.addEventListener('touchstart', startFn)
window.addEventListener('mouseup', endFn)
window.addEventListener('touchend', endFn)
window.addEventListener('mousemove', moveFn)
window.addEventListener('touchmove', moveFn)

demo

    let slider = document.createElement('div');
    let sliderTrack = document.createElement('button')
    let sliderThumb = document.createElement('button')
    slider.setAttribute("class", "slider");
    sliderTrack.setAttribute("class", "slider-track")
    sliderThumb.setAttribute("class", 'slider-thumb');
    let bodyCon = document.getElementsByTagName("body")[0]
    slider.appendChild(sliderTrack);
    slider.appendChild(sliderThumb)
    bodyCon.appendChild(slider)
    slider.setAttribute("style", '--percent:50')
    sliderTrack.onclick = (e) => {
      let width = window.getComputedStyle(sliderTrack, 'width').width
      let percentWidth = e.offsetX * 100 / parseInt(width)
      slider.setAttribute("style", `--percent:${percentWidth}`)
    }
    sliderThumb.onmousedown = function (e) {
      let initX = slider.offsetLeft
      let initY = e.clientY
      document.onmousemove = function (e) {
        let width = window.getComputedStyle(sliderTrack, 'width').width
        let distanceX = e.clientX - initX
        let distanceY = e.clientY - initY
        if (distanceY > 20 || distanceY < -20) {
          document.onmousemove = null
        } else {
          let distancePercent = distanceX * 100 / parseInt(width)
          distancePercent = distanceX < 0 ? 0 : distancePercent
          distancePercent = distanceX > parseInt(width) ? 100 : distancePercent
          slider.setAttribute("style", `--percent:${distancePercent}`)
        }
      }
    }

JsBin在线Demo

(function (window) {
    var SliderBar = function SliderBar(root, percent) {
        if (!root || root.nodeType !== Node.ELEMENT_NODE) {
            throw Error('请确保构造函数的第一个参数为DOM节点')
        }
        this.root = root;
        this.percent = percent || 0;
        this.isMobile = "ontouchstart" in window;
        this.setPercentThroughMouse = this.setPercentThroughMouse.bind(this);
        this.getSlidBarPosition = this.getSlidBarPosition.bind(this);
        this.render();
        this.bindEvent();
        this.getSlidBarPosition();
        window.addEventListener('resize', this.getSlidBarPosition)
    };
    SliderProtoType = SliderBar.prototype;

    // 将滑动条添加到DOM中。
    SliderProtoType.render = function () {
        this.sliderBarDom = document.createElement('div');
        this.sliderBarDom.setAttribute('class', 'slider');
        this.sliderBarDom.setAttribute('style', '--percent:' + this.percent);
        this.sliderTrack = document.createElement('button');
        this.sliderTrack.setAttribute('class', 'slider-track');
        this.sliderThumb = document.createElement('button');
        this.sliderThumb.setAttribute('class', 'slider-thumb');
        this.sliderBarDom.appendChild(this.sliderTrack);
        this.sliderBarDom.appendChild(this.sliderThumb);
        this.root.appendChild(this.sliderBarDom);
    };

    // 将滑动条的位置进行存储
    SliderProtoType.getSlidBarPosition = function (percent) {
        this.sliderTrackBoundingClientRect = this.sliderTrack.getBoundingClientRect();
    };

    // 用于设置滑动条的进度
    SliderProtoType.setPercent = function (percent) {
        this.percent = Math.round(percent * 100) / 100;
        if (this.percent < 0) {
            this.percent = 0
        } else if (this.percent > 100) {
            this.percent = 100
        }
        this.percentChange();
        this.sliderBarDom.setAttribute('style', '--percent:' + this.percent);
    };

    // 用于处理点击和滑动时的操作
    SliderProtoType.setPercentThroughMouse = function (e) {
        var left = this.sliderTrackBoundingClientRect.left;
        var width = this.sliderTrackBoundingClientRect.width;
        console.log(e);
        var clientX = this.isMobile ? (e.touches && e.touches[0].pageX) : e.clientX
        this.setPercent((clientX - left) / width * 100)
    };

    //绑定事件
    SliderProtoType.bindEvent = function () {
        var _this = this;
        var down = this.isMobile ? "touchstart" : 'mousedown';
        var move = this.isMobile ? 'touchmove' : 'mousemove';
        var up = this.isMobile ? 'touchend' : 'mouseup';
        window.addEventListener(up, function (e) {
            window.removeEventListener(move, _this.setPercentThroughMouse)
        });

        this.sliderThumb.addEventListener(down, function (e) {
            window.addEventListener(move, _this.setPercentThroughMouse)
        });

        this.sliderTrack.addEventListener('click', _this.setPercentThroughMouse)
    };

    // 执行监听函数,当滑动条变化,会执行实例的onPercentChange方法
    SliderProtoType.percentChange = function () {
        Object.prototype.toString.call(this.onPercentChange) === "[object Function]" && this.onPercentChange(this.percent)
    };

    //将构造函数挂在到window
    window.SliderBar = SliderBar
})(window);


var firstSlidBar = new SliderBar(document.body);
var secondSlidBar = new SliderBar(document.body);

secondSlidBar.onPercentChange = function (percent) {
    document.querySelector('.percent').innerText = percent
};

document.querySelector('.set-percent-btn').addEventListener('click', function () {
    firstSlidBar.setPercent(50)
});
  1. 不要使用pageX,当有水平滚动时候会有bug!
  2. 点击track定位比较好的方法是使用offsetX,无需计算,其次clientX;
  3. 避免直接在DOM元素上使用onmouse**方法,因为很容易被重置,建议使用addEventListener。
  4. 如果你的DOM对象是直接createElement创建,无需在使用选择器进行获取,直接使用即可;
  5. 误区:过度包装(封装)。过度重用不一定是好事情,会影响代码的可读性。要权衡。大家是成熟的开发人员了,要学会适当冗余。
  6. 宽度一定要在点击或移动事件中实时获取,不然会有定位问题(如果窗口宽度发生变化)。
  7. 不要使用offsetLeft,定位是有问题的。
  8. mousedown移动不应该用在thumb上,也不要非thumb元素都定位(以后元素扩展会有bug)。
  9. 判断是否支持touchstart事件最好直接'ontouchstart' in document.body。
  10. body元素直接document.body即可!
  11. 大家都做了边界判断,很OK,但是,如果超出边界,记得设置为边界值,否则会有bug!

性感代码在线预览

class Slider{
  constructor(v=0){
    this.isDraging = false
    this.init()
    this.bindClick()
    this.bindDrag()
    this.setPercent(v)
  }

  init(){
    let slider = document.createElement('div')
    slider.className = 'slider'
    slider.innerHTML = `<button class="slider-track"></button><button class="slider-thumb"></button>`
    document.body.appendChild(slider)
    this.slider = slider
  }

  bindClick(){
    this.slider.addEventListener('click', e => {
      if (this.isDraging) return
      let percent = Math.round(e.offsetX / this.slider.offsetWidth * 100)
      this.setPercent(percent)
    }, false)
  }

  bindDrag(){
    let start = 0
    let x = 0
    let thumb = this.slider.getElementsByClassName('slider-thumb')[0]
    let max = this.slider.offsetWidth

    function down(e){
      if (e.target.className === 'slider-thumb'){
        e.preventDefault()
        this.isDraging = true
        start = e.pageX
        x = this.getPercent()
      }
    }

    function move(e){
      if (!this.isDraging) return
      e.preventDefault()
      let now = Math.round((e.pageX - start) / max * 100) + x
      if (now < 0 ) now = 0
      else if (now > 100) now = 100
      this.setPercent(now)
    }

    function up(e){
      if (!this.isDraging) return
      // 在下次事件循环解锁click事件
      setTimeout(() => {
        this.isDraging = false
      }, 0)
    }
    window.addEventListener('mousedown', down.bind(this), false)
    window.addEventListener('mousemove', move.bind(this), false)
    window.addEventListener('mouseup', up.bind(this), false)
  }

  setPercent(v=0){
    if (typeof v !== 'number' || v > 100 || v < 0) throw new TypeError('param error')
    this.slider.style.setProperty('--percent', v)
  }

  getPercent(){
    return +window.getComputedStyle(this.slider, null).getPropertyValue('--percent')
  }

}

let s = new Slider(50)

当创建多个实例时.两个实例有联动.没有隔离