Skip to content

Latest commit

 

History

History
executable file
·
487 lines (444 loc) · 14.1 KB

File metadata and controls

executable file
·
487 lines (444 loc) · 14.1 KB
监听浏览器标签页的显示与隐藏
/**
 * 监听浏览器标签页的显示与隐藏
 */
class ListenerPageVisibility {
    constructor () {
        // 设置隐藏属性和改变可见属性的事件的名称
        this.hidden = ''
        this.visibilityChange = ''
        if (typeof document.hidden !== "undefined") { // Opera 12.10 and Firefox 18 and later support
            this.hidden = "hidden"
            this.visibilityChange = "visibilitychange"
        } else if (typeof document.msHidden !== "undefined") {
            this.hidden = "msHidden"
            this.visibilityChange = "msvisibilitychange"
        } else if (typeof document.webkitHidden !== "undefined") {
            this.hidden = "webkitHidden"
            this.visibilityChange = "webkitvisibilitychange"
        }

        this.handleChange = (callBackHidden, callBackVisibility) => {
            if (document[this.hidden]) {
                // 页面是隐藏状态
                callBackHidden && callBackHidden()
            } else {
                // 页面是展示状态
                callBackVisibility && callBackVisibility()
            }
        }

    }

    /**
     * 全局访问点
     * @param callBackHidden 页面隐藏执行的回调方法
     * @param callBackVisibility 页面显示执行的回调方法
     */
    linsternVisibility (callBackHidden, callBackVisibility) {
        // 如果浏览器不支持addEventListener 或 Page Visibility API 给出警告
        if (typeof document.addEventListener === "undefined" || typeof document[this.hidden] === "undefined") {
            console.log("This demo requires a browser, such as Google Chrome or Firefox, that supports the Page Visibility API.")
        } else {
            // 处理页面可见属性的改变
            document.addEventListener(this.visibilityChange, () => {
                this.handleChange(callBackHidden, callBackVisibility)
            }, false)
        }
    }
}

// 调用实例
let navChange = new ListenerPageVisibility()
navChange.linsternVisibility(
    // 页面是隐藏状态执行方法
    () => {
        // TODO 浏览器标签页处于隐藏状态时,执行的方法
    },
    // 页面是可见状态执行方法
    () => {
        // TODO 浏览器标签页处于显示状态时,执行的方法
    }
)
监听dom变化
/**
 * 监听dom变化
 * @param callback
 */
function monitorDomChange (callback) {
    var mutationObserver = new MutationObserver(function (mutations) {
        mutations.forEach(function (mutation) {
            callback(mutation)
        })
    })
    // 开始侦听页面的根 HTML 元素中的更改。
    mutationObserver.observe(document.documentElement, {
        attributes: true,
        characterData: true,
        childList: true,
        subtree: true,
        attributeOldValue: true,
        characterDataOldValue: true
    })
}
滚动到底部
// 滚动到低部
export function scrollToBottom (dom) {
    const dom_box = document.querySelector(dom)
    if (!dom_box) return
    function scroll () {
        // 已经滚动的高度
        const currentScroll = dom_box.scrollTop
        // 容器高度
        const clientHeight = dom_box.offsetHeight
        // 内容高度
        const scrollHeight = dom_box.scrollHeight
        if (scrollHeight - 10 > currentScroll + clientHeight) {
            window.requestAnimationFrame(scroll)
            dom_box.scrollTo(0, currentScroll + (scrollHeight - currentScroll - clientHeight) / 2)
        }
    }

    scroll()
}
页面title滚动
 setInterval(function () {
    // 取 title 中最后一个字符
    const first_char = document.title[0]
    // 取除了第一个剩下的
    const last_string = document.title.substr(1)
    // 给title重新赋值
    document.title = last_string + first_char
 }, 10)
阻止滚动穿透
	const maskPageStartY = ref(0);
 	// 阻止滚动穿透
	const preventThrough = (dom) => {
		const _dom: any = document.querySelector(dom);
		_dom?.addEventListener(
			"touchstart",
			(e: any) => {
				maskPageStartY.value = e?.changedTouches?.[0]?.pageY;
			},
			false
		);

		_dom?.addEventListener(
			"touchmove",
			(e: any) => {
				e?.stopPropagation();
				const moveY = e?.changedTouches?.[0]?.pageY - maskPageStartY.value;
				// 禁止向上滚动溢出
				if (e.cancelable && moveY > 0 && (_dom?.scrollTop || 0) <= 0) {
					e.preventDefault();
				}

				// 禁止向下滚动溢出
				if (e.cancelable && moveY < 0 && _dom?.scrollTop + _dom?.clientHeight >= _dom?.scrollHeight) {
					e.preventDefault();
				}
			},
			false
		);
	};
水印
<style>
#watermark_box {
  width: 100vw;
  height: 100vh;
  position: absolute;
  z-index: -1;
  overflow: hidden;
}
</style>
<div id="watermark_box"></div>
class Watermark {
  constructor() {
    this.clientW = document.documentElement.clientWidth || document.body.clientWidth || window.innerWidth;
    this.clientH = document.documentElement.clientHeight || document.body.clientHeight || window.innerHeight;
    this.mark = null
    this.init = this.init.bind(this)
  }

  addWaterMark(params) {
    const _mark = document.querySelector('#skillnull_watermark_container')
    if (_mark) {
      _mark.parentElement.removeChild(_mark)
    }
    // 水印容器
    this.mark = document.createElement('canvas')
    this.mark.id = 'skillnull_watermark_container'
    this.mark.height = params.height || this.clientH
    this.mark.width = params.width || this.clientW
    // 水印内容
    const mark_text = this.mark.getContext('2d')
    mark_text.font = params.mark_text_font || '14px serif'
    mark_text.fillStyle = params.mark_text_font_color || '#434a56ab'
    mark_text.textBaseline = params.mark_text_baseline || 'hanging'
    const _gap = params.gaps || [100, 100]
    const _density = params.density || [150, 150]
    for (let i = 0; i < _density[0]; i++) {
      for (let j = 0; j < _density[1]; j++) {
        mark_text.save()
        mark_text.translate(i * _gap[0], j * _gap[1])
        mark_text.rotate((params.mark_text_rotate || 15) * Math.PI / 180)
        mark_text.fillText(params.mark_text || '水印', i * _gap[0], j * _gap[1])
        mark_text.restore()
      }
    }
    // 要添加水印元素
    const mark_target = document.querySelector(params.target)
    mark_target.appendChild(this.mark)
  }

  observer(params) {
    let observer = new MutationObserver((mutations) => {
      mutations.forEach((item) => {
        if (item.removedNodes.length > 0 && item.removedNodes[0].id === "skillnull_watermark_container") {
          this.addWaterMark(params)
        }
        if (item.type === 'attributes' && item.target.id === "skillnull_watermark_container") {
          const target = document.querySelector('#skillnull_watermark_container')
          target.style.display = 'block'
          target.style.opacity = 1
        }
        if (item.type === 'attributes' && item.target.id === params.target.replace('#', '').replace('.', '')) {
          const target = document.querySelector(params.target)
          target.style.display = 'block'
          target.style.opacity = 1
        }
      })
    })
    observer.observe(document.body, {
      childList: true,
      attributes: true,
      subtree: true,
      attributeOldValue: true,
      characterData: true,
      characterDataOldValue: true
    })
  }

  init(params) {
    this.addWaterMark(params)
    this.observer(params)
  }
}

new Watermark().init({
  target: '#watermark_box', // 必须,被添加水印元素
  height: '', // 水印容器高,默认页面高
  weight: '', // 水印容器宽,默认页面宽
  mark_text: 'SKILLNULL', // 水印内容,默认 '水印'
  mark_text_font: '300 12px Arial', // 水印字体样式,默认 '14px serif'
  mark_text_font_color: "#434a56ab", // 水印字体颜色,默认 '#434a56ab'
  mark_text_baseline: '', // 文字基线位置,默认 'hanging'
  mark_text_rotate: 20, // 文字旋转角度,默认 15
  gaps: [100, 100], // [x间隔, y间隔], 默认 [100, 100]
  density: [150, 150] // [x数量, y数量],默认 [150, 150]
})
点击元素外部
/*
 * 点击除排除元素列表和目标元素外的元素,执行回调
 * @els: 排除元素列表,点击不执行回调
 * @target: 目标元素,点击不执行回调
 *
 * 例:
 *	clickOutside(".pop-click", "#pop_content", ()=>{ // do somthing });
 */
export const clickOutside = (els: any = "", target: any = "", calback: any = null) => {
	const handler = (e: Event) => {
		const target_dom: any = document.querySelector(target);
		const els_dom: any = document.querySelectorAll(els);
		let flag = false;
		Array.from(els_dom)?.map((item: any) => {
			if (item?.contains(e.target)) {
				flag = true;
			}
		});
		if (!flag && !target_dom?.contains(e.target)) {
			calback();
		}
	};

	document.addEventListener("click", (e) => handler(e));
	document.addEventListener("touchmove", (e) => handler(e));
};
禁止橡皮筋效果
export type GET_MOVE_DIRECTION_INFO = {
  moveX: number;
  moveY: number;
};

export type GET_MOVE_DIRECTION = {
  dom?: string | Element;
  notEdge?: boolean | null;
  edgeStop?: boolean | null;
  /** 为 true 时 callback 第二个参数返回额外信息 */
  otherInfo?: boolean | null;
  callback: (direction: string, info?: GET_MOVE_DIRECTION_INFO) => void;
};

/**
 * @Function 获取指定 dom 的 touchmove 方向
 * @props {
 *   dom: 指定 dom(选择器或元素)
 *   notEdge: 不达到边缘就触发回调,默认 false
 *   edgeStop: 到达边缘时是否禁止橡皮筋效果,默认 false
 *   otherInfo: 是否返回额外信息,默认 false
 *   callback: 回调函数,返回方向;otherInfo 为 true 时额外返回 { moveX, moveY }
 * }
 */
export class GetMoveDirection {
  startX: number;
  startY: number;
  dom: Element | Document | null;
  props: GET_MOVE_DIRECTION;
  private listenerOptions: AddEventListenerOptions;

  constructor(props: GET_MOVE_DIRECTION) {
    this.startX = 0;
    this.startY = 0;
    if (!props?.dom) {
      this.dom = document;
    } else if (typeof props.dom === "string") {
      this.dom = document?.querySelector(props.dom);
    } else {
      this.dom = props.dom;
    }
    this.removeAllListent = this.removeAllListent.bind(this);
    this.listenTouchstart = this.listenTouchstart.bind(this);
    this.listenTouchmove = this.listenTouchmove.bind(this);
    this.props = props;
    // notEdge:捕获阶段,避免子元素 stopPropagation 拦掉 callback
    // edgeStop:passive:false,才能在边缘 preventDefault 禁用橡皮筋效果
    this.listenerOptions = {
      capture: !!props?.notEdge,
      passive: !props?.edgeStop,
    };
    this.dom?.addEventListener(
      "touchstart",
      this.listenTouchstart,
      this.listenerOptions
    );
    this.dom?.addEventListener(
      "touchmove",
      this.listenTouchmove,
      this.listenerOptions
    );
  }

  private getScrollDom(): HTMLElement | null {
    if (!this.dom) return null;
    return this.dom instanceof Document
      ? document.documentElement
      : (this.dom as HTMLElement);
  }

  private isAtLeftEdge(dom: HTMLElement) {
    return dom.scrollLeft === 0;
  }

  private isAtRightEdge(dom: HTMLElement) {
    return (
      Math.ceil(dom.scrollLeft + dom.offsetWidth) >= dom.scrollWidth &&
      Math.ceil(dom.scrollLeft + dom.clientWidth) >= dom.scrollWidth
    );
  }

  private isAtTopEdge(dom: HTMLElement) {
    return dom.scrollTop === 0;
  }

  private isAtBottomEdge(dom: HTMLElement) {
    return (
      Math.ceil(dom.scrollTop + dom.offsetHeight) >= dom.scrollHeight &&
      Math.ceil(dom.scrollTop + dom.clientHeight) >= dom.scrollHeight
    );
  }

  private applyEdgeStop(ev: TouchEvent, atEdge: boolean, axis: "x" | "y") {
    if (!this.props?.edgeStop || !atEdge) return;
    ev.cancelable && ev.preventDefault();
    if (axis === "y") {
      ev.stopPropagation();
    }
  }

  private emitDirection(direction: string, info: GET_MOVE_DIRECTION_INFO) {
    if (this.props?.otherInfo) {
      this.props.callback(direction, info);
    } else {
      this.props.callback(direction);
    }
  }

  listenTouchstart(ev: any) {
    this.startX = ev?.changedTouches?.[0]?.pageX;
    this.startY = ev?.changedTouches?.[0]?.pageY;
  }

  listenTouchmove(ev: any) {
    const moveEndX = ev?.changedTouches?.[0]?.pageX;
    const moveEndY = ev?.changedTouches?.[0]?.pageY;
    const moveX = moveEndX - this.startX;
    const moveY = moveEndY - this.startY;
    const _dom = this.getScrollDom();
    if (!_dom) return;

    const absX = Math.abs(moveX);
    const absY = Math.abs(moveY);
    const info: GET_MOVE_DIRECTION_INFO = { moveX, moveY };

    // 向右拖拽
    if (absX > absY && moveX > 0) {
      const atEdge = this.isAtLeftEdge(_dom);
      if (this.props?.notEdge || atEdge) {
        this.emitDirection("right", info);
      }
      this.applyEdgeStop(ev, atEdge, "x");
    }

    // 向左拖拽
    if (absX > absY && moveX < 0) {
      const atEdge = this.isAtRightEdge(_dom);
      if (this.props?.notEdge || atEdge) {
        this.emitDirection("left", info);
      }
      this.applyEdgeStop(ev, atEdge, "x");
    }

    // 向下拖拽
    if (absY > absX && moveY > 0) {
      const atEdge = this.isAtTopEdge(_dom);
      if (this.props?.notEdge || atEdge) {
        this.emitDirection("down", info);
      }
      this.applyEdgeStop(ev, atEdge, "y");
    }

    // 向上拖拽
    if (absY > absX && moveY < 0) {
      const atEdge = this.isAtBottomEdge(_dom);
      if (this.props?.notEdge || atEdge) {
        this.emitDirection("up", info);
      }
      this.applyEdgeStop(ev, atEdge, "y");
    }
  }

  removeAllListent() {
    this.dom?.removeEventListener(
      "touchstart",
      this.listenTouchstart,
      this.listenerOptions
    );
    this.dom?.removeEventListener(
      "touchmove",
      this.listenTouchmove,
      this.listenerOptions
    );
  }
}

// 示例
new GetMoveDirection({
  dom: scrollElement,
  notEdge: true,
  otherInfo: true,
  callback: (direction, info) => {
    if (direction !== "down" && direction !== "up") return;
    actionTo(EBridgeActionType.emitMoveInfoToNative, {
      scrollUp: direction === "down" ? 2 : 1,
      offsetY: info?.moveY ?? 0,
    });
  },
});