甜甜的泥土

生成标签

在下面白色框框的输入框里输入文字,按enter生成标签,生成的标签可以拖动排序。

    
    (function(){
        'use strict'
        var g = function(id) {
            return typeof id === "string" ? document.getElementById(id) : id
        };
        var label_container = g('label_container');
        var label_list = g('label_list');
        var label_input = g('label_input');
        var labelArr = [];
    
        // press enter do sth
        label_input.addEventListener('keyup',function(e){
            var val = this.value;
            if (!val) return false;
            var key = e.keyCode;
            switch(key){
                case 13:
                    _pushArry(val);
                default:
                    break;
            }
        },false);
    
        // 如果数组不存在该值,则添加进去
        var _pushArry = function(val){
            if (labelArr.indexOf(val) == -1) {
                labelArr.push(val);
                _createLabel(val);
            }else{
                toast(val+'已经存在!');
            }
        };
    
        //生成标签并插入ul容器
        var _createLabel = function(val){
            var label = '
  • '+val+'-
  • '; label_list.innerHTML += label; label_input.value = ''; }; //点击删除标签 label_list.addEventListener('click',function(event){ var target = event.target; var isDEL = target.classList.contains('del'); if (!isDEL) {return false} event.stopPropagation(); var val = target.getAttribute('data-val'); var index = labelArr.indexOf(val); labelArr.splice(index,1); var li = target.parentNode; li.parentNode && li.parentNode.removeChild(li); toast("删除成功"); },false); // ******************************拖动排序********************************* // var tips = document.getElementById('tips'); var moveTarget = null; // 跟随鼠标移动的元素,鼠标抬起将销毁它 var timer = null; var t = null; var t_start; var labels =[]; // 该数组用来记录每个项目 var labelPositionArr = []; // 该数组用来记录每个项目的左上角的坐标位置 var labelAndMouseDis = []; // 该数组用来记录鼠标坐标和每个项目的两点间距离 var isSortEnd = false; // 是否判断完成想占位元素放到哪里 var indexOfInsert; // 将要插入到某元素的前面,该元素的索引 var indexOfTarget; // 被选中的事件目标元素在列表里的索引 var holderLi = null; var holderWidth; // 占位元素的宽 var holderHeight; // 占位元素的高 var targetElem; // 被选中的事件目标元素 var ignore = false; // 判断是否忽略插入位置 // 容器的基于文档位置 var containerPosition = label_container.getBoundingClientRect(); var containerPosition_x = containerPosition.left; var containerPosition_y = containerPosition.top; var t2 = null; window.onresize = function(){ clearTimeout(t2); t2 = setTimeout(function(){ containerPosition = label_container.getBoundingClientRect(); containerPosition_x = containerPosition.left; containerPosition_y = containerPosition.top; },200); } // 鼠标距离目标左上角的距离 var mouseInTarget_x; var mouseInTarget_y; // 计算数组最小值 var getMinArry = function(arry){ var min = arry[0]; var len = arry.length; for (var i = 1; i < len; i++){ if (arry[i] < min){ min = arry[i]; } } return min; }; // 移动的元素跟随鼠标变化位置 var _changeMoveTargetPostion = function(x,y){ console.log('鼠标移动中...'); tips.innerHTML += '
    鼠标移动中...'; var disX = x - mouseInTarget_x - containerPosition_x; var disY = y - mouseInTarget_y - containerPosition_y; // 限制移动范围 disX < 0 && (disX = 0); disX > (label_container.offsetWidth - moveTarget.offsetWidth) && (disY = label_container.offsetWidth - moveTarget.offsetWidth); disY < 0 && (disY = 0); disY > (label_container.offsetHeight - moveTarget.offsetHeight) && (disY = label_container.offsetHeight - moveTarget.offsetHeight); moveTarget.style.left = disX + 'px'; moveTarget.style.top = disY + 'px'; }; // 判断插入的位置 var _findInsertPositon = function(x,y){ // 删除占位元素 holderLi && holderLi.parentNode && holderLi.parentNode.removeChild(holderLi); holderLi = null; labelAndMouseDis.length = 0; // 重置了位置 for (var i = 0; i < labelPositionArr.length; i++) { var _x = Math.pow((labelPositionArr[i][0] - x),2); var _y = Math.pow((labelPositionArr[i][1] - y),2); labelAndMouseDis.push(Math.sqrt(_x + _y)); } // 取得鼠标位置和每项之间最小的距离,得到插入的索引 var minArr = getMinArry(labelAndMouseDis); indexOfInsert = labelAndMouseDis.indexOf(minArr); // 被选中的索引和需要插入的索引一样或者是后面一个元素就不执行。 // 该方案的好处就是它其实可以判断到自己和后面一个元素,只是不表现出来, // 判断到自己和后面元素的时候,看到的只是拖动效果,没有占位元素。 if(ignore && (indexOfInsert === indexOfTarget || (indexOfInsert - indexOfTarget ===1))){ return false; } // 创建并设置占位元素的宽高 holderLi = document.createElement('li'); holderLi.className = 'holderLi label_item'; holderLi.style.width = holderWidth + 'px'; holderLi.style.height = holderHeight + 'px'; label_list.insertBefore(holderLi,labels[indexOfInsert]); isSortEnd = true; // 排序完成了。 console.log('插入索引是:'+indexOfInsert+' 状态是:'+isSortEnd); tips.innerHTML += '
    插入索引是:'+indexOfInsert+' 状态是:'+isSortEnd; } // 鼠标移动的事件 var _mouseMove = function(event){ var e = event; var t_curr = +new Date(); clearTimeout(timer); !t_start && (t_start = t_curr); if(t_curr - t_start >= 200){ _changeMoveTargetPostion(e.clientX,e.clientY); t_start = t_curr; }else { timer = setTimeout(function(){ _changeMoveTargetPostion(e.clientX,e.clientY); },100); } // 移动停下再判断位置,就不会浪费性能。 clearTimeout(t); t = setTimeout(function(){ _findInsertPositon(e.clientX,e.clientY); },100); }; // 鼠标抬起的事件 var _mouseUp = function(){ clearTimeout(t); // 先清除这个定时任务,终于解决了困扰我几天的bug!!! // 当判断排序位置完成且存在移动元素且存在占位元素,就执行替换动作(插入) isSortEnd && targetElem && holderLi && label_list.replaceChild(targetElem,holderLi); // 只要鼠标抬起了,都要执行 targetElem.classList.contains('move') && targetElem.classList.remove('move'); moveTarget.parentNode && moveTarget.parentNode.removeChild(moveTarget); document.removeEventListener('mousemove',_mouseMove,false); document.removeEventListener('mouseup',_mouseUp,false); console.log('鼠标抬起,是否拖动且排序完成:'+isSortEnd); tips.innerHTML += '
    鼠标抬起,是否拖动且排序完成:'+isSortEnd; }; // 鼠标按下判断是否在目标元素激活mousedown事件 label_list.addEventListener('mousedown',function(event){ var e = event; targetElem = e.target; var isITEM = targetElem.classList.contains('label_item'); if (!isITEM) {return false;} console.log('鼠标按下了...' + e.type); tips.innerHTML = ''; tips.innerHTML += '鼠标按下了...' + e.type; isSortEnd && (isSortEnd = false); // 重置 labels = label_list.getElementsByClassName('label_item'); labels = Array.prototype.slice.call(labels); // 类数组转为数组 // 只有一个的时候不允许拖动 var length = labels.length; if (length === 1) {return false;} // 获得所有标签的左上角坐标 labelPositionArr.length = 0; for (var i = 0; i < length; i++) { var _a = labels[i].getBoundingClientRect(); labelPositionArr.push([_a.left,_a.top]); } // 获得被选中的标签的索引 indexOfTarget = labels.indexOf(targetElem); // 如果被选的元素是第一个或者第二个,或者倒数第一个或者倒数第二个, // 就采取忽略插入位置的方式,这样用户体验更接近真实 // 反之 // 把目标元素和后一个元素的位置都清除,在判断插入节点的时候,就不会判断到自己和后一个元素。 // 该方案提高准确度,但是每次拖动,一定有个占位元素。 if (indexOfTarget === 0 || indexOfTarget === 1 || indexOfTarget === length-1 || indexOfTarget === length - 2) { ignore = true; }else{ ignore = false; labelPositionArr.splice(indexOfTarget,2); labels.splice(indexOfTarget,2); } moveTarget = label_list.appendChild(targetElem.cloneNode(true)); targetElem.classList.add('move'); // 目标元素的基于文档位置 var targetPosition = targetElem.getBoundingClientRect(); var targetPosition_x = targetPosition.left; var targetPosition_y = targetPosition.top; // 取到宽高供占位元素使用 holderWidth = targetPosition.width; holderHeight = targetPosition.height; // 设置拖动元素的初始化样式 moveTarget.style.position = 'absolute'; moveTarget.style.left = targetPosition_x - containerPosition_x + 2 + 'px'; // 加2是为了向下偏移2像素 moveTarget.style.top = targetPosition_y - containerPosition_y + 2 + 'px'; // 鼠标距离目标左上角的距离 mouseInTarget_x = e.clientX - targetPosition_x; mouseInTarget_y = e.clientY - targetPosition_y; // 绑定鼠标移动和抬起的事件 document.addEventListener('mousemove',_mouseMove,false); document.addEventListener('mouseup',_mouseUp,false); },false); })();