Vue的基本使用

作者: 追逐_chase | 来源:发表于2018-09-19 17:30 被阅读0次
timg.jpeg

前端的主流框架 Vue.jsAngular.js,*React.js *

Vue的教程网站

安装
npm  install vue
使用
Hmtl文件

    <!--导入包-->
    <script src="./node_modules/vue/dist/vue.js"></script>


//vue 控制这个元素
<div id="app">
    {{ message }}
</div>

JS文件


    // <!--创建一个Vue的实例-->
    //在浏览器的内存中 就会多一个vue的构造函数
     var app = new Vue({
       // el 表示,当前我们new的这个Vue实例,要控制页面上的那个区域/元素
       el:"#app",
       //存放的是el要用的数据
       data:{
           message:"这是一个vue的开始"
       }
       //通过Vue提供的指令,很方便就能把数据渲染到页面上,我们不在手动操作DOM元素了,

   });

v-cloak指令 插值表达式 和v-textv-html指令
 <style>
        [v-cl0ak]{
            display: none;
        }

    </style>
 <p v-cloak>++++++{{msg}}+++++</p>


<h4 v-text="title">======</h4>

 <!--
    区别:v-text默认没有闪烁问题
    v-text会覆盖元素的原本的内容,但是 插值表达式 只会替换自己的这个占位符,不会把整个元素的内容清空


    -->

<div v-html="msg2">
        <!--{{msg2}} v-text="msg2" 转义成字符串-->

        <!--v-html="msg2" 是在div里面添加一个子标签-->
    </div>

  var vm = new Vue({
        el:"#vm",
        data:{
            msg:"v-clok的介绍",
            title:"白哦提案",
            msg2:"<h1>这是一个h1标签的嘛</h1>",
            mytitle:"自定义额按钮1",
            // show:function () {
            //     window.alert("试试");
            // }

        },
        methods:{
            //定义方法
            show:function () {

                alert("btn按钮的方发");
            }
        }
    });

v-bind 指令

// 这里v-bind指令将title属性与mytitle的值绑定在一起
<input type="button" value="按钮" v-bind:title = "mytitle">

// v-bind可以简写为 :
  <input type="button" value="按钮" :title = "mytitle">
<!--在绑定的时候,拼接绑定内容::title="btnTitle + ', 这是追加的内容'" -->

v-on事件绑定
 <!--Vue中 提供了v-on来绑定事件  缩写是@-->
input type="button" value="按钮" :title = "mytitle"  @click="show">
案例
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <style>

        #app{
            padding-top: 10px;
            margin-left: 10px;
        }
        .box{
            padding-top: 20px;
        }

        /*[v-cloak]{*/
            /*display: none;*/

        /*}*/
    </style>

    <script src="node_modules/vue/dist/vue.js"></script>
</head>
<body>




<div id="app">

    <input type="button" value="浪起来" @click="show">

    <input type="button" value="低调" @click="stop">

    <div class="box" v-cloak>
        {{msg}}
    </div>
</div>



</body>
</html>

<!-创建实例-->
<script>
    var app = new Vue({
        el:"#app",
        data:{
            msg:"开始,浪七类!!!!",
            timer:null
        },
        methods:{
            show:function () {
                var that = this;
                // 在Vue实例中 如果想要获取data的数据或者methods里面的方法,必须通过this进行方法,this是Vue的实例对象
                //   注意this指向问题

                //判断是否存在
                if (this.timer != null) return;
                //定时器
                this.timer = setInterval(function () {
                    var startStr = that.msg.substring(0,1);
                    var endStr = that.msg.substring(1);
                    that.msg  = endStr + startStr;
                },500)


            },
            stop:function () {
                //清空定时器
                clearInterval(this.timer);
                //置位 null
                this.timer = null;
            }
        }
    })


</script>
事件修饰符
  • .stop阻止事件冒泡 防止div事件传递
  • .prevent 阻止默认行为
  • .capture添加事件监听器时使用事件捕获模式
  • self 当前元素自身时触发处理函数
  • .once点击事件将只会触发一次
<div id="app">
<div class="inner" @click.self="divHander">

    <!--.stop阻止事件冒泡 防止div事件传递-->
    <!--<input type="button" value="点击" @click.stop="btnHandle">-->
     <!--.prevent 阻止默认行为-->
    <!--<a href="http://www.baidu.com" @click.prevent = "linkClick"> 点击去百度问一下</a>-->

    <!--.capture 添加事件监听器时使用事件捕获模式 -->

    <!--<div @click.capture = "captureClick">-->
        <!--<input type="button" value="点击" @click.stop="btnHandle">-->
    <!--</div>-->

    <!--self 当前元素自身时触发处理函数 -->
    <!--<div @click.self="selfClick">-->
        <!--<input type="button" value="点击" @click="btnHandle">-->
    <!--</div>-->

    <!--.once 点击事件将只会触发一次 -->
    <a href="http://www.baidu.com" @click.once.prevent = "linkClick"> 点击去百度问一下</a>

</div>

</div>


    new Vue({
        el:"#app",
        data:{

        },
        methods:{
            divHander:function () {

                console.log("这是DIV");

            },
            btnHandle:function () {

                console.log("这是按钮Btn");

            },
            linkClick:function () {
                console.log("加载")
            },
            captureClick:function () {
                console.log("铺货事件");
            },
            selfClick:function () {
                console.log("自己自身时触发 方法");
            }

        }
    })

键盘 按键修饰符

  • .enter
  • .tab
  • .delete
  • .esc
  • .space
  • .up
  • .down
  • .left
  • .right
  • ``
  • ``
v-model数据的双向绑定
<div id="app">

    <h4>
        {{msg}}
    </h4>
<!-- v-bind只能绑定属性,获取值,不能随之修改-->
    <!--<input type="text" :value = "msg" style="width:300px">-->

    <!-- v-model可以实现 数据的双向绑定 随时更改 简写: -->
    <input type="text" v-model= "msg" style="width:300px">

 new Vue({
        el:"#app",
        data:{
            msg:"这是一个开始v-model双向数据绑定"
        },
        methods:{

        }
    })
Class和Style样式
  • class数组
  <style>
        .red{
            color: red;
        }
        .thin{
            font-weight: 200;
        }
        .italic{
            font-style: italic;
        }

        .active{
            /*字体间距  中文使用*/
            letter-spacing: 0.5em;
        }
    </style>
  <!--第一种使用方式 -->
<!--数组里面的 要有单引号-->
    <h1 :class="['red','thin','active']">这是一个H1得到,来阿里阿莱阿里啊!!!</h1>

    <!---->
<!--数组中使用 三元表达式-->

    <h1 :class="['red','thin',isflag?'active':'']">这是一个H1得到,来阿里阿莱阿里啊!!!</h1>


    <!-- 对象代替三元表达式-->
    <h1 :class="['red',{'active':isflag}]">这是一个H1得到,来阿里阿莱阿里啊!!!</h1>

    <!--直接使用对象-->
    <h1 :class="{active:isflag}">这是一个H1得到,来阿里阿莱阿里啊!!!</h1>
   new Vue({
        el:"#box",
       data:{
            isflag:true
       }


    })
  • style
<div id="app">

    <!--对象就是无序键值对的集合-->
    <!--如果属性中有符号 要加单引号-->
    <!--<h1 :style="objct1">这是一个h1标签</h1>-->
    <h1 :style=[objct1,obect2]>这是一个h1标签</h1>
    

</div>
 new Vue({
        el:"#app",
        data:{
            objct1:{color:'red','font-weight':800},
            obect2:{'font-size': '80px', 'font-style': 'italic'}

        },
        methods:{}
    })

v-for循环指令

  • 遍历数组
// 标准是 item in items 其中items是数据数组  item是数组元素
 <div id="app">
        <ul>
            <li v-for="(item,index) in items">
                {{item.msg}} 索引是:{{index}}
            </li>
        </ul>

    </div>

    new Vue({
        el:"#app",
        data:{
            items:[
                {msg:"这是西部建设11"},
                {msg:"这是西部建设22"},
                {msg:"这是西部建设33"},
                {msg:"这是西部建设44"},
                {msg:"这是西部建设55"},
                {msg:"这是西部建设66"}

            ],
           
        }

    })
  • 遍历对象

    <ul id="v-for-object" class="demo">  
      //只遍历 Value 
        <li v-for="value in object">
            {{ value }}
        </li>
    </ul>

//  key-value
 <ul id="v-for-object" class="demo">
//注意语法
        <li v-for="(value,key) in object">
           {{key}}:{{ value }}
        </li>
    </ul>

//对应结果
firstName:John
lastName:Doe
age:30

//index-key -value 有参数 索引

 <ul id="v-for-object" class="demo">
        <li v-for="(value,key,index) in object">
           {{key}}:{{ value }}
        </li>
    </ul>

//对应的结果

0 ----- firstName:John
1 ----- lastName:Doe
2 ----- age:30


new Vue({
        el:"#v-for-object",
        data:{

            object: {
                firstName: 'John',
                lastName: 'Doe',
                age: 30
            }
        }

    })

过滤器

  • 一般在2中情况下使用 插件表达式{{}}v-bind表达式
  • 格式:<过滤的格式 {{ name | 过滤器的名称}},过滤器中的function第一个参数已经被规定死了,永远是过滤器 管道符|前面传过来的数据
Vue.filter("过滤器名称",function(data){
})
<div id="app">
        <p>{{msg | msgForamt("疯狂是不是")}}</p>

    </div>



JS文件
 Vue.filter('msgForamt',function (msg,arg) {

        console.log(msg);
        //replace 方法,第一个参数,除了可写一个字符串外,还可以定义一个正则
        // msg.replace('一个','邪恶')
        // msg.replace(/一个/g,'邪恶')

        return msg + ",哈哈哈哈" + arg;

    })

    new Vue({
        el:"#app",
        data:{
            msg:"曾经,我也有梦想,也想成为一个大作家,一个人旅行,坐在一个安静的地方,慢慢的品茶"
        },
        methods:{

        }

    })



  • 定义私有过滤器

html文件
<div id="app2">
  <p>{{dt | strForamt}}</p>
</div>





JS文件
    //定义一个私有的过滤器
new Vue({

    el:"#app2",
    data:{
        dt:"这是一个私有过滤器的开始"
    },
    methods:{

    },
    //私有过滤器
    filters:{
        strForamt:function (dt) {
            console.log(dt + "哈哈哈");

            return dt + ",添加一个和喜爱和精细化静安寺"
        }
    }

});
自定义指令
<label>
   搜索关键字:
   <input type="text" class="form-control" v-model="keywords" v-focus >
 </label>


    //定义全局指令 让文本获取焦点
    //参数一:指令的 名称,在定义的时候,指令前面,不需要加 v- 前缀,但是在调用的时候,必须在指令前面前 加上 v-前缀
    //参数二:对象,这个对象 身上有一些指令 相关的处理函数,这些函数可以在特定的阶段,执行相应的操作

    Vue.directive("focus",{
        //每当指令绑定到元素上的时候,会立即执行这个bind函数,只执行一次
        bind:function (el) {
            // 在元素 刚绑定指令的时候,还没有 插入到DOM中去,这个时候,调用focus方法没有作用
            //因为,一个元素,只有插入到DOM中才可以获取到焦点

            //这个一般使用在样式上

        },

        //元素插入到DOM中的时候,会执行 inserted函数
        inserted: function (el) {
            // 聚焦元素
            el.focus()
        },

        //当VNode更新时候,会执行updated,可能会出发多次
        update:function (el) {

        }


        //注意:在每一个函数中,第一个参数,永远是el,表示被绑定了指令的那个元素,这个el参数,是一个原生的JS对象,可以用来直接操作DOM
// 第二个参数:binding 是一个对象 包含了一下几个属性
// name: 定义指令的名称,不包括 v-前缀
// value:指定的绑定值
// expression:指令绑定的表达式


    });

vue实例的生命周期

  • 什么是生命周期:从Vue实例创建、运行、到销毁期间,总是伴随着各种各样的事件,这些事件,统称为生命周期!
  • 生命周期钩子:就是生命周期事件的别名而已;
  • 生命周期钩子 = 生命周期函数 = 生命周期事件
  • 主要的生命周期函数分类:
  • 创建期间的生命周期函数:
    • beforeCreate:实例刚在内存中被创建出来,此时,还没有初始化好 data 和 methods 属性
    • created:实例已经在内存中创建OK,此时 data 和 methods 已经创建OK,此时还没有开始 编译模板
    • beforeMount:此时已经完成了模板的编译,但是还没有挂载到页面中
    • mounted:此时,已经将编译好的模板,挂载到了页面指定的容器中显示
  • 运行期间的生命周期函数:
    • beforeUpdate:状态更新之前执行此函数, 此时 data 中的状态值是最新的,但是界面上显示的 数据还是旧的,因为此时还没有开始重新渲染DOM节点
    • updated:实例更新完毕之后调用此函数,此时 data 中的状态值 和 界面上显示的数据,都已经完成了更新,界面已经被重新渲染好了!
  • 销毁期间的生命周期函数:
    • beforeDestroy:实例销毁之前调用。在这一步,实例仍然完全可用。
    • destroyed:Vue 实例销毁后调用。调用后,Vue 实例指示的所有东西都会解绑定,所有的事件监听器会被移除,所有的子实例也会被销毁。
<script>
    new Vue({
        el:"#app",
        data:{
            msg:"111"
        },
        methods:{},
        beforeCreate:function () {
           console.log("这是第一个周期函数,表示实例创建完全被创建出来之前,会执行");
           console.log(this.msg);
           //  beforeCreate生命周期函数执行的时候,data和methods中的数据 还没有初始化
        },
        created:function () {
            console.log("实例创建加载到内存中");
            console.log(this.msg);
            //  created 生命周期函数执行的时候,data和methods已经初始化完成
            // 如果要调用methods中的方法或者操作data中的数据,最早,只能在这个方法中操作
        },
        beforeMount:function () {

            console.log("beforeMount 模板已经渲染 页面还没有刷新" );
            //开始渲染模板,把Vue代码中的那些指令进行执行,最终在内存中生成一个编译好的最终模板字符串,然后,把这个模板字符串,
            // 渲染为内存中的DOM,此时,只是在内存中渲染好模板,并没有把模板挂在页面中


            //在这个函数执行的时候,页面中的元素,还没有被真正的替换过来,只是之前写的一些模板字符串

        },

        mounted:function () {

            //mounted是 实例创建期间的最后一个生命周期函数,当执行完mounted就表示,实例已经被完全创建好了,此时 如果没有其他操作
            //的话这个实例,就一直存在内存中

            //如果要操作DOM 节点,最早要在这个函数中进行

            //执行到这个函数时,标明组件脱离了创建阶段,进入到了运行阶段
        },

        //运行期间的周期函数
        beforeUpdate:function () {

            //页面中显示的数据还是旧的。此时data数据是最新的,页面还没有被新的数据渲染

        },
        updated:function () {

            //页面和data数据 已经保持同步了

        },

        //销毁期间的周期函数
        beforeDestroy:function () {

        },
        destroyed:function () {

        }


    })

lifecycle.png

vue-resource 实现 get, post, jsonp请求

除了 vue-resource 之外,还可以使用 axios 的第三方包实现实现数据的请求

  1. 之前的学习中,如何发起数据请求?
  2. 常见的数据请求类型? get post jsonp
  3. 测试的URL请求资源地址:
  1. JSONP的实现原理
  • 由于浏览器的安全性限制,不允许AJAX访问 协议不同、域名不同、端口号不同的 数据接口,浏览器认为这种访问不安全;
  • 可以通过动态创建script标签的形式,把script标签的src属性,指向数据接口的地址,因为script标签不存在跨域限制,这种数据获取方式,称作JSONP(注意:根据JSONP的实现原理,知晓,JSONP只支持Get请求);
  • 具体实现过程:
    • 先在客户端定义一个回调方法,预定义对数据的操作;
    • 再把这个回调方法的名称,通过URL传参的形式,提交到服务器的数据接口;
    • 服务器数据接口组织好要发送给客户端的数据,再拿着客户端传递过来的回调方法名称,拼接出一个调用这个方法的字符串,发送给客户端去解析执行;
    • 客户端拿到服务器返回的字符串之后,当作Script脚本去解析执行,这样就能够拿到JSONP的数据了;
  • 带大家通过 Node.js ,来手动实现一个JSONP的请求例子;
   const http = require('http');
   // 导入解析 URL 地址的核心模块
   const urlModule = require('url');

   const server = http.createServer();
   // 监听 服务器的 request 请求事件,处理每个请求
   server.on('request', (req, res) => {
     const url = req.url;

     // 解析客户端请求的URL地址
     var info = urlModule.parse(url, true);

     // 如果请求的 URL 地址是 /getjsonp ,则表示要获取JSONP类型的数据
     if (info.pathname === '/getjsonp') {
       // 获取客户端指定的回调函数的名称
       var cbName = info.query.callback;
       // 手动拼接要返回给客户端的数据对象
       var data = {
         name: 'zs',
         age: 22,
         gender: '男',
         hobby: ['吃饭', '睡觉', '运动']
       }
       // 拼接出一个方法的调用,在调用这个方法的时候,把要发送给客户端的数据,序列化为字符串,作为参数传递给这个调用的方法:
       var result = `${cbName}(${JSON.stringify(data)})`;
       // 将拼接好的方法的调用,返回给客户端去解析执行
       res.end(result);
     } else {
       res.end('404');
     }
   });

   server.listen(3000, () => {
     console.log('server running at http://127.0.0.1:3000');
   });
  1. vue-resource 的配置步骤:
  • 直接在页面中,通过script标签,引入 vue-resource 的脚本文件;
  • 注意:引用的先后顺序是:先引用 Vue 的脚本文件,再引用 vue-resource 的脚本文件;
  1. 发送get请求:
getInfo() { // get 方式获取数据
  this.$http.get('http://127.0.0.1:8899/api/getlunbo').then(res => {
    console.log(res.body);
  })
}
  1. 发送post请求:
postInfo() {
  var url = 'http://127.0.0.1:8899/api/post';
  // post 方法接收三个参数:
  // 参数1: 要请求的URL地址
  // 参数2: 要发送的数据对象
  // 参数3: 指定post提交的编码类型为 application/x-www-form-urlencoded
  this.$http.post(url, { name: 'zs' }, { emulateJSON: true }).then(res => {
    console.log(res.body);
  });
}
  1. 发送JSONP请求获取数据:
jsonpInfo() { // JSONP形式从服务器获取数据
  var url = 'http://127.0.0.1:8899/api/jsonp';
  this.$http.jsonp(url).then(res => {
    console.log(res.body);
  });
}

配置本地数据库和数据接口API

  1. 先解压安装 PHPStudy;
  2. 解压安装 Navicat 这个数据库可视化工具,并激活;
  3. 打开 Navicat 工具,新建空白数据库,名为 dtcmsdb4;
  4. 双击新建的数据库,连接上这个空白数据库,在新建的数据库上右键 -> 运行SQL文件,选择并执行 dtcmsdb4.sql 这个数据库脚本文件;如果执行不报错,则数据库导入完成;
  5. 进入文件夹 vuecms3_nodejsapi 内部,执行 npm i 安装所有的依赖项;
  6. 先确保本机安装了 nodemon, 没有安装,则运行 npm i nodemon -g 进行全局安装,安装完毕后,进入到 vuecms3_nodejsapi目录 -> src目录 -> 双击运行 start.bat
  7. 如果API启动失败,请检查 PHPStudy 是否正常开启,同时,检查 app.js 中第 14行 中数据库连接配置字符串是否正确;PHPStudy 中默认的 用户名是root,默认的密码也是root

品牌管理改造

展示品牌列表

添加品牌数据

删除品牌数据

Vue中的动画

为什么要有动画:动画能够提高用户的体验,帮助用户更好的理解页面中的功能;

使用过渡类名

  1. HTML结构:
<div id="app">
    <input type="button" value="动起来" @click="myAnimate">
    <!-- 使用 transition 将需要过渡的元素包裹起来 -->
    <transition name="fade">
      <div v-show="isshow">动画哦</div>
    </transition>
  </div>
  1. VM 实例:
// 创建 Vue 实例,得到 ViewModel
var vm = new Vue({
  el: '#app',
  data: {
    isshow: false
  },
  methods: {
    myAnimate() {
      this.isshow = !this.isshow;
    }
  }
});
  1. 定义两组类样式:
/* 定义进入和离开时候的过渡状态 */
    .fade-enter-active,
    .fade-leave-active {
      transition: all 0.2s ease;
      position: absolute;
    }

    /* 定义进入过渡的开始状态 和 离开过渡的结束状态 */
    .fade-enter,
    .fade-leave-to {
      opacity: 0;
      transform: translateX(100px);
    }

使用第三方 CSS 动画库

  1. 导入动画类库:
<link rel="stylesheet" type="text/css" href="./lib/animate.css">
  1. 定义 transition 及属性:
<transition
    enter-active-class="fadeInRight"
    leave-active-class="fadeOutRight"
    :duration="{ enter: 500, leave: 800 }">
    <div class="animated" v-show="isshow">动画哦</div>
</transition>

使用动画钩子函数

  1. 定义 transition 组件以及三个钩子函数:
<div id="app">
    <input type="button" value="切换动画" @click="isshow = !isshow">
    <transition
    @before-enter="beforeEnter"
    @enter="enter"
    @after-enter="afterEnter">
      <div v-if="isshow" class="show">OK</div>
    </transition>
  </div>
  1. 定义三个 methods 钩子方法:
methods: {
        beforeEnter(el) { // 动画进入之前的回调
          el.style.transform = 'translateX(500px)';
        },
        enter(el, done) { // 动画进入完成时候的回调
          el.offsetWidth;
          el.style.transform = 'translateX(0px)';
          done();
        },
        afterEnter(el) { // 动画进入完成之后的回调
          this.isshow = !this.isshow;
        }
      }
  1. 定义动画过渡时长和样式:
.show{
      transition: all 0.4s ease;
    }

v-for 的列表过渡

  1. 定义过渡样式:
<style>
    .list-enter,
    .list-leave-to {
      opacity: 0;
      transform: translateY(10px);
    }

    .list-enter-active,
    .list-leave-active {
      transition: all 0.3s ease;
    }
</style>
  1. 定义DOM结构,其中,需要使用 transition-group 组件把v-for循环的列表包裹起来:
  <div id="app">
    <input type="text" v-model="txt" @keyup.enter="add">

    <transition-group tag="ul" name="list">
      <li v-for="(item, i) in list" :key="i">{{item}}</li>
    </transition-group>
  </div>
  1. 定义 VM中的结构:
    // 创建 Vue 实例,得到 ViewModel
    var vm = new Vue({
      el: '#app',
      data: {
        txt: '',
        list: [1, 2, 3, 4]
      },
      methods: {
        add() {
          this.list.push(this.txt);
          this.txt = '';
        }
      }
    });

列表的排序过渡

<transition-group> 组件还有一个特殊之处。不仅可以进入和离开动画,还可以改变定位。要使用这个新功能只需了解新增的 v-move 特性,它会在元素的改变定位的过程中应用

  • v-movev-leave-active 结合使用,能够让列表的过渡更加平缓柔和:
.v-move{
  transition: all 0.8s ease;
}
.v-leave-active{
  position: absolute;
}

相关文章

  1. vue.js 1.x 文档
  2. vue.js 2.x 文档
  3. String.prototype.padStart(maxLength, fillString)
  4. js 里面的键盘事件对应的键码
  5. pagekit/vue-resource
  6. navicat如何导入sql文件和导出sql文件
  7. 贝塞尔在线生成器

相关文章

  • vue Vuex axios 相关资料

    vue中文文档 使用Vuex详解 vue-router 基本使用 vue全局使用axios的方法 vue 父子组件...

  • 非单文件组件

    1.基本使用 基本使用 ...

  • Vue基本指令

    Vue的基本指令功能和使用方式汇总 Vue的基本结构 通过元素创建Vue对象 el:Vue对象指定的元素范围 da...

  • Vue基本使用

    数据传递 数据的单向传递把数据交给vue实例对象,实例对象将数据交给界面 vue中数据双向绑定可以用 v-mode...

  • Vue 基本使用

    传统开发模式对比 原生JSJQuery Vue.js之 HelloWorld 基本步骤 Vue 的基本使用步骤:1...

  • Vue 基本使用

    特点 1.基于MVVM模式(Model-View-ViewModel),中间通过viewmodel层实现了双向绑定...

  • VUE基本使用

    vue简介 vue.js的数据驱动视图是基于MVVM模型实现的 model 代表数据 view 代表视图模板 vi...

  • Vue的基本使用

    前端的主流框架 Vue.js,Angular.js,*React.js * Vue的教程网站 安装 使用 v-cl...

网友评论

    本文标题:Vue的基本使用

    本文链接:https://www.haomeiwen.com/subject/qzfynftx.html