前端日常问题记录

273 阅读11分钟

可拖动元素图替换默认的预览图

元素设置dragable 时元素就可以使用鼠标拖动,此时拖动出去的预览图和原图一模一样,要想改变预览图为其它图片需要用到dataTransfer.setDragImage(img, xOffset,yOffset)

// setDragImage如果直接使用MDN推荐的image对象话会存在拖动时图片还未加载完成失效问题,所以采用提前创建element的方式
const img = document.createElement('img');
  // @ts-ignore
img.style = "position: absolute; top: -9999px; max-width: 100px; max-height: 100px;"
img.src = 'https://d322uc7y3fcjjx.cloudfront.net/test/easy-email/product.png'
document.body.append(img)
  const onDragStart = useCallback(
    (ev: React.DragEvent) => {
      ev.dataTransfer.setDragImage(img, 50, 50);
    },
  );

参考链接www.51cto.com/article/707…

实现鼠标点击左右拖动div块

通过创建ref指向div,然后分别监听div的鼠标事件onMouseDown onMouseMove onMouseUp

// 鼠标监听实现鼠标左右滑动 
const ScrollContainer = useRef(null)
let downScrollLeft = 0 
let downClientX = 0 
let isMoving = false 
// 监听鼠标按下 
const handleMouseDown = ( event: React.MouseEvent<HTMLDivElement, MouseEvent>, ) => { // 鼠标按钮样式变小手 ScrollContainer.current.style.cursor = 'pointer' 
isMoving = true 
if (ScrollContainer.current) { 
    downClientX = event.clientX downScrollLeft =              ScrollContainer.current.scrollLeft 
} }

const scrollCallback = throttle((clientX: number) => { 
if (isMoving && ScrollContainer.current) { ScrollContainer.current.scrollLeft = downScrollLeft + (downClientX - clientX) } }, 16)

// 监听鼠标移动
const handleMouseMove = ( event: React.MouseEvent<HTMLDivElement, MouseEvent>, ) => scrollCallback(event.clientX)

// 监听鼠标拉起 
const handleMouseUp = () => { 
isMoving = false
ScrollContainer.current.style.cursor = 'default' 
}


<div  ref={ScrollContainer} onMouseDown={handleMouseDown} onMouseMove={handleMouseMove} onMouseUp={handleMouseUp}>

JS实现鼠标拖动驱动滚动条滚动

第一种方案:
// TODO 监听鼠标事件驱动滚动条
  // @y 拖动
  const ele = getShadowRoot()?.getElementById('shadow-container')

  useEffect(() => {
    // 记录鼠标位置
    let pos = { top: 0, y: 0 };

    const mouseDownHandler = function (e) {
      pos = {
        // 当前滚动位置
        top: ele?.scrollTop,
        // 当前鼠标位置
        y: e.clientY,
      };

      document.addEventListener('drag', mouseMoveHandler);
      document.addEventListener('dragend', mouseUpHandler);
    };
    document.addEventListener('dragstart', mouseDownHandler);
    // @y preClientY用来保存上一位置的e.clientY的值,用来判断当前鼠标在窗口顶部还是底部
    // 窗口顶部和底部当前e.clientY都为0,只是上一次的值顶部的为0 底部的为较大值
    let preClientY;
    const mouseMoveHandler = function (e) {
      // 鼠标移动距离
      const dy = e.clientY - pos.y;
      // 滚动到指定位置
      if (ele) {
        ele.scrollTop = pos.top + dy ;
        // 纵向可移动的最大距离
        let limitY = ele.scrollHeight - ele.offsetHeight ;

        if (ele.scrollTop >= limitY) {
          // 当滑块移动到底端时
          ele.scrollTop = limitY;
        } else if (ele.scrollTop <= 0) {
          if (dy < 0) {
            if (preClientY < 500) {
              // 说明此时鼠标位置在顶部
              ele.scrollTop = 0;
            } else {
              // 说明此时鼠标位置在底部
              ele.scrollTop = limitY
            }
          }
        } else {
          ele.scrollTop = pos.top + dy;
        }
        // preClientY记录上一次鼠标的位置
        preClientY = e.clientY;
      }
    };

    const mouseUpHandler = function () {
      document.removeEventListener('drag', mouseMoveHandler);
      document.removeEventListener('dragend', mouseUpHandler)
    };

    return () => {
      document.removeEventListener('dragstart', mouseDownHandler);
    }
  }, [])
第二种方案:

通过检测鼠标位置,当鼠标位置在浏览器上边或者下边的时候 让scrollTop的值一直加或者减,实现浏览器滚动条缓缓上滑或者下滑

useEffect(() => {
    const ele = ref.current;
    if (!ele) return;

    ele.addEventListener('dragend', onDragEnd);
    return () => {
      ele.removeEventListener('dragend', onDragEnd);
    };
  }, [onDragEnd]);
  // TODO 监听鼠标事件驱动滚动条
  // @y 拖动
  const ele = getShadowRoot()?.getElementById('shadow-container')

  const scrollUp = () => {
    if (timer.current) {
      clearInterval(timer.current)
    }
    console.log('xxsxs');
    if (ele) {
      if (ele?.scrollTop !== 0) {
        // ele.scrollTop = ele?.scrollTop - 20;
        timer.current = setInterval(() => {
          console.log('定时')
          ele.scrollTop = ele.scrollTop - 20;
        }, 20);
      }
      console.log(ele.scrollTop, 'ele.scrollTop')
    }
  }
  const scrollDown = (isBottom: any) => {
    if (ele) {
      ele.scrollTop = ele?.scrollTop + 20;
      if (isBottom) {
        if (ele.scrollTop < ele.scrollHeight) {
          if (timer.current) {
            clearInterval(timer.current)
          }
          timer.current = setInterval(() => {
            console.log('定时')
            ele.scrollTop = ele.scrollTop + 20;
          }, 20);
        }
      }
    }
  }

  useEffect(() => {
    // 记录鼠标位置
    let pos = { top: 0, y: 0 };

    const mouseDownHandler = function (e: { clientY: any; }) {
      if (ele) {
        pos = {
          // 当前滚动位置
          top: ele.scrollTop,
          // 当前鼠标位置
          y: e.clientY,
        };
      }

      document.addEventListener('drag', mouseMoveHandler);
      document.addEventListener('dragend', mouseUpHandler);
    };
    document.addEventListener('dragstart', mouseDownHandler);

    const mouseMoveHandler = function (e: { clientY: number; }) {
      // 鼠标移动距离
      // const dy = e.clientY - pos.y;
      // 滚动到指定位置
      if (ele) {
        // ele.scrollTop = pos.top + dy ;
        // 纵向可移动的最大距离
        // let limitY = ele.scrollHeight - ele.offsetHeight;
        //e.clientY 120px 触发缓缓向上滚动

        if (e.clientY < 200 && e.clientY > 0) {
          scrollUp()
        }

        if (e.clientY > window.innerHeight - 10) {
          scrollDown(false)
        }

        if (e.clientY === 0 && ele.scrollTop !== 0) {
          console.log('到底')
          scrollDown(true)
        }
      }
    };

    const mouseUpHandler = function () {
      document.removeEventListener('drag', mouseMoveHandler);
      document.removeEventListener('dragend', mouseUpHandler);
      if (timer.current) {
        clearInterval(timer.current)
      }
    };

    return () => {
      document.removeEventListener('dragstart', mouseDownHandler);
    }
  }, [])

设置a标签的第一个子元素img的opacity属性设置不上问题(css问题)

需要上下设置两次,因为如果只是上面设置一次的话 可能就被下面的那次:hover给影响了,所以需要再设置一次

   .${seletedClassName} > table > tbody:hover .img1 {
      opacity: 0.5;
    }

    .${seletedClassName} > table > tbody:hover > tr > td > a .upload-icon {
      display: inline !important;
    }
    // opacity属性不能设置到父级否则会影响子元素的opacity
    // opacity 属性的hover状态设置要放在上一个hover下面否则不生效
    .${seletedClassName} > table > tbody:hover .img1 {
      opacity: 0.5;
    }

css实现hover到图片上去出现编辑小图标且图片出现蒙层

image.png

image.png

const seletedClassName: any = el.attributes['product-img-css-class']
    return css + `
    .${seletedClassName} > table {
      background-color: #0D0C16;

    }
    .${seletedClassName} > table > tbody {
      position: relative;
    }
    .${seletedClassName} > table > tbody > tr > td > a > img{
      cursor: pointer;
    }
    .${seletedClassName} > table > tbody:hover .img1 {
      opacity: 0.5;
    }

    .${seletedClassName} > table > tbody:hover > tr > td > a .upload-icon {
    // hover上去让小图标出现
      display: inline !important;
    }
    // opacity属性不能设置到父级否则会影响子元素的opacity
    // opacity 属性的hover状态设置要放在上一个hover下面否则不生效
    .${seletedClassName} > table > tbody:hover .img1 {
      opacity: 0.5;
    }
    // .${seletedClassName} > table > tbody:hover::before {
    //   content: '11';
    //   background: url('https://d322uc7y3fcjjx.cloudfront.net/test/easy-email/grab-block.png');
    //   position: absolute;
    //   width: 28px;
    //   height: 28px;
    //   top: 50%;
    //   left: 50%;
    //   transform: translate(-50%, -50%) !important;
    //   cursor: pointer;
    // }
    .upload-icon {
      width: 28px;
      height: 28px;
      position: absolute;
      top: 50%;
      left: 50%;
      transform: translate(-50%, -50%) !important;
      cursor: pointer;
      display: none;
    }

实现颜色选择器和吸色器

1.利用react-color这个库来实现颜色选择器, 但是这些颜色选择器都不带吸色器,所以吸色器需要额外的实现

颜色选择器如下 image.png 吸色器如下

image.png

方案1:

const nativePick = async (e) => {
    const val =  e ? e.target.value : null
     if (val) {
       console.log('获得颜色: ' + val)
     } else {
       const eyeDropper = new window.EyeDropper() // 初始化一个EyeDropper对象
       try {
         const result = await eyeDropper.open() // 开始拾取颜色
         console.log('获得颜色: ' + result.sRGBHex)
         setColor(result.sRGBHex);
       } catch (e) {
       }
     }
   }
因为EyeDropper这个方法只有少数浏览器支持
{/* 兼容性判断 只有支持EyeDropper属性的浏览器才显示取色器 */}
            {'EyeDropper' in window && <div style={{ flex: 1, display: 'flex', alignItems: 'center', marginTop: '16px' }}>
              <i className='iconfont icon-xiguangongju' onClick={nativePick} style={{marginRight: '10px'}}></i>
              <div
                style={{
                  display: 'inline-block',
                  height: 32,
                  width: 32,
                  boxSizing: 'border-box',
                  padding: 4,
                  border: '1px solid var(--color-neutral-3, rgb(229, 230, 235))',
                  borderRadius:  4,
                  fontSize: 0,
                  borderRight: 'none',
                  position: 'relative',
                  cursor: 'pointer',
                  marginRight: '6px'
                }}
              >
                <span
                  style={{
                    position: 'relative',
                    display: 'block',
                    border:
                      '1px solid var(--color-neutral-3, rgb(229, 230, 235))',
                    borderRadius: 2,
                    width: '100%',
                    height: '100%',
                    textAlign: 'center',
                    backgroundColor: color ? color : props.value,
                  }}
                />
              </div>
              <Input
                value={color ? color : props.value}
                style={{ outline: 'none', flex: 1, width: '155px' }}
                onChange={onInputChange}
              />
            </div>}

参考文章:juejin.cn/post/714017…

正则匹配

正则语法大全

问题描述: 给定一个html字符串,需要匹配里面的所有a标签并进行替换

htmlString.replace(/(<[^>]+>)[\s]*?(<a\b[^>]+\bhref="([^"]*)"[^>]*>([\s\S]*?)<\/a>)/g, (match, p1, p2, p3) => {
// match为当前整个正则每一次匹配到的内容
// p1 p2 p3 分别表示正则里面用小括号括起来的部分从左到右
// return 的东西是每一次正则匹配的内容的替换
return p1-p2

}

这段正则 :

  1. (<[^>]+>) 表示匹配所有html的起实标签,<>里的内容表示除了>的所有字符,因为起始标签里唯独不能出现的就是>出现它就表示这个标签的闭合了
  2. [\s]*? 表示0个或者无数个空白符,包括换行
  3. ()表示一个子表达式,可以再后续拿到的值中用索引获取这个子值,比如replace的第二三四参数就是子表达式的值
  4. []表示一个中括号表达式,类似于()也是表示里面的正则是一个小整体配合后续的+一起使用,表示这个子正则出现多次,主要是为了复用这个正则子表达式
  5. [^"] ^在方括号中使用表示除了xxx,否则表示以xx开头,* 表示前面的表达式出现0或无数次
  6. \b描述单词的前或后边界
  7. [\s\S]*? [\s\S]表示匹配所有,\s 是匹配所有空白符,包括换行,\S 非空白符,不包括换行, ?表示非贪婪
  8. 所以这个正则就是匹配整个html字符串所有的标签,以及a标签前面的父级标签比如
    xxx /(<[^>]+>)[\s]*?(<a\b[^>]+\bhref="([^"]*)"[^>]*>([\s\S]*?)<\/a>)/g

总结:通过htmlString.replace(//, (match, p1,p2) => {}),通过匹配到的a标签,再用自定义的标签替换达到目的

Js如何渲染一个html字符串到页面上

通过 window.open().document.write(htmlString) 可以在新的窗口中打开并渲染这个html字符串

给定一个html字符串如何给指定的标签上设置hover状态的自定义提示框,如图:

image.png

解决方法: 通过给指定的标签添加id属性,然后匹配到html字符串的样式标签,在style中选中id选择器,通过设置伪元素::before和:hover

<p id="tip" data-tip="链接: http://xxxxx 点击人数: 2
#tip:hover::before {
  white-space: pre; // 让content中的内容可以换行
  color: #21202A;
  font-size: 14px;
  text-align: left;
  padding: 24px;
  z-index: 10000;
  content: attr(data-tip); //显示在提示框中的内容,attr()表示调用id为tip的标签上的data-tip属性的值
  position: absolute;
  border-radius: 4px;
  background-color: #FFFFFF;
  box-shadow: 2px 2px 8px 0px rgba(13, 12, 22, 0.16), 2px 2px 16px 0px rgba(13, 12, 22, 0.08);
  width: 353px;
  top:28px; 
  left:0px;

标签style样式的添加可以使用replace的正则匹配到标签,然后添加样式

const finalHtml = htmlString.replace(/(<style type="text\/css">)([\s\S]*?)``(<\/style>)/, (match, p1, p2, p3) => {
  return `${p1}${p2} #tip:hover::before {white-space: pre;color: #21202A;font-size: 14px;text-align: left;padding: 24px;z-index: 10000;content: attr(data-tip);position: absolute;border-radius: 4px;background-color: #FFFFFF;box-shadow: 2px 2px 8px 0px rgba(13, 12, 22, 0.16), 2px 2px 16px 0px rgba(13, 12, 22, 0.08);width: 353px;top:28px; left:0px}${p3}`
})

cssmodule的使用

cssmodule用法教程--阮一峰

1.首先在react中是默认开启了cssModule的,只要样式文件以xxx.module.sass/less/css来命名即可

2.CSS Modules 允许使用:global(.className)的语法,声明一个全局规则。凡是这样声明的class,都不会被编译成哈希字符串,反之默认定义的类名都会被webpack编译为哈希字符串(样式表中的和引用在组件上的同时编译为同一个哈希串)

3.所以更改三方组件库的样式需要使用:global(.className),因为我们定义在less中的类名比如.arco-btn 其实会被webpack编译为哈希 最后并不是三方组件库的那个类名,所以需要声明:global(.className) 表示不对这个类名进行哈希,保留原始类名,这样才能起到样式覆盖作用

4.:global(.className) 会影响全局所有使用该类的地方,所以为了将样式修改限制到本组件,一般都会把:global(.className)限制在组件自定义类名范围下,然后把这个类名添加到三方组件的上层标签中作为类名

.message-box {
  width: 400px;
                                                              
  :global(.arco-tabs-header-nav) {
    padding: 8px 16px;
    border-bottom: 1px solid var(--color-border-2);
  }

import styles from './style/index.module.less';

<div className={styles['message-box']}>
  <Spin loading={loading} style={{ display: 'block' }}>

window.location.href 跳转问题

window.location.href 可以用来跳转链接地址,分为两种情况

  1. 不加域名,默认的域名为当前页面的域名

  2. 加了域名,就跳到指定域名

后端返回完整html页面的htmlstring 并打开新窗口渲染页面

1.window.open().document.write(htmlString) 会直接在新窗口打开并渲染 缺点:新页面点击刷新按钮(浏览器)页面就没内容了,且chrome的插件截图那些都失效 2. 通过react方式,定义一个新组件然后通过dangerouslySetInnerHTML属性渲染htmlstring,这种方式浏览器地址栏的地址是该组件的地址,且刷新页面内容不会失效

CSS部分

css 想让p标签(块级标签)的宽度随着里面的内容自适应

问题场景: 主要解决有些情况的块级盒子的宽度需要随着里面的内容自适应,所以不能定宽,但是不设置宽度会默认继承占满父盒子的宽度

1.设置display: inline-block,然后内容左右居中可以设置padding,但是注意从display:block变为inline-block 可能会影响布局因为原先是占据一整行的,修改以后不占了

2.设置float 让盒子浮动起来,首先浮动以后就不影响其它元素的布局,而且因为浮动了所以块级宽度也不继承父盒子了,所以宽度是随着内容自适应的

3.设置绝对定位也可以让宽度随着内容自适应

width:auto和width:100%的区别

1.width:100%的作用是占满它的参考元素的宽度。 (一般情况下参考元素 == 父级元素,这里写成参考元素而不是父级元素,在下面我会再细说)

2.width:auto也是以“占满参考元素宽度”为目标。但不同的地方在于,它能根据margin和padding的值动态地调整width的值。 当参考元素的宽度一定时,子元素的margin或者padding多一点,那么子元素的width就会少一点。

【注意】:width:100%不会将自身的margin和padding包含计算在参考元素的width内 所以有时候width: 100%以后 子元素会溢出父元素,这个时候应该设置width: auto,

浮动/定位对width:auto和width:100%的影响

  • 默认情况下:以它的父级元素宽度为参考基准

  • 子元素相对定位,仍然以它的父级元素宽度为参考基准

  • 子元素绝对定位,则分两种情况讨论 <1>默认情况下以body元素宽度为参考基准(所以如果上层没有相对定位的父级元素的话他的宽度会占满整个屏幕),若存在被定位的上层元素,则以距离最近的相对定位的元素宽度为基准(所以可以设置父级元素为相对定位解决)

  • 元素浮动对width:100%无影响,浮动/定位对width:auto的影响, 子元素绝对定位,固定定位,浮动,width:auto等同于width:0 所以,当我们在绝对定位,固定定位,要记得给元素设宽度浮动的时候可以不设置,默认为内容的宽度

浮动/定位对其他元素布局的影响

其中float,position:absolute/fixed能够脱离文档流 ,而position:relative不能够脱离文档流

在这里,我们把脱离文档流的那一部分元素归为“浮动流”,而把没有脱离文档流的那一部分元素归为“标准流”,那么:

1.浮动流顺序排列,这个顺序是和HTML中元素的顺序一致的,HTMl中先浮动的元素排在前面,这个“前面”指的是靠近屏幕边缘的一端,“后面指的是远离屏幕的一端”

以上面的例子为基础

.div1,.div2,.div3,.div4{
        float: left;
}

 

.div1,.div2,.div3,.div4{
       float: right;
}

 

2.浮动流的起始位置由最先设置浮动的元素未浮动时的位置决定

 

我们再回到上述的例子,div2,div3,div4向左浮动的时候

 

浮动流的起始位置被最先设置浮动的元素原本的位置决定了。其他元素的只能跟在“领头浮动元素”的后面 指的是第一个元素即使浮动起来了他的位置还是原来的位置(见上图div2浮动起来依然在div1后面还在原本位置,后面浮动元素跟在第一个浮动元素后面)

3.浮动流本身并不会影响标准流元素的定位,但是却影响着标准流文本的定位 也就是div2 浮动了 下面的div3元素盒子会占据div2的位置,但是div3盒子里面的问本“div3” 还是会显示在文本”div2“旁边,也就是还占据着标准流文本的位置

参考链接:浮动和定位对元素的宽度-外边距或其他元素的影响