Precise Drag and Drop within a contenteditable

后端 未结 3 399
礼貌的吻别
礼貌的吻别 2020-12-22 23:42

The Setup

So, I have a contenteditable div -- I\'m making a WYSIWYG editor: bold, italics, formatting, whatever, and most lately: inserting fancy images (in a fanc

3条回答
  •  無奈伤痛
    2020-12-23 00:29

    Since I wanted to see this in a native JS solution I worked a bit to remove all jQuery dependencies. Hopefully it can help someone.

    First the markup

        
    WAITING FOR STUFF
    Block 1 Second Blk

    Then some helpers

        function addClass( elem, className ){
            var classNames = elem.className.split( " " )
            if( classNames.indexOf( className ) === -1 ){
                classNames.push( className )
            }
            elem.className = classNames.join( " " )
        }
        function selectElem( selector ){
            return document.querySelector( selector )
        }
        function selectAllElems( selector ){
            return document.querySelectorAll( selector )
        }
        function removeElem( elem ){
             return elem ? elem.parentNode.removeChild( elem ) : false
        }
    

    Then the actual methods

        function nativeBindDraggable( elems = false ){
            elems = elems || selectAllElems( '.native_drag' );
            if( !elems ){
                // No element exists, abort
                return false;
            }else if( elems.outerHTML ){
                // if only a single element, put in array
                elems = [ elems ];
            }
            // else it is html-collection already (as good as array)
    
            for( let i = 0 ; i < elems.length ; i++ ){
                // For every elem in list, attach or re-attach event handling
                elems[i].dataset.transferreference = `transit_${ new Date().getTime() }`;
                elems[i].ondragstart = function(e){
                    if (!e.target.id){
                        e.target.id = (new Date()).getTime();
                    }
    
                    window.inTransferMarkup = e.target.outerHTML;
                    window.transferreference = elems[i].dataset.transferreference;
                    addClass( e.target, 'dragged');
                };
            };
        }
    
        function nativeBindWriteRegion( elems = false ){
            elems = elems || selectAllElems( '.native_receiver' );
            if( !elems ){
                // No element exists, abort
                return false;
            }else if( elems.outerHTML ){
                // if only a single element, put in array
                elems = [ elems ];
            }
            // else it is html-collection
    
            for( let i = 0 ; i < elems.length ; i++ ){
                elems[i].ondragover = function(e){
                    e.preventDefault();
                    return false;
                };
                elems[i].ondrop = function(e){
                    receiveBlock(e);
                };
            }
        }
    
        function receiveBlock(e){
            e.preventDefault();
            let content = window.inTransferMarkup;
    
            window.inTransferMarkup = "";
    
            let range = null;
            if (document.caretRangeFromPoint) { // Chrome
                range = document.caretRangeFromPoint(e.clientX, e.clientY);
            }else if (e.rangeParent) { // Firefox
                range = document.createRange();
                range.setStart(e.rangeParent, e.rangeOffset);
            }
            let sel = window.getSelection();
            sel.removeAllRanges(); 
            sel.addRange( range );
            e.target.focus();
    
            document.execCommand('insertHTML',false, content);
            sel.removeAllRanges();
    
            // reset draggable on all blocks, esp the recently created
            nativeBindDraggable(
              document.querySelector(
                `[data-transferreference='${window.transferreference}']`
              )
            );
            removeElem( selectElem( '.dragged' ) );
            return false;
        }
    

    And lastly instantiate

    nativeBindDraggable();
    nativeBindWriteRegion();
    

    Below is the functioning snippet

    function addClass( elem, className ){
                var classNames = elem.className.split( " " )
                if( classNames.indexOf( className ) === -1 ){
                    classNames.push( className )
                }
                elem.className = classNames.join( " " )
            }
            function selectElem( selector ){
                return document.querySelector( selector )
            }
            function selectAllElems( selector ){
                return document.querySelectorAll( selector )
            }
            function removeElem( elem ){
                 return elem ? elem.parentNode.removeChild( elem ) : false
            }
            
          
        	function nativeBindDraggable( elems = false ){
        		elems = elems || selectAllElems( '.native_drag' );
        		if( !elems ){
        			// No element exists, abort
        			return false;
        		}else if( elems.outerHTML ){
        			// if only a single element, put in array
        			elems = [ elems ];
        		}
        		// else it is html-collection already (as good as array)
                
        		for( let i = 0 ; i < elems.length ; i++ ){
        			// For every elem in list, attach or re-attach event handling
        			elems[i].dataset.transferreference = `transit_${ new Date().getTime() }`;
        			elems[i].ondragstart = function(e){
        				if (!e.target.id){
        					e.target.id = (new Date()).getTime();
        				}
    
        				window.inTransferMarkup = e.target.outerHTML;
        				window.transferreference = elems[i].dataset.transferreference;
        				addClass( e.target, 'dragged');
        			};
        		};
        	}
            
        	function nativeBindWriteRegion( elems = false ){
        		elems = elems || selectAllElems( '.native_receiver' );
        		if( !elems ){
        			// No element exists, abort
        			return false;
        		}else if( elems.outerHTML ){
        			// if only a single element, put in array
        			elems = [ elems ];
        		}
        		// else it is html-collection
        		
        		for( let i = 0 ; i < elems.length ; i++ ){
        			elems[i].ondragover = function(e){
        				e.preventDefault();
        				return false;
        			};
        			elems[i].ondrop = function(e){
        				receiveBlock(e);
        			};
        		}
        	}
            
            function receiveBlock(e){
        		e.preventDefault();
        		let content = window.inTransferMarkup;
        		
        		window.inTransferMarkup = "";
        		
        		let range = null;
        		if (document.caretRangeFromPoint) { // Chrome
        			range = document.caretRangeFromPoint(e.clientX, e.clientY);
        		}else if (e.rangeParent) { // Firefox
        			range = document.createRange();
        			range.setStart(e.rangeParent, e.rangeOffset);
        		}
        		let sel = window.getSelection();
        		sel.removeAllRanges(); 
        		sel.addRange( range );
        		e.target.focus();
        		
        		document.execCommand('insertHTML',false, content);
        		sel.removeAllRanges();
        		
                // reset draggable on all blocks, esp the recently created
        		nativeBindDraggable(
                  document.querySelector(
                    `[data-transferreference='${window.transferreference}']`
                  )
                );
        		removeElem( selectElem( '.dragged' ) );
        		return false;
        	}
    
    
        nativeBindDraggable();
        nativeBindWriteRegion();
            
    WAITING FOR STUFF
    Block 1 Second Blk

提交回复
热议问题