当前位置: 首页 > news >正文

HTML 笔记(八):SVG

HTML 笔记(八):SVG

介绍

和 Canvas 相同,SVG 也是用与绘图的标签,区别在于 canvas 用于绘制位图、而 svg 用于绘制矢量图,示例如下:

位图(jpg、png、gif)是由一块块小方格组成,一个方格表示一个像素,优点是色彩丰富,缺点是放大失真、体积较大,矢量图用 XML 格式定义,通过各种「路径」和「填充颜色」来描述渲染的图片,优点是放大后不会失真、体积较小,缺点是不易制作色彩丰富的图片

<svg>
    <circle cx="50" cy="50" r="50" />
</svg>

svg 与 canvas 的不同之处在于,svg 可以通过 css 样式或行内样式自定义元素宽高

导入方式

svg 代码除了可以内嵌至 HTML 中之外,也可以保存在以 .svg 扩展名结尾的文件中,示例如下:

<svg width="500" height="400" xmlns="http://www.w3.org/2000/svg">
    <circle cx="150" cy="150" r="50" />
</svg>

SVG 使用 XML 格式定义图形,xmlns 属性定义 SVG 命名空间

以 .svg 扩展名结尾的文件本质上是一个图片,所以可以:

  1. 浏览器预览
  2. img 标签引用 svg 图片
  3. css 图片属性

示例如下:

<style>
    * {
        margin: 0;
        padding: 0;
    }

    div {
        height: 400px;
        background: url(./svg-sample.svg) no-repeat;
    }
</style>
<img src="./svg-sample.svg" alt="">
<div></div>

绘制图形

SVG 通过在根标签中定义各种图形标签来绘制图形,此外,必须说明的是,fill 属性表示图形填充的颜色、stroke 表示图形边框的颜色,以下是常用图形的示例:

矩形

<svg>
    <rect x="50" y="50" width="200" height="100" fill="none" stroke="black" />
</svg>

SVG 中的 rect 标签可以通过指定属性 rx/ry 设置矩形的圆角半径,示例如下:

<svg>
    <rect x="50" y="50" width="200" height="100" fill="none" stroke="black" rx="20" ry="20"/>
</svg>

rx/ry 的原理与 CSS 中 border-radius 属性的原理相同

<svg>
    <circle cx="250" cy="250" r="50" fill="none" stroke="black" />
</svg>

椭圆

<svg>
    <ellipse cx="150" cy="100" rx="100" ry="50" fill="none" stroke="black" />
</svg>

直线

<svg>
    <line x1="170" y1="150" x2="400" y2="150" stroke="black" />
</svg>

在 line 标签中 stroke 属性是必要的

折线

在 points 属性中两个相邻值为一组表示若干个被相连的坐标

非闭合

<svg>
    <polyline points="100 100 300 100 300 200" fill="none" stroke="black" />
</svg>

闭合

<svg>
    <polygon points="100 100 300 100 300 200" fill="none" stroke="black" />
</svg>

常用属性

在 SVG 中有一些常用的属性,大部分可以被用于任何图形标签中,也可以被用到 CSS 样式表中,内容如下:

属性描述
fill填充颜色
fill-opacity填充颜色的透明度(0 ~ 1)
stroke边框颜色
stroke-width边框宽度
stroke-opacity边框颜色的透明度(0 ~ 1)
stroke-linecap线条两侧的额外图形(butt / square / round)
stroke-dasharray虚线
stroke-dashoffset虚线的偏移位
stroke-linejoin折线转角的样式(miter / bevel / round)

示例如下:

rect {
    fill: blue;
    fill-opacity: 0.5;
    stroke: red;
    stroke-width: 20;
    stroke-opacity: 0.2;
}
<svg width="500" height="400">
    <rect x="50" y="50" width="300" height="100" />
</svg>

由于上述表格中的所有属性与 Canvas 中的含义相同,所以不再演示所有属性

绘制路径

在 SVG 中,可以通过 path 标签以及相应的指令绘制各种复杂的路径

线条

SVG 路径中线条的指令与 canvas 中常用的各种函数相似,内容如下:

指令含义
M起点坐标(moveTo)
L下一个坐标(lineTo)
H和上一个坐标的 Y 相等的坐标(horizontal lineTo)
V和上一个坐标的 X 相等的坐标(vertical lineTo)
Z关闭路径(closePath)

示例如下:

<svg width="500" height="400">
    <path d="M 100 100 L 300 100 L 300 300" fill="none" stroke="red" stroke-width="20" />
</svg>

如果一个坐标的 x 坐标或横坐标与上一个坐标的相应值相同,那么可以使用 H 或 V 指令简写,示例如下:

<svg width="500" height="400">
    <path d="M 100 100 H 300 V 300" fill="none" stroke="red" stroke-width="20" />
</svg>

类似于 canvas 中的闭合问题,可以通过闭合路径解决,示例如下;

<svg width="500" height="400">
    <path d="M 100 100 H 300 V 300 Z" fill="none" stroke="red" stroke-width="20" stroke-linejoin="round" />
</svg>

必须注意的是,指令的大小写区别:大写指令表示绝对位置、小写指令表示相对于上一个点的相对位置,示例如下:

<svg width="500" height="400">
    <path d="M 100 100 l 300 100" fill="none" stroke="red" />
</svg>

圆弧

SVG 中绘制圆弧可以使用 A 指令,从当前位置绘制弧线到特定位置,原型如下:

A (rx, ry, xr, laf, sf, x, y)

各个参数的含义如下:

参数含义
rx椭圆的 X 半径
ry椭圆的 Y 半径
xr椭圆的旋转角度
laf是否选择弧长较长的那一段
sf是否顺时针绘制
x终点 X 坐标
y终点 Y 坐标

先绘制以 rx 和 ry 为半径的椭圆,将椭圆旋转 xr 度,将此椭圆平移至某个位置,此位置的弧线上包含初始坐标和终点坐标(x, y),通过 laf 和 sf 确定椭圆上的某一段弧长,示例如下:

<svg width="500" height="400">
    <path d="M 100 100 A 100 50 60 1 0 300 200" fill="none" stroke="orange" />
</svg>

贝塞尔曲线

贝塞尔曲线是绘制弧线的一种方式,此处不讨论原理,将演示如何使用贝塞尔曲线指令

二次方

二次方贝塞尔曲线必须有起始点、控制点和终点,SVG 中使用 Q 指令从当前位置绘制二次贝塞尔曲线到指定位置,原型如下:

Q (x1, y1, x, y)

各个参数含义如下:

参数含义
x1控制点 X 坐标
y1控制点 Y 坐标
x终点 X 坐标
y终点 Y 坐标

示例如下:

<svg width="500" height="400">
    <path d="M 100 100 Q 150 0 300 100" fill="none" stroke="red" />
</svg>

三次方

三次方贝塞尔曲线必须有起始点、控制点 1、控制点 2 和终点,SVG 中使用 C 指令从当前位置绘制三次贝塞尔曲线到指定位置,原型如下:

C (x1, y1, x2, y2, x, y)

各个参数含义如下:

参数含义
x1控制点 1 的 X 坐标
y1控制点 1 的 Y 坐标
x2控制点 2 的 X 坐标
y2控制点 2 的 Y 坐标
x终点 X 坐标
y终点 Y 坐标

示例如下:

<svg width="500" height="400">
    <path d="M 100 100 C 150 0 250 0 300 100" fill="none" stroke="red" />
</svg>

绘制文本

类似于在 Canvas 中绘制文本,在 SVG 中绘制文本使用 text 标签,示例如下:

<svg width="500" height="400">
    <line x1="0" y1="200" x2="500" y2="200" stroke="red" />
    <line x1="250" y1="0" x2="250" y2="500" stroke="red" />
    <text x="250" y="200" style="font-size: 20px;" text-anchor="middle" dominant-baseline="middle">Reyn Morales</text>
</svg>

在上述示例中,使用 text-anchor 属性设置文本的水平对齐方式,使用 dominant-baseline 设置文本的垂直对齐方式,此外参考点和 canvas 中类似(左下角),style 属性设置文本大小和字体

此外,SVG 中也可以绘制多行文本,示例如下:

<svg width="500" height="400">
    <text stroke="blue">
        <tspan x="100" y="100">R</tspan>
        <tspan x="100" y="150">E</tspan>
        <tspan x="100" y="200" stroke="red">y</tspan>
        <tspan x="100" y="250">N</tspan>
    </text>
</svg>

路径文本

在 SVG 中实现路径文本的步骤如下:

  1. 定义路径
  2. 关联路径

示例如下:

<svg width="500" height="400">
    <defs>  <!-- 1. 定义路径 -->
        <path id="rainbow" d="M 100 100 Q 150 50 200 100" fill="none" stroke="red" />   
    </defs>
    <text>  <!-- 2. 关联路径 -->
        <textPath xlink:href="#rainbow">Reyn Morales</textPath> 
    </text>
</svg>

SVG 中 defs 标签可以隐藏路径,此外,如果文本内容的长度大于路径长度,那么多余的文本内容将被省略

超链接

在 SVG 中可以使用 a 标签为文本或图形添加超链接,示例如下:

<svg width="500" height="400">
    <a xlink:href="https://blog.csdn.net/Raymiles?spm=1019.2139.3001.5343" xlink:title="CSDN" target="blank">
        <text x="100" y="100" style="font-size: 20px;">Reyn's blog</text>
        <circle cx="200" cy="200" r="50" />
    </a>
</svg>

绘制图片

在 SVG 中可以使用 image 标签绘制图片,示例如下:

<svg width="500" height="400">
    <image xlink:href="./images/SpiderMan.jpg" x="50" y="50" width="200" height="400" />
</svg>

在上述示例中,为 image 标签设置宽高后,图片并不是严格以指定宽高显示:总是在某个方向上保持不变

结构元素

SVG 中提供了一系列结构元素,专门用于保存代码,从而简化程序

g

g 标签是 group 的简称,即用于将一系列标签分组保存以便统一操作,示例如下:

<g id="circles" fill="yellowgreen">
    <circle cx="50" cy="100" r="50" />
    <circle cx="50" cy="200" r="50" />
    <circle cx="50" cy="300" r="50" />
</g>

defs

defs 标签的作用类似于 g 标签,不同之处在于 defs 标签默认情况下不显示,示例如下:

<defs id="rects">
    <g>
        <rect x="50" y="100" width="50" height="50" />
        <rect x="50" y="200" width="50" height="50" />
        <rect x="50" y="300" width="50" height="50" />
    </g>
</defs>

use

use 标签可以引用 g 标签以及 defs 标签中的元素,并且可以在引用时自定义样式,示例如下:

<use xlink:href="#circles" x="400" />
<use xlink:href="#rects" x="150" fill="orangered" />

裁剪

SVG 中通过 clipPath 标签描述裁剪路径,位于路径范围之外的内容将被裁剪(不显示),示例如下:

<svg width="500" height="400">
    <clipPath id="clip">
        <circle cx="250" cy="200" r="100" />
    </clipPath>
    <rect x="100" y="100" width="300" height="200" clip-path="url(#clip)" />
</svg>

蒙版

SVG 中通过 mask 标签描述蒙版路径,位于路径范围之外的内容将被裁剪(不显示),位于路径范围之内的内容以透明显示,示例如下:

<svg width="500" height="400">
    <mask id="mask">
        <circle cx="250" cy="200" r="100" fill="orange" />
    </mask>
    <rect x="100" y="100" width="300" height="200" fill="blue" mask="url(#mask)" />
</svg>

渐变

SVG 中通过 linearGradient 和 radialGradient 标签分别描述线性渐变以及径向渐变,此处说明线性渐变,径向渐变是类似的,示例如下:

<svg width="500" height="400">
    <linearGradient id="myColor" x1="0" y1="0" x2="1" y2="1" gradientUnits="objectBoundingBox">
        <stop offset="0" stop-color="red" />
        <stop offset="1" stop-color="blue" />
    </linearGradient>
    <rect x="100" y="100" width="300" height="200" fill="url(#myColor)" />
</svg>

类似于 canvas 中的渐变属性,SVG 中也使用两个坐标表示渐变方向以及范围,此处坐标以百分比的形式描述,坐标形式由 gradientUnits 属性描述,默认取值为 objectBoundingBox,如果取值为 userSpaceOnUse,那么坐标以用户自定义坐标描述,示例如下:

<linearGradient id="myColor" x1="100" y1="100" x2="400" y2="100" gradientUnits="userSpaceOnUse">
    <stop offset="0" stop-color="red" />
    <stop offset="1" stop-color="blue" />
</linearGradient>
<rect x="100" y="100" width="300" height="200" fill="url(#myColor)" />

此外,stop 标签中的 offset 属性以及 stop-color 属性类似于 canvas 中的 addColorStop 方法

画笔

在 SVG 中通过 pattern 标签实现以自定义图形填充,示例如下:

<svg width="500" height="400">
    <defs>
        <pattern id="myPattern" width="0.2" height="0.2" patternUnits="objectBoundingBox">
            <circle cx="10" cy="10" r="10" fill="red" />
        </pattern>
    </defs>
    <rect x="100" y="100" width="300" height="200" fill="url(#myPattern)" />
</svg>

类似于渐变,SVG 中 pattern 标签的 width 和 height 属性在默认情况下以百分比的形式描述,形式由 patternUnits 属性决定,默认取值为 objectBoundingBox,如果取值为 userSpaceOnUse,那么宽高以用户自定义坐标描述,示例如下:

<defs>
    <pattern id="myPattern" width="20" height="20" patternUnits="userSpaceOnUse">
        <circle cx="10" cy="10" r="10" fill="red" />
    </pattern>
</defs>
<rect x="100" y="100" width="300" height="200" fill="url(#myPattern)" />

pattern 标签中 width 和 height 的取值决定了填充图案的密度

transform 相关

SVG 中 transform 相关的属性与 canvas 中的类似,均是调整坐标系的位置、角度以及缩放,示例如下:

<svg width="500" height="400">
    <rect x="100" y="50" width="300" height="100" />
    <rect x="100" y="250" width="300" height="100" transform="translate(50, 0) scale(0.5, 1) rotate(5)" />
</svg>

ViewBox

SVG 中,ViewBox 的含义是可视区域,默认情况下,内容区域即为可视区域,可以通过 svg 标签的 viewBox 属性设置可视区域的位置和大小,示例如下:

<svg>
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- 默认情况 -->
<svg viewBox="0 0 200 200">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- 等比例放大 -->
<svg viewBox="0 0 400 400">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- 等比例缩小 -->
<svg viewBox="0 0 100 100">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- 非等比例 width 缩小 -->
<svg viewBox="0 0 100 200">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- 非等比例 height 缩小 -->
<svg viewBox="0 0 200 100">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- 非等比例 width 放大 -->
<svg viewBox="0 0 400 200">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- 非等比例 height 放大 -->
<svg viewBox="0 0 200 400">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- X -->
<svg viewBox="50 0 200 200">
    <circle cx="100" cy="100" r="50" />
</svg>

<!-- Y -->
<svg viewBox="0 50 200 200">
    <circle cx="100" cy="100" r="50" />
</svg>

在上述示例中,所有 svg 标签的宽高均为 200px,viewBox 属性中的 4 个取值分别表示可视区域的 x、y、width 和 height,如果 x、y 被更改,那么可视区域将以原内容区域的原点为参考移动,如果 width、height 属性被等比例放大或缩小,那么内容区域也将被缩小或放大,类似于近大远小的效果,而此时可视区域的坐标系与内容区域的坐标系相同,若 width、height 属性不是被等比例放大或缩小的,那么系统将自动调整 viewBox 的位置, 自定义的 x、y 属性将失效,此时如果可视区域的坐标系和内容区域的坐标系必须保持相同, 那么必须使用 preserveAspectRatio 属性设置对齐方式,属性值如下所示:

属性值描述
xMinviewport 和 viewBox 左对齐
xMidviewport 和 viewBox x 轴中心对齐
xMaxviewport 和 viewBox 右对齐
YMinviewport 和 viewBox 上对齐
YMidviewport 和 viewBox y 轴中心对齐
YMaxviewport 和 viewBox 下对齐

上述表格中,xM-- 和 YM-- 必须连写,示例如下:

<!-- 不规则 -->
<svg viewBox="50 50 100 400" preserveAspectRatio="xMinYMin">
    <circle cx="100" cy="100" r="50" />
</svg>

动画

SVG 中可以通过 animate 相关的一系列标签实现动画效果

简单动画

SVG 中通过 animate 标签创建简单动画,示例如下:

<svg width="500" height="400">
    <animate attributeName="r" from="50" to="100" dur="2s" fill="freeze" xlink:href="#circle" />
    <circle id="circle" cx="100" cy="100" r="50" />
</svg>

animate 标签的属性描述如下:

属性描述
attributeName属性名称
from初始值
to终末值
dur持续时间
fill终末状态(remove【默认】、freeze)

此外,SVG 也可以将 animate 标签置于动画元素中,此时可以省略 xlink:href 属性,示例如下:

<svg width="500" height="400">
    <circle cx="100" cy="100" r="50">
        <animate attributeName="r" from="50" to="100" dur="2s" fill="freeze" />
    </circle>
</svg>

常用属性

SVG 中常用的 animate 标签属性描述如下所示:

属性描述
repeatCount执行次数
repeatDur总时长
begin延迟时间
restart元素开始动画之后,是否可以再次执行(always【默认】、whenNotActive、never)
calcMode每一个动画片段的动画表现(linear【默认】、discrete、paced、spline)
keyTimes划分动画时间片段
values划分相应片段的属性值

示例如下:

<svg width="500" height="400">
    <circle cx="100" cy="100" r="50">
        <animate attributeName="r" from="50" to="100" dur="2s" repeatCount="2" begin="click + 2s"
            restart="whenNotActive" fill="freeze" />
    </circle>
</svg>

联合动画

SVG 中可以将若干 animate 标签置于动画元素中,从而实现联合动画,示例如下:

<svg width="500" height="400">
    <circle cx="100" cy="100" r="50">
        <animate attributeName="r" from="50" to="100" dur="2s" fill="freeze" />
        <animate attributeName="fill" from="yellowgreen" to="orangered" dur="2s" fill="freeze" />
    </circle>
</svg>

下一个示例演示了如何实现往返动画:

<svg width="500" height="400">
    <circle cx="100" cy="100" r="50">
        <animate id="toLeft" attributeName="cx" from="100" to="300" dur="2s" begin="0;toRight.end" fill="freeze" />
        <animate id="toRight" attributeName="cx" from="300" to="100" dur="2s" begin="toLeft.end" fill="freeze" />
    </circle>
</svg>

在上述示例中,begin 属性值 toLeft.end 表示在 toLeft 动画结束后立即执行当前动画,而 0; 则表示第一次无延迟时间、之后在 toRight 动画结束后立即执行此动画

transform 动画

SVG 中通过 animateTransform 标签实现 transform 属性相关的动画,示例如下:

<rect x="100" y="100" width="300" height="100">
    <animateTransform id="translate" attributeName="transform" type="translate" from="0 0" to="0 100"
        dur="2s" />
    <animateTransform id="scale" attributeName="transform" type="scale" from="1 1" to="0.5 1" dur="2s"
        begin="translate.end" />
    <animateTransform id="rotate" attributeName="transform" type="rotate" from="0" to="30" dur="2s"
        begin="scale.end" />
</rect>

路径动画

SVG 中通过 animateMotion 标签实现路径动画,示例如下:

<svg width="500" height="400">
    <path d="M 100 100 L 300 200" fill="none" stroke="red" />
    <rect x="100" y="100" width="10" height="10" fill="blue">
        <animateMotion path="M 0 0 L 200 100" begin="click" dur="4s" fill="freeze"></animateMotion>
    </rect>
</svg>

在上述示例中,path 标签不是必须的,绘制出来可以作为一个参考,此外,animateMotion 标签中的 path 属性和 path 标签中 d 属性用法相同,关键在于 animateMotion 中 path 属性的坐标系以动画元素为原点

脚本编程

如下是一个 SVG 示例:

<svg width="500" height="400">
    <circle cx="100" cy="100" r="50" fill="orange" />
    <image xlink:href="./images/JavaScript.jpg" x="300" y="200"/>
</svg>

如下示例演示了如何使用 JavaScript 动态实现上述效果:

window.onload = function () {
    const SVG_NS = "http://www.w3.org/2000/svg";
    let oSvg = document.createElementNS(SVG_NS, "svg");
    oSvg.setAttribute("width", "500");
    oSvg.setAttribute("height", "400");
    document.body.appendChild(oSvg);

    let oCircle = document.createElementNS(SVG_NS, "circle");
    oCircle.setAttribute("cx", "100");
    oCircle.setAttribute("cy", "100");
    oCircle.setAttribute("r", "50");
    oCircle.setAttribute("fill", "orange");
    oSvg.appendChild(oCircle);

    const XLINK_NS = "http://www.w3.org/1999/xlink";
    let oImage = document.createElementNS(SVG_NS, "image");
    oImage.setAttributeNS(XLINK_NS, "xlink:href", "./images/JavaScript.jpg");
    oImage.setAttribute("x", "300");
    oImage.setAttribute("y", "200");
    oSvg.appendChild(oImage);
}   

SVG 脚本编程的关键在于,凡是创建 SVG 元素时,必须使用方法 createElementNS,且在调用时传入相应的命名空间,xlink:href 属性亦是如此,必须使用方法 setAttributeNS,如果必须使用脚本编程,那么建议学习如下框架:

  • SVG.js
  • Snap.svg

相关文章:

  • 15.5 - 边界值法
  • 图解MySQL 记录
  • Effective C++学习笔记——确定对象被使用前已先被初始化
  • 一文弄懂 HashMap 中的位运算
  • 【易购管理系统】路由界面基础搭建
  • Linux系统常规异常报错解决汇总:
  • 【编程语言】什么是闭包?你可能经常在用它,但不知道它叫闭包!
  • 【live2D看板娘】为你的网站添加萌萌的二次元板娘,这都拿不下你?
  • 信息学奥赛一本通:1014:与圆相关的计算
  • 【APP 逆向百例】Frida 初体验,root 检测与加密字符串定位
  • 安卓中listview中性能优化的处理
  • 期刊论文-写作-投稿-工具等的经验合集
  • Java 属性文件乱码问题
  • 腾讯毕业复盘
  • C语言基础篇
  • Angularjs之国际化
  • css系列之关于字体的事
  • docker-consul
  • ES6语法详解(一)
  • Intervention/image 图片处理扩展包的安装和使用
  • Iterator 和 for...of 循环
  • Java Agent 学习笔记
  • java概述
  • Mac转Windows的拯救指南
  • PAT A1050
  • PyCharm搭建GO开发环境(GO语言学习第1课)
  • Python代码面试必读 - Data Structures and Algorithms in Python
  • Vue 2.3、2.4 知识点小结
  • 从零开始在ubuntu上搭建node开发环境
  • 订阅Forge Viewer所有的事件
  • 基于遗传算法的优化问题求解
  • 面试遇到的一些题
  • 名企6年Java程序员的工作总结,写给在迷茫中的你!
  • 前端存储 - localStorage
  • 如何正确配置 Ubuntu 14.04 服务器?
  • 设计模式走一遍---观察者模式
  • 数据科学 第 3 章 11 字符串处理
  • 腾讯优测优分享 | Android碎片化问题小结——关于闪光灯的那些事儿
  • 函数计算新功能-----支持C#函数
  • ​ssh免密码登录设置及问题总结
  • (1)虚拟机的安装与使用,linux系统安装
  • (4)(4.6) Triducer
  • (机器学习-深度学习快速入门)第一章第一节:Python环境和数据分析
  • (亲测)设​置​m​y​e​c​l​i​p​s​e​打​开​默​认​工​作​空​间...
  • (全部习题答案)研究生英语读写教程基础级教师用书PDF|| 研究生英语读写教程提高级教师用书PDF
  • (十五)使用Nexus创建Maven私服
  • (五)MySQL的备份及恢复
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • .form文件_一篇文章学会文件上传
  • .net core 调用c dll_用C++生成一个简单的DLL文件VS2008
  • .NET MVC 验证码
  • .Net Redis的秒杀Dome和异步执行
  • .NET 使用 ILMerge 合并多个程序集,避免引入额外的依赖
  • .Net6 Api Swagger配置
  • .NET精简框架的“无法找到资源程序集”异常释疑