Skip to content

Latest commit

 

History

History
426 lines (386 loc) · 11.9 KB

dom-handle.md

File metadata and controls

426 lines (386 loc) · 11.9 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));
};
禁止橡皮筋效果

util.ts

export const getClientHeight = () => {
	return (
		window.innerHeight ||
		document.documentElement.clientHeight ||
		document.body.clientHeight
	);
};

/*
 * @Function 处理touchmove, 将滚动条拖到边缘的时候,禁止橡皮筋效果
 */
class HandleTouchMove {
	startX;
	startY;

	constructor() {
		this.startX = 0;
		this.startY = 0;
		this.listenTouchstart = this.listenTouchstart.bind(this);
		this.listenTouchmove = this.listenTouchmove.bind(this);
	}

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

	listenTouchmove = (ev: any, _dom: any) => {
		const dom = document.querySelector(_dom);
		const moveEndX = ev.changedTouches[0].pageX;
		const moveEndY = ev.changedTouches[0].pageY;
		const moveX = moveEndX - this.startX;
		const moveY = moveEndY - this.startY;
		if (Math.abs(moveX) > Math.abs(moveY) && moveX > 0) {
			// 向右拖拽
			if (dom?.scrollLeft === 0) {
				ev.cancelable && ev.preventDefault();
			}
		}
		if (Math.abs(moveX) > Math.abs(moveY) && moveX < 0) {
			// 向左拖拽
			if (
				dom?.scrollLeft + dom?.offsetWidth === dom?.scrollWidth &&
				dom?.scrollLeft + dom?.clientWidth === dom?.scrollWidth
			) {
				ev.cancelable && ev.preventDefault();
			}
		}
		if (Math.abs(moveY) > Math.abs(moveX) && moveY > 0) {
			// 向下拖拽
			if (dom?.scrollTop === 0) {
				ev.cancelable && ev.preventDefault();
			}
		}
		if (Math.abs(moveY) > Math.abs(moveX) && moveY < 0) {
			// 向上拖拽
			if (
				dom?.scrollTop + dom?.offsetHeight === dom?.scrollHeight &&
				dom?.scrollTop + dom?.clientHeight === dom?.scrollHeight
			) {
				ev.cancelable && ev.preventDefault();
			}
		}
	};
}

export const handleTouchmove = new HandleTouchMove();

demo.vue

<template>
	<div class="wrap" :style="{ height: client_height }">
		<div
			class="content"
			@touchstart="handleTouchmove.listenTouchstart"
			@touchmove="
				(ev) => handleTouchmove.listenTouchmove(ev, '.content')
			">
			<div style="height: 1000px">132</div>
		</div>
	</div>
</template>

<script lang="ts" setup>
	import { getClientHeight, handleTouchmove } from "./utils";
	import { onMounted, ref } from "vue";

	const client_height: any = ref("100vh");

	onMounted(() => {
		client_height.value = getClientHeight() + "px";
	});
</script>

<style lang="scss" scoped>
	.wrap {
		width: 100%;

		.content {
			height: 100%;
			overflow-y: auto;
			overscroll-behavior: none;
		}
	}

	@supports (padding-bottom: env(safe-area-inset-bottom)) or
		(padding-bottom: constant(safe-area-inset-bottom)) {
		.content {
			padding-bottom: constant(safe-area-inset-bottom); /* 兼容 iOS < 11.2 */
			padding-bottom: env(safe-area-inset-bottom); /* 兼容 iOS >= 11.2 */
		}
	}

	@supports not (padding-bottom: env(safe-area-inset-bottom)) {
		.content {
			padding-bottom: 20px;
		}
	}

	@supports not (padding-bottom: constant(safe-area-inset-bottom)) {
		.content {
			padding-bottom: 20px;
		}
	}
</style>