【PyQt】(自制类)简易的控件画布

说一下标题的意思,就是一个可往上面放QtWidgets控件(例如QLabelQPushButton)并且画布可拖拽缩放的一个简易画布类。
强调一下的就是,这和涂鸦画布(类比于win自带的画图软件)不是同个东西。

只不过通过这个自制类我明白了一点的就是控件数量太多会造成明显卡顿(哪怕控件数量也才几百个),这让我对自己写的鸡肋玩意儿的整活程度又上升了一个档次(想想都鸡肋,写的这破玩意儿用哪才合适。



Python代码:

#XJ_Canvas.py
import numpy as np
from PyQt5.QtWidgets import QWidget
from PyQt5.QtCore import Qt,QRect
from XJ_MouseStatus import *

__all__=['XJ_Canvas']
class XJ_Canvas(QWidget):
    __objs={}#{weight:[obj,...]}
    __weights={}#{obj:weight}
    __poses={}#{obj:QRect}#不知道要咋起名,随便来算了
    __matrix=None#转换矩阵(np.array),逻辑坐标→显示坐标
    __mouseStatus=None#XJ_MouseStatus
    def __init__(self):
        super().__init__()
        self.__objs={}
        self.__weights={}
        self.__poses={}
        self.__matrix=np.array([[3,0,0],[0,3,0],[0,0,1]])
        self.__mouseStatus=XJ_MouseStatus()
    def Opt_ObjectAdd(self,obj,weight=1,pos=QRect(0,0,1,1)):#添加控件(需要附加控件的显示权重,数值越小越优先),pos为控件位置
        if(isinstance(obj.parent(),XJ_Canvas)):#obj曾出现在别的画布中
            obj.parent().Opt_ObjectRemove(obj)
        obj.setParent(self)

        objs=self.__objs
        weigs=self.__weights
        poses=self.__poses
        lst=objs.setdefault(weight,[])
        if(obj in lst):
            lst.remove(obj)
        lst.insert(0,obj)
        weigs[obj]=weight
        poses[obj]=pos

        keys=sorted(objs)
        index=keys.index(weight)
        if(index==0):#顶级
            obj.raise_()
        else:#置后
            key_front=keys[index-1]
            obj_front=objs[key_front][-1]
            obj.stackUnder(obj_front)
        obj.show()

        self.__Update(obj)
    def Opt_ObjectRemove(self,obj):#移除控件
        if(obj in self.__weights):
            self.__objs[self.__weights[obj]].pop(obj)
            self.__weights.pop(obj)
            obj.setParent(None)
            obj.hide()
    def Opt_OrderAlter(self,obj_move,obj_target,*,Above=False,Below=False):#使控件obj_move位置置于obj_target之上/下(取决于Above/Below取值),同时修改obj_move的权
        objs=self.__objs
        weigs=self.__weights
        if(Above^Below):#有一值为真
            if(obj_move in weigs and obj_target in weigs):#确保俩控件都在画布中
                obj_m=obj_move
                obj_t=obj_target
                weig_m=weigs[obj_move]
                weig_t=weigs[obj_target]
                lst=objs[weig_t]
                index=lst.index(weig_t)

                objs[weig_m].remove(obj_m)
                weigs[obj_m]=weig_t
                obj_m.stackUnder(obj_t)
                if(Above):#置上
                    obj_t.stackUnder(obj_m)
                    lst.insert(index,obj_m)
                else:#置下
                    lst.insert(index+1,obj_m)

    def Get_ObjectExist(self,obj):#判断控件是否存在
        return obj in self.__weights
    def Get_ObjectPosition(self,obj):#获取控件位置(控件不存在将返回无效QRect)
        if(obj not in self.__weights):
            return QRect()
        return self.__poses[obj]
    def Get_ObjectWeight(self,obj):#获取控件权重
        return self.__weights[obj]

    def Set_ObjectWeight(self,obj,weight):#设置控件权重(本质调用Opt_ObjectAdd)
        self.Opt_ObjectAdd(obj,weight)
        return True
    def Set_ObjectPosition(self,obj,pos):#设置控件位置(pos为QRect)
        if(obj not in self.__weights):
            return False
        if(not isinstance(pos,QRect)):#不是QRect,抛出错误(趁早修改错误调用)
            raise TypeError("非QRect对象",pos)
        self.__poses[obj]=pos
        self.__Update(obj)
        return True

    def __Update(self,*objs):#更新指定控件。如果objs为空那么将更新所有对象
        if(not objs):
            objs=self.__weights.keys()
        for obj in objs:
            pos=self.__poses[obj]
            mat=np.array([[pos.left(),pos.top(),1],[pos.right(),pos.bottom(),1]])
            mat=mat.dot(self.__matrix)/self.__matrix[2][2]
            L,T,_=mat[0]
            R,B,_=mat[1]
            obj.setGeometry(L,T,R-L,B-T)
            obj.update()

    def wheelEvent(self,event):
        pos=event.pos()
        rate=1+event.angleDelta().y()/1000
        if(self.__matrix[0][0]<0.05 and rate<1):#防止过度缩小
            return
        self.__matrix=self.__matrix.dot(np.array([[rate,0,0],[0,rate,0],[pos.x()*(1-rate),pos.y()*(1-rate),1]]))#以鼠标位置为中心进行缩放
        self.__Update()
    def mousePressEvent(self,event):
        ms=self.__mouseStatus
        ms.Opt_Update(event)#更新鼠标状态
    def mouseReleaseEvent(self,event):#对象的点击事件在鼠标抬起时触发,而不在鼠标按下时触发,这样做是为了避免和拖拽操作相冲突
        ms=self.__mouseStatus
        ms.Opt_Update(event)#更新鼠标状态
        if(not ms.Get_HasMoved()):#鼠标未发生拖拽行为
            event.ignore()#让Object对象处理点击释放操作
    def mouseMoveEvent(self,event):
        ms=self.__mouseStatus
        ms.Opt_Update(event)#更新鼠标状态
        if(ms.Get_PressButtonStatus()[0]==Qt.LeftButton):#左键拖拽
            event.ignore()
            offset=ms.Get_MoveDelta(False)
            self.__matrix[2]=self.__matrix[2]+[offset.x(),offset.y(),0]
            self.__Update()
            
if __name__=='__main__':
    import sys
	from PyQt5.QtWidgets import QApplication,QWidget,QLabel,QLineEdit,QPushButton
	from XJ_Object import *

    app = QApplication(sys.argv)
	class Test(XJ_Object,QLabel):#需要继承XJ_Object。虽然不继承也没啥,一样能往XJ_Canvas塞Qt原生控件,就是点到控件时没法拖拽画布而已
    # class Test(Object,QPushButton):
        pass

    cv= XJ_Canvas()
    cv.show()
    for x in range(30):
        for y in range(30):
            t=Test(f"{x},{y}")
            cv.Opt_ObjectAdd(t,pos=QRect(10*x,10*y,10,10))

    sys.exit(app.exec())
#XJ_MouseStatus.py
from PyQt5.QtCore import pyqtSignal
from PyQt5.QtCore import QPoint,Qt,QObject
from PyQt5.QtGui import QMouseEvent

__all__=['XJ_MouseStatus']

class XJ_MouseStatus(QObject):#mousePressEvent、mouseMoveEvent和mouseReleaseEvent特供。只处理单键(多键行为请在外部代码控制)
    longClick=pyqtSignal()#鼠标原地不动长按时触发

    __antiJitter=5#防抖,当鼠标点击位置与鼠标当前位置的曼哈顿距离不超过该值时仍将鼠标视为不动状态
    __doubleClickInterval=500#双击间隔(ms)
    __longPressInterval=500#长按间隔(ms)
    __record={
        'lastPress':None,#上一次按下时的信息
        'lastMouse':None,#上一次的鼠标信息
        'currMouse':None,#当前鼠标信息
        }
    __press=[QMouseEvent.MouseButtonRelease,QMouseEvent.MouseButtonPress,QMouseEvent.MouseButtonDblClick]#偷懒用的
    __move=False#用于判断是否长按
    __timerID=0#鼠标按下时对应的定时器
    class __Data:
        pos=None#鼠标位置
        btn=None#鼠标按键(左中右)
        pressStatus=None#鼠标当前按下状态(单双击/抬起)
        timeStamp=None#鼠标事件时间刻
        def __init__(self,event):
            self.pos=event.globalPos()
            self.btn=event.button()
            self.pressStatus=event.MouseButtonRelease
            self.timeStamp=event.timestamp()

    def __init__(self,*arg):
        super().__init__(*arg)
        record=self.__record.copy()
        fakeEvent=QMouseEvent(QMouseEvent.MouseButtonRelease,QPoint(0,0),Qt.NoButton,Qt.NoButton,Qt.NoModifier)
        data=self.__Data(fakeEvent)
        data.timeStamp-=self.__doubleClickInterval#小防,避免开局单击时触发双击行为
        record['lastMouse']=data
        record['currMouse']=data
        record['lastPress']=data
        self.__record=record
    def timerEvent(self,event):
        record=self.__record
        press=self.__press
        tId=event.timerId()
        cId=self.__timerID
        self.killTimer(event.timerId())
        if(cId==tId):#当前定时器
            if(not self.__move and record['currMouse'].pressStatus!=press[0]):#未发生移动,未抬起鼠标,触发长按信号
                self.longClick.emit()

    def Set_DoubleClickInterval(self,interval):#设置双击时间间隔(ms)
        self.__doubleClickInterval=interval
    def Set_LongPressInterval(self,interval):#设置长按时间间隔(ms)
        self.__longPressInterval=interval
    def Set_AntiJitter(self,val):#设置防抖值
        self.__antiJitter=val if val>0 else 0

    def Get_Position(self):#返回鼠标坐标。是屏幕坐标(global),需要使用QWidget.mapFromGlobal(QPoint)自行转换为控件相对坐标
        return self.__record['currMouse'].pos
    def Get_PressButtonStatus(self):#返回当前鼠标的键(左中右)以及按下状态(单击/双击/抬起)
        return self.__record['currMouse'].btn,self.__record['currMouse'].pressStatus
    def Get_MoveDelta(self,total=True,strict=True):#返回鼠标移动量(仅鼠标按下时有效),为QPoint对象
        press=self.__press
        record=self.__record
        data_curr=record['currMouse']
        if(data_curr.pressStatus!=press[0]):#说明鼠标按下
            if(not strict or self.__move):#严格模式下,仅判定发生移动时计算移动量
                p1=record['currMouse'].pos
                if(total):
                    p2=record['lastPress'].pos
                else:
                    p2=record['lastMouse'].pos
                return QPoint(p1.x()-p2.x(),p1.y()-p2.y())
        return QPoint(0,0)
    def Get_HasMoved(self):#判断是否发生移动(毕竟用Get_MoveDelta来判断移动的发生是有点麻烦,还不如多一个函数
        return self.__move

    def Opt_Update(self,event):#更新状态
        press=self.__press
        record=self.__record
        data_curr=self.__Data(event)
        if(event.type()==press[1] or event.type()==press[2]):#单/双击
            self.__move=False
            data_old=record['lastPress']
            data_curr.pressStatus=press[1]
            if(data_old.btn==data_curr.btn):#同键位按下
                if(data_curr.timeStamp-data_old.timeStamp<self.__doubleClickInterval):#在时间间隔内
                    if(data_old.pressStatus!=press[2]):#没有双击过
                        data_curr.pressStatus=press[2]#双击
            record['lastPress']=data_curr
            record['lastMouse']=data_curr
            record['currMouse']=data_curr
            self.__timerID=self.startTimer(self.__longPressInterval)
        else:#移动/抬起
            data_curr.btn=event.buttons()
            data_curr.pressStatus=record['lastMouse'].pressStatus
            if(event.type()==press[0]):#抬起
                if(data_curr.btn==Qt.NoButton):#确保无按键按下时设置为Release
                    data_curr.pressStatus=press[0]
                    data_curr.btn=event.button()
            else:#移动(QMouseEvent.MouseMove)
                if(data_curr.pressStatus!=press[0] and not self.__move):#判断有无发生拖拽
                    delta=self.Get_MoveDelta(strict=False)
                    if(abs(delta.x())+abs(delta.y())>self.__antiJitter):
                        self.__move=True
                        record['currMouse'].pos=record['lastPress'].pos
            record['lastMouse']=record['currMouse']
            record['currMouse']=data_curr

if __name__=='__main__':
    import sys
    from PyQt5.QtWidgets import QApplication,QWidget

    class Test(QWidget):
        __mouseStatus=None
        def __init__(self,*arg):
            super().__init__(*arg)
            ms=XJ_MouseStatus()
            ms.longClick.connect(lambda:print("<LongClick!>"))
            self.__mouseStatus=ms
        def __EasyPrint(self):
            press={
                QMouseEvent.MouseButtonRelease:"Release",
                QMouseEvent.MouseButtonPress:"Press",
                QMouseEvent.MouseButtonDblClick:"DblClick",}
            button={
                Qt.LeftButton:'Left',
                Qt.MidButton:'Middle',
                Qt.RightButton:'Right',}
            tPoint=lambda point:(point.x(),point.y())
            tBtn=lambda btn:[button[key] for key in button if key&btn]
            tBtnStatus=lambda status:(tBtn(status[0]),press[status[1]])

            ms=self.__mouseStatus
            pos=tPoint(self.mapFromGlobal(ms.Get_Position()))
            moveDelta=tPoint(ms.Get_MoveDelta())
            btnStatus=tBtnStatus(ms.Get_PressButtonStatus())
            print(f'pos{pos},\tdelta{moveDelta},\t{btnStatus[0]}-{btnStatus[1]}')
            if(btnStatus[1]=='Release'):
                print()
        def mousePressEvent(self,event):
            self.__mouseStatus.Opt_Update(event)
            self.__EasyPrint()
        def mouseMoveEvent(self,event):
            self.__mouseStatus.Opt_Update(event)
            self.__EasyPrint()
        def mouseReleaseEvent(self,event):
            self.__mouseStatus.Opt_Update(event)
            self.__EasyPrint()

    app = QApplication(sys.argv)

    t=Test()
    t.show()

    sys.exit(app.exec())
#XJ_Object.py
__all__=['XJ_Object']
class XJ_Object:#这个类的主要作用是鼠标事件逆传递,即先让父控件处理然后本控件才进行动作
    def mousePressEvent(self,event,defaultInvoke=True):
        canvas=self.parent()
        if(canvas):
            canvas.mousePressEvent(event)#先让上级调用
            if(not event.isAccepted() and defaultInvoke):
                super().mousePressEvent(event)
                event.accept()
    def mouseReleaseEvent(self,event,defaultInvoke=True):
        canvas=self.parent()
        if(canvas):
            canvas.mouseReleaseEvent(event)#先让上级调用
            if(not event.isAccepted() and defaultInvoke):
                super().mouseReleaseEvent(event)
                event.accept()
    def mouseMoveEvent(self,event,defaultInvoke=True):
        canvas=self.parent()
        if(canvas):
            canvas.mouseMoveEvent(event)#先让上级调用
            if(not event.isAccepted() and defaultInvoke):
                super().mouseMoveEvent(event)
                event.accept()

这里简单说明一下,上面代码有三个类:
  • XJ_Canvas:画布类,支持控件的层级放置+画布拖拽+滚轮缩放,控件需要额外继承XJ_Object以避免鼠标点中控件时无法拖拽画布的问题。
  • XJ_MouseStatus:用于支持XJ_Canvas,大幅简化鼠标点击/拖拽的逻辑代码,这个类与我之前写的博文关联:【PyQt】(自制类)处理鼠标点击逻辑
  • XJ_Object,实现鼠标点击事件的逆传递(没找到更好的实现方法就此作罢)。关于Qt控件的事件传递可以参考这篇博客:[博客园]Qt事件系统之一:Qt中的事件处理与传递



测试代码和运行结果:

#Main.py
import sys
from PyQt5.QtWidgets import QApplication,QLabel,QPushButton
from XJ_Object import *
from XJ_Canvas import *

if __name__=='__main__':
    app = QApplication(sys.argv)

    # class Test(XJ_Object,QLabel):
    class Test(XJ_Object,QPushButton):
        pass

    cv= XJ_Canvas()
    cv.show()
    for x in range(30):#塞进30*30=900个控件
        for y in range(30):
            t=Test(f"{x},{y}")
            cv.Opt_ObjectAdd(t,pos=QRect(10*x,10*y,10,10))

    sys.exit(app.exec())

运行结果
上面测试代码中往画布放置了30*30=900个控件,在画布拖拽和缩放时已经出现了极为明显的卡顿(画面响应速度超过1秒)。无助,且鸡肋。

感觉有点用 但依旧是一坨垃圾,就放到博客里头 来污染网络环境




未经本人同意不得私自转载,本文发布于CSDN:https://blog.csdn.net/weixin_44733774/article/details/134356809

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/134721.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

一句话讲明白buck和boost电源电路

大部分教程就是垃圾 虽然buck和boost结构上很像&#xff0c;但是是两个原理完全不一样的东西 BUCK&#xff08;降压&#xff09;电源 buck就是把方波&#xff0c;用LC滤波器后&#xff0c;变成正弦波 滤波&#xff1a;就是让电压缓慢增加&#xff0c;缓慢减少。&#xff08…

《红蓝攻防对抗实战》十二.内网穿透之利用ICMP协议进行隧道穿透

内网穿透之利用ICMP协议进行隧道穿透 一.前言二.前文推荐三.利用ICMP协议进行隧道穿透1.ICMPsh获取反弹shell2.PingTunnel 搭建隧道 四.本篇总结 一.前言 本文介绍了利用ICMP协议进行隧道穿透的方法。ICMP协议不需要开放端口&#xff0c;可以将TCP/UDP数据封装到ICMP的Ping数据…

Gradio App生产环境部署教程

如果机器学习模型没有投入生产供人们使用&#xff0c;就无法充分发挥其潜力。 根据我们的经验&#xff0c;将模型投入生产的最常见方法是为其创建 API。 然而&#xff0c;我们发现这个过程对于 ML 开发人员来说可能相当令人畏惧&#xff0c;特别是如果他们不熟悉 Web 开发的话。…

任正非说:到现在我们终于可以说没有失败,但我们还不能说成功。

你好&#xff01;这是华研荟【任正非说】系列的第36篇文章&#xff0c;让我们聆听任正非先生的真知灼见&#xff0c;学习华为的管理思想和管理理念。 华研荟导语&#xff1a;今天的任正非先生讲话主要节选了他在2001-2004年的几个关于IPD、ISC的论述&#xff0c;可能大家会发现…

【C++】:内存管理:C++内存分布 || C++中动态内存管理(new || delete)

&#x1f4ed;1. C/C内存分布 【说明】 &#x1f0cf;1. 栈又叫堆栈–非静态局部变量/函数参数/返回值等等&#xff0c;栈是向下增长的 &#x1f0cf;2. 内存映射段是高效的I/O映射方式&#xff0c;用于装载一个共享的动态内存库。用户可使用系统接口创建共享共享内存&#xff…

跨域:利用CORS实现跨域访问

跨域知识点&#xff1a;跨域知识点 iframe实现跨域的四种方式&#xff1a;iframe实现跨域 JSONP和WebSocket实现跨域&#xff1a;jsonp和websocket实现跨域 目录 cors介绍 简介 两种请求 简单请求 基本流程 withCredentials 属性 非简单请求 预检请求 预检请求的回应 …

利用OGG实现PostgreSQL实时同步

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

postman接口测试—Restful接口开发与测试

开发完接口&#xff0c;接下来我们需要对我们开发的接口进行测试。接口测试的方法比较多&#xff0c;使用接口工具或者Python来测试都可以&#xff0c;工具方面比如之前我们学习过的Postman或者Jmeter &#xff0c;Python脚本测试可以使用Requests unittest来测试。 测试思路…

GPT 写作与改编

GPT 写作与改编 文商科GPT 写作收益 改编技巧【改编一段话】【改编评价】【意识预设】落差&#xff0c;让顾客看到就感性和冲动害怕&#xff0c;让顾客看到就想买和拥有画面&#xff0c;切换空间&#xff0c;瞬间代入&#xff0c;勾人魂魄对比&#xff0c;设置参考物&#xff0…

RT-DETR推理详解及部署实现

目录 前言1. RT-DETR-官方2. RT-DETR-U版2.1 RT-DETR预测2.2 RT-DETR预处理2.3 RT-DETR后处理2.4 RT-DETR推理 3. RT-DETR-C3.1 ONNX导出3.2 RT-DETR预处理3.3 RT-DETR后处理3.4 RT-DETR推理 4. RT-DETR部署4.1 源码下载4.2 环境配置4.2.1 配置CMakeLists.txt4.2.2 配置Makefil…

有奖 | Python 开发者 2023 年度调查

你好&#xff0c;我是 EarlGrey&#xff0c;一名双语学习者&#xff0c;会一点编程&#xff0c;目前已翻译出版《Python 无师自通》、《Python 并行编程手册》等书籍。 点击上方蓝字关注我&#xff0c;持续接收优质好书、高效工具和赚钱机会&#xff0c;一起提升认知和思维。 1…

免费分享一套基于Springboot+Vue的在线考试系统,挺漂亮的

大家好&#xff0c;我是java1234_小锋老师&#xff0c;看到一个不错的SpringbootVue的在线考试系统&#xff0c;分享下哈。 项目视频演示 【免费】springbootvue在线考试系统 Java毕业设计_哔哩哔哩_bilibili【免费】springbootvue在线考试系统 Java毕业设计项目来自互联网&a…

notes_质谱蛋白组学数据分析基础知识

目录 1. 蛋白组学方法学1.1 液相-质谱法1) 基本原理2) bottom-up策略的基本流程 1.2 PEA/Olink 2. 质谱数据分析2.1 原始数据格式2.2 分析过程1&#xff09;鉴定2&#xff09;定量3&#xff09;预处理 2.3 下游分析 参考附录 1. 蛋白组学方法学 目前常见的蛋白组学方法学如下图…

Pinme POS无代码开发集成营销系统,实现广告推广自动化

无代码开发平台的优势 无代码开发平台如集简云是一款超级软件连接器&#xff0c;无需开发&#xff0c;无需代码知识就可以轻松打通千款软件之间的数据连接&#xff0c;构建自动化与智能化的业务流程。这种方式无需花费数周甚至数个月的时间做软件集成开发&#xff0c;最快20分…

【中国知名企业高管团队】系列65:方太FOTILE

今天华研荟为您介绍另一个行业的知名企业和高管团队信息——厨房电器这个细分领域&#xff0c;也产生了许多大的公司&#xff0c;而且这些头部公司都集中在一起&#xff0c;是当地重要的一个产业集群。 首先介绍细分领域的、号称做高端的方太厨电FOTILE。 一、关于方太集团FO…

JavaWeb Day08 Mybatis-入门

目录 ​编辑​编辑​编辑 一、快速入门程序 ①准备工作 ②引入Mybatis相关依赖&#xff0c;配置Mybatis ③编写SQL&#xff08;注解/XML&#xff09; ④单元测试 ⑤相关代码 1.pom.xml 2. application.properties 3.User.java 4. UserMapper.java 5.Test.java ⑥配置…

网络运维Day10

文章目录 SHELL基础查看有哪些解释器使用usermod修改用户解释器BASH基本特性 shell脚本的设计与运行编写问世脚本脚本格式规范执行shell脚本方法一方法二实验 变量自定义变量环境变量位置变量案例 预定义变量 变量的扩展运用多种引号的区别双引号的应用单引号的应用反撇号或$()…

【星海随笔】SDN neutron (三) Service-plugin

Neutron L3 L3的实现只负责路由的功能&#xff0c;传统路由器中的其他功能&#xff08;如Firewalls、LB、VPN&#xff09;都被独立出来实现了&#xff0c;因此ML3的实际需求比较少。 neutron-server 接到请求 –> 将请求发送到MQ –> neotron-plugins 得到请求 –> 发…

编程艺术之源:深入了解设计模式和设计原则

深入了解设计模式和设计原则 一、认识设计模式1.1、设计模式是什么&#xff1f;1.2、设计模式是怎么来的&#xff1f;1.3、设计模式解决了什么问题&#xff1f; 二、设计模式的基础2.1、面向对象思想2.2、设计原则 三、如何学习设计模式3.1、明确目的3.2、学习步骤 总结 一、认…

HTML跳转锚点

跳转锚点适用于本页面和其他页面的任意标签的跳转以及JavaScript的运行 使用方法即给标签加上独一无二的id属性&#xff0c;再使用a标签跳转 如果是其他页面的标签只需加上其他页面的路径&#xff0c;eg.href"其他页面的路径#zp1" id属性的最好不要使用数字开头 <…