前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >原生 JS 手写一个优雅的图片预览功能,带你吃透背后原理

原生 JS 手写一个优雅的图片预览功能,带你吃透背后原理

原创
作者头像
茶无味的一天
修改于 2022-11-02 10:40:13
修改于 2022-11-02 10:40:13
3.9K014
代码可运行
举报
文章被收录于专栏:品味前端品味前端
运行总次数:14
代码可运行

前言

本文将用一个极简的例子详细讲解如何用原生JS一步步实现完整的图片预览和查看功能,无任何第三方依赖,兼容PC与H5,实现了触屏双指缩放等,干货满满。

完整代码

为提升阅读体验,正文中代码展示均有部分省略处理,查看完整代码可以访问以下链接:https://code.juejin.cn/pen/7158337368355766285

实现原理

实现图片预览/查看的关键点在于 CSS3 中的 transform 变换,该属性应用于元素在2D或3D上的旋转,缩放,移动,倾斜等等变换,通过设置 translate(x,y) 即可偏移元素位置,设置scale即可缩放元素,当然你也可以只设置 matrix 来完成上述所有操作,这涉及到矩阵变换的知识,本文使用的均是CSS提供的语法糖进行变换操作。

PC上的点击、移动,H5的手势操作,都离不开DOM事件监听。例如鼠标移动事件对应 mousemove,移动端因为没有鼠标则对应 touchmove,而本文将介绍如何仅通过指针事件来进行多端统一的事件监听。在监听事件中我们可以通过 event 对象获取各种属性,例如常用的 offsetXoffsetY 相对偏移量,clientXclientY 距离窗口的横坐标和纵坐标等。

除此之外可能还需要具备一点数学基础,如果像我这样数学知识几乎都还给了高中老师的话可以复习下向量的加减计算。

打开蒙层

在开始前我们先准备一个图片列表,并绑定好点击事件,当点击图片时,通过 document.createElement 创建元素,然后把图片节点克隆进蒙层中,这对你来说并不难,简单实现如下。

代码语言:html
AI代码解释
复制
<div id="list">
    <img class="item" src="...." />
    ............
</div>
代码语言:css
AI代码解释
复制
/* 图片预览样式 */
.modal {
  touch-action: none;
  user-select: none;
  position: fixed;
  top: 0;
  left: 0;
  width: 100vw;
  height: 100vh;
  background-color: rgba(0, 0, 0, 0.75);
}
.modal > img {
  position: absolute;
  padding: 0;
  margin: 0;
  transform: translateZ(0);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let cloneEl = null
let originalEl = null
document.getElementById('list').addEventListener('click', function (e) {
  e.preventDefault()
  if (e.target.classList.contains('item')) {
    originalEl = e.target // 缓存原始图DOM节点
    cloneEl = originalEl.cloneNode(true) // 克隆图片
    originalEl.style.opacity = 0
    openPreview() // 打开预览
  }
})

function openPreview() {
  // 创建蒙层
  const mask = document.createElement('div')
  mask.classList.add('modal')
  // 添加在body下
  document.body.appendChild(mask)
  // 注册蒙层的点击事件,关闭弹窗
  const clickFunc = function () {
    document.body.removeChild(this)
    originalEl.style.opacity = 1
    mask.removeEventListener('click', clickFunc)
  }
  mask.addEventListener("click", clickFunc)
  mask.appendChild(cloneEl) // 添加图片
}

// 用于修改样式的工具类,并且可以减少回流重绘,后面代码中会频繁用到
function changeStyle(el, arr) {
  const original = el.style.cssText.split(';')
  original.pop()
  el.style.cssText = original.concat(arr).join(';') + ';'
}

这时候我们成功添加一个打开预览的蒙层效果了,但克隆出来的图片位置是没有指定的,此时需要用 getBoundingClientRect() 方法获取一下元素相对于可视窗口的距离,设置为图片的起始位置,覆盖在原图片的位置之上,以取代文档流中的图片。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// ......
// 添加图片
const { top, left } = originalEl.getBoundingClientRect()
changeStyle(cloneEl, [`left: ${left}px`, `top: ${top}px`])
mask.appendChild(cloneEl)

效果如下,看起来像点击高亮图片的感觉:

接下来我们需要实现焦点放大的效果,简单来说就是计算两点之间的位移距离作为 translate 偏移量,将图片偏移到屏幕中心点位置,然后缩放一定的比例来达到查看效果,通过 transition 实现过渡动画。

中心点位置我们可以通过 window 下的 innerWidthinnerHeight 来获取浏览器可视区域宽高,然后除以2即可得到中心点坐标。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
const { innerWidth: winWidth, innerHeight: winHeight } = window

// 计算自适应屏幕的缩放值
function adaptScale() {
  const { offsetWidth: w, offsetHeight: h } = originalEl // 获取文档中图片的宽高
  let scale = 0
  scale = winWidth / w
  if (h * scale > winHeight - 80) {
    scale = (winHeight - 80) / h
  }
  return scale
}

// 移动图片到屏幕中心位置
  const originalCenterPoint = { x: offsetWidth / 2 + left, y: offsetHeight / 2 + top }
  const winCenterPoint = { x: winWidth / 2, y: winHeight / 2 }
  const offsetDistance = { left: winCenterPoint.x - originalCenterPoint.x + left, top: winCenterPoint.y - originalCenterPoint.y + top }
  const diffs = { left: ((adaptScale() - 1) * offsetWidth) / 2, top: ((adaptScale() - 1) * offsetHeight) / 2 }
  changeStyle(cloneEl, ['transition: all 0.3s', `width: ${offsetWidth * adaptScale() + 'px'}`, `transform: translate(${offsetDistance.left - left - diffs.left}px, ${offsetDistance.top - top - diffs.top}px)`])
  // 动画结束后消除定位重置的偏差
  setTimeout(() => {
    changeStyle(cloneEl, ['transition: all 0s', `left: 0`, `top: 0`, `transform: translate(${offsetDistance.left - diffs.left}px, ${offsetDistance.top - diffs.top}px)`])
    offset = { left: offsetDistance.left - diffs.left, top: offsetDistance.top - diffs.top } // 记录值
  }, 300)

这里先利用绝对定位 left top 来设置克隆元素的初始位置,再通过 translate 偏移位置,是为了更自然地实现动画效果,动画结束后再将绝对定位的数值归零并把偏移量加进 translate 中,并且这里我并没有直接使用 scale 放大元素,而是将比例转化为宽高的变化。最终效果如下:

图片缩放(PC)

在PC实现图片缩放相对是比较简单的,我们利用滚轮事件监听并改变 scale 值即可。重点是利用 deltaY 值的正负来判断滚轮是朝上还是朝下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let origin = 'center'
let scale = 1
// 注册事件
mask.addEventListener('mousewheel', zoom, { passive: false })

// 滚轮缩放
const zoom = (event) => {
  if (!event.deltaY) {
    return
  }
  event.preventDefault()
  origin = `${event.offsetX}px ${event.offsetY}px`
  
  if (event.deltaY < 0) {
    scale += 0.1 // 放大
  } else if (event.deltaY > 0) {
    scale >= 0.2 && (scale -= 0.1) // 缩小
  }
  changeStyle(cloneEl, ['transition: all .15s', `transform-origin: ${origin}`, `transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`])
}

因为缩放始终都以图片中心为原点进行缩放,这显然不符合我们的操作习惯,所以在上面的代码中,我们通过鼠标当前的偏移量offsetX、offsetY 的值改变 transform-origin 来动态设置缩放的原点,效果如下:

乍一看好像没什么问题,事实上如果鼠标不断移动且幅度很大时会出现抖动,需要消除原点位置突然改变带来的影响才能完全解决这个问题(期初我并未发现,后面在做移动端缩放时简直是灾难级体验)而由于PC上问题并不明显,这里先按下不表,后面会详细提到。

移动查看

由于缩放导致图像发生变化,我们自然地想到要靠移动来观察图片,此时体现在PC端的是按住鼠标拖拽,移动端则是手指点击滑动,而两者各自的事件监听显然并不共通,如以移动事件为例,PC端对应的是 mousemove 事件而移动端则是 touchmove 事件,这就意味着如果我们要做到兼容多端,就必须注册多个事件监听。

那么有没有一种事件可以做到同时监听鼠标操作手指操作呢?答案是有的!那就是 指针事件(Pointer events),它被设计出来就是为了便于提供更加一致与良好的体验,无需关心不同用户和场景在输入硬件上的差异。接下来我们就以此事件为基础来完成各项操作功能。

指针 是输入设备的硬件层抽象(比如鼠标,触摸笔,或触摸屏上的一个触摸点),它能指向一个具体表面(如屏幕)上的一个(或一组)坐标,可以表示包括接触点的位置,引发事件的设备类型,接触表面受到的压力等。PointerEvent 接口继承了所有 MouseEvent 中的属性,以保障原有为鼠标事件所开发的内容能更加有效的迁移到指针事件。

移动图片的实现是比较简单的,在每次指针按下时我们记录 clientXclientY 为初始值,移动时计算当前的值与初始点位的差值加到 translate 偏移量中即可。需要注意的是每次移动事件结束时都必须更新初始点位,否则膨胀的偏移距离会使图片加速逃逸可视区域。另外当抬起动作结束时,会触发 click 事件,所以注意加入全局变量标记以及定时器进行一些判断处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let startPoint = { x: 0, y: 0 } // 记录初始触摸点位
let isTouching = false // 标记是否正在移动
let isMove = false // 正在移动中,与点击做区别

// 鼠标/手指按下
window.addEventListener('pointerdown', function (e) {
  e.preventDefault()
  isTouching = true
  startPoint = { x: e.clientX, y: e.clientY }
})
// 鼠标/手指抬起
window.addEventListener('pointerup', function (e) {
  isTouching = false
  setTimeout(() => {
    isMove = false
  }, 300);
})
// 鼠标/手指移动
window.addEventListener('pointermove', (e) => {
  if (isTouching) {
    isMove = true
    // 单指滑动/鼠标移动
    offset = {
      left: offset.left + (e.clientX - startPoint.x),
      top: offset.top + (e.clientY - startPoint.y),
    }
    changeStyle(cloneEl, [`transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`, `transform-origin: ${origin}`])
    // 注意移动完也要更新初始点位,否则图片会加速逃逸可视区域
    startPoint = { x: e.clientX, y: e.clientY }
  }
})

双指缩放(移动端)

TouchEvent 的事件对象中,我们可以找到 touches 这个数组,在移动端通常都是利用这个数组来判断触点个数的,例如 touches.length > 1 即是多点操作,这是我们实现双指缩放的基础。但在 指针事件 中却找不到类似的对象(MDN对其描述只是扩展了 MouseEvent 的接口),想来 Touch 对象只服务于 TouchEvent 这点其实也可以理解,所以要自己实现对触摸点数的记录。

这里我们使用 Map 数组对触摸点进行记录(通过这个实例你可以看到 Map 数组纯 api 操作增删改有多么优雅)。其中我们利用 pointerId 标识触摸点,移动事件中根据事件对象的 pointerId 来更新对应触点(指针)的数据,当触点抬起时则从Map中删除点位:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let touches = new Map() // 触摸点数组

window.addEventListener('pointerdown', function (e) {
  e.preventDefault()
  touches.set(e.pointerId, e) // TODO: 点击存入触摸点
  isTouching = true
  startPoint = { x: e.clientX, y: e.clientY }
  if (touches.size === 2) { 
        // TODO: 判断双指触摸,并立即记录初始数据
  }
})

window.addEventListener('pointerup', function (e) {
  touches.delete(e.pointerId) // TODO: 抬起时移除触摸点
  // .....
})

window.addEventListener('pointermove', (e) => {
  if (isTouching) {
    isMove = true
    if (touches.size < 2) {
      // TODO: 单指滑动,或鼠标拖拽
    } else {
      // TODO: 双指缩放
      touches.set(e.pointerId, e) // 更新点位数据
      // .......
    }
  }
})

Map 是二维数组,可以利用 Array.from 转成普通数组即可通过 index 下标取值。

简单在手机浏览器上测试后发现,这个数组偶尔会不停增加(例如在滑动页面时),也就是 pointerup 会出现不能正确删除对应点位的情况,或者说被意外中断了,此时会触发 pointercancel 事件监听(对应 touchcancel 事件),我们必须在这里清空数组,这是容易被忽略的一点,原本 TouchEvent 中的 touches 并不需要处理。

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
window.addEventListener('pointercancel', function (e) {
  touches.clear() // 可能存在特定事件导致中断,所以需要清空
})

现在我们有了对触摸点判断的基础,就可以开始实现缩放了,当双指接触屏幕时,记录两点间距离作为初始值,当双指在屏幕上捏合,两点间距不停发生变化,此时存在一个变化比例 = 当前距离 / 初始距离,该比例作为改变 scale 的系数就能得到新的缩放值。

在上一篇文章手写拖拽效果中我也讲到了如何在JS中使用数学方法计算两点间距离,下面介绍另一种常见的简洁写法,Math.hypot() 函数返回其参数的平方和的平方根:

w(end.x - start.x, 2) + Math.pow(end.y - start.y, 2))

回到代码中,直接取出 touches 的前两个点位,于两点间获取距离:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
// 获取距离
function getDistance() {
  const touchArr = Array.from(touches)
  if (touchArr.length < 2) {
    return 0
  }
  const start = touchArr[0][1]
  const end = touchArr[1][1]
  return Math.hypot(end.x - start.x, end.y - start.y)
}

继续完善上面的事件监听代码:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let touches = new Map() // 触摸点数组
let lastDistance = 0 // 记录最后的双指初始距离
let lastScale = 1 // 记录下最后的缩放值

window.addEventListener('pointerdown', function (e) {
  // .........
  if (touches.size === 2) { // TODO: 判断双指触摸,并立即记录初始数据
    lastDistance = getDistance()
    lastScale = scale // 把当前的缩放值存起来
  }
})

window.addEventListener('pointerup', function (e) {
// .........
  if (touches.size <= 0) {
    // .........
  } else {
    const touchArr = Array.from(touches)
    // 双指如果抬起了一个,可能还有单指停留在触屏上继续滑动,所以更新点位
    startPoint = { x: touchArr[0][1].clientX, y: touchArr[0][1].clientY }
  }
  // .......
})

window.addEventListener('pointermove', (e) => {
  e.preventDefault()
  if (isTouching) {
    isMove = true
    if (touches.size < 2) { // 单指滑动
      // .......
    } else {
      // 双指缩放
      touches.set(e.pointerId, e)
      const ratio = getDistance() / lastDistance // 比较距离得出比例
      scale = ratio * lastScale // 修改新的缩放值
      changeStyle(cloneEl, ['transition: all 0s', `transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`, `transform-origin: ${origin}`])
    }
  }
})

以上仅是实现了缩放的处理,而缩放原点还在默认的图片中心,就和PC端一样我们还要改变原点才显得自然,对于双指缩放来说,改变的只是两点间距离,无论双指间距如何改变,两点连成的线段中心点是不会变的,所以我们只要通过两点求出中心点坐标然后设置为缩放原点坐标即可:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
window.addEventListener('pointermove', (e) => {
  // .......
      // 双指缩放
      const ratio = getDistance() / lastDistance // 比较距离得出比例
      scale = ratio * lastScale // 修改新的缩放值    
      const touchArr = Array.from(touches)
      const start = touchArr[0][1]
      const end = touchArr[1][1]
      x = (start.offsetX + end.offsetX) / 2
      y = (start.offsetY + end.offsetY) / 2
      origin = `${x}px ${y}px`
      changeStyle(cloneEl, ['transition: all 0s', `transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`, `transform-origin: ${origin}`])
  // ........
})

这时缩放感觉是没有问题了,但是每当往屏幕中的不同位置再多进行几次操作时,图片会突然间闪动一下位置,到最后几乎不受控制。

这就回到前面提到的,原点位置突然改变带来的偏移量引起了图片位置的闪动,这段偏移是如何产生的呢?我们画两张图看下,在原点变化的前后图像的坐标点发生了哪些变化:

如上图,原点为 O 时,我们取右下角的点设为点 A,图像放大2倍时 A 点变换到 B 点。

而当原点突然变为 O’ 时,点 A 在图像放大2倍时则变换到了 B' 点。

我们可以把图像的偏移抽象为图像某个点位的偏移,这样问题就变成了计算 BB' 的距离:

设原点 O=(Ox , Oy),点 A=(x, y),缩放值为 sOA 向量乘缩放倍数得出 OB 的向量:

B 坐标就等于 OB 向量加上原点 O 的坐标:

同理得出点 B' 的坐标:

BB' 的距离就是两点相减后的结果,两点已在上面得出,代入计算过程这里就不多写了,最终化简的结果如下:

在进行缩放时我们主动改变 scale 的值,那么 s 是已知的,双指落下时是我们主动改变了缩放原点,(Ox , Oy)(O'x , O'y) 这两个点也是已知的,那么根据上面的式子就可以得出 BB' 的实际距离了,也就是图像的偏移量。这么说有点抽象,我们还是回到代码中,在双指缩放时将这个偏移量减掉,同样的在PC端的缩放中,我们也加入对偏移量的修正:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
let scaleOrigin = { x: 0, y: 0, }
// 获取中心改变的偏差
function getOffsetCorrection(x = 0, y = 0) {
  const touchArr = Array.from(touches)
  if (touchArr.length === 2) {
    const start = touchArr[0][1]
    const end = touchArr[1][1]
    x = (start.offsetX + end.offsetX) / 2
    y = (start.offsetY + end.offsetY) / 2
  }
  origin = `${x}px ${y}px`
  const offsetLeft = (scale - 1) * (x - scaleOrigin.x) + offset.left
  const offsetTop = (scale - 1) * (y - scaleOrigin.y) + offset.top
  scaleOrigin = { x, y }
  return { left: offsetLeft, top: offsetTop }
}

window.addEventListener('pointermove', (e) => {
  // .......
      // 双指缩放
      touches.set(e.pointerId, e)
      const ratio = getDistance() / lastDistance
      scale = ratio * lastScale
      offset = getOffsetCorrection()
      changeStyle(cloneEl, ['transition: all 0s', `transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`, `transform-origin: ${origin}`])
  // ........
})

// 滚轮缩放
const zoom = (event) => {
  // ........
  offset = getOffsetCorrection(event.offsetX, event.offsetY)
  changeStyle(cloneEl, ['transition: all .15s', `transform-origin: ${origin}`, `transform: translate(${offset.left + 'px'}, ${offset.top + 'px'}) scale(${scale})`])
}

最终双指缩放效果如下,啊~ 如此丝滑,不由得流下两行热泪

一些细节

有些在正文中未提及,但同样重要的小细节。

是什么阻止了滚动?

虽然浏览器滚动对应的其实是 scroll 事件,但我们在PC上滚动通常都是用利用滚轮(笔记本触控板也被视作滚轮),所以在滚轮事件阻止系统默认事件也就阻止了滚动,但不是完全阻止,因为滚动条没隐藏的话还是可以拖动来滚动页面的,在本文例子中并没有针对滚动做什么处理,如果需要完全禁止滚动,应该在打开弹窗时为 body 设置 overflow'hidden'

注意滚轮事件(wheel)是可以触发冒泡捕获的,而滚动事件(scroll)却无法触发冒泡,了解更多可以看我之前的一篇文章:哪些浏览器事件不会冒泡

至于移动端又是为什么阻止了滚动呢?得益于一个强大的CSS属性,可能在开头布局部分你就发现了这个属性,没错,这里为弹层遮罩设置了 touch-action: none; 从而阻止了所有手势效果,自然也就不会发生页面滚动。该属性在平时的业务代码中也可用于优化移动端性能、解决 touchmovepassive 报错等,这个我在之前另一篇文章中有提到,感兴趣可以看看:一行CSS提升页面滚动性能

translateZ(0) 有什么用?

在本例的代码中这个CSS本身是没有意义的,为的只是触发css3硬件加速来提升性能,那为什么不直接使用 translate3d() 呢?又或者使用 will-change: transform; 来告诉浏览器提升渲染性能呢?

正常图片显示

使用了 translate3d 之后

答案是后两者都会使移动端的图片变模糊!(Android似乎不会)起初我发现图片在手机上模糊的问题时,调试很久都没定位到源头,一筹莫展之际想起以前做H5网页常使用 vant 框架,就想要不看看它源码中的图片预览组件吧,很快我找到相关代码位置,代码截图:

从代码片段中我看到 vant 并没有使用任何 translate3dscale3d 属性,看来是真的有坑了。其实我们使用 translate3d 提升性能也是把第三个参数一直设置为0(2d平面没有Z轴)来实现的,这和 translateZ(0) 是等价的。

但奇怪的是单独设置 translateZ 却没有引发问题。。

will-change 这个属性,我也是最近无意中发现的,根据 MDN 文档的描述,该属性是用于提升性能的最后手段,怎么理解这句话呢?根据上面实践的结论来看,应该可以认为是浏览器尝试牺牲掉一些画面质量来换取性能提升的一种手段。

结束

以上就是文章的全部内容,感谢看到这里,希望对你有所帮助或启发!创作不易,如果觉得文章写得不错,可以点赞收藏支持一下,也欢迎关注,我会更新更多实用的前端知识与技巧。我是茶无味de一天,期待与你共同成长~

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
暂无评论
推荐阅读
YashanDB|Ubuntu 加载 C 驱动后 PHP 启动失败?原来是“库冲突”惹的祸
在 Ubuntu 系统中配置 YashanDB 数据库时,一些开发者会将 C 驱动路径加入 LD_LIBRARY_PATH,以支持 PHP、Python 等语言通过 C 接口连接数据库。但在实际操作中,有用户反馈:
数据库砖家
2025/04/24
300
技术干货 | YashanDB+Zabbix搭建监控可视化平台,手把手实操!
Zabbix是一个基于WEB界面的提供分布式系统监视以及网络监视功能的企业级的开源解决方案,能监视各种网络参数,保证服务器系统的安全运营,并提供灵活的通知机制以让系统管理员快速定位/解决存在的各种问题。接下来,这篇文章将详细展开如何基于YashanDB进行Zabbix的部署
用户10349277
2025/02/24
1140
Linux系统下实现QT程序打包发布
比如: build-ffmpeg_code-Desktop_Qt_5_12_6_GCC_64bit-Release
DS小龙哥
2022/01/12
8.8K0
错误 could not find or load the Qt platform plugin "xcb" 解决方案
在使用 VS code 调试Linux远程代码时报错,could not find or load the Qt platform plugin "xcb",本文记录解决方案。 错误复现 VS code 调试远程代码时报错、 This application failed to start because it could not find or load the Qt platform plugin "xcb". Available platform plugins are: linuxfb, mi
为为为什么
2022/08/06
5.2K0
Ubuntu下显卡驱动安装
安装完毕后跳出一个界面,选择lightdm,再sudo service lightdm stop。
数据科学工厂
2023/01/19
3.6K0
【YashanDB知识库】如何远程连接、使用YashanDB?
在各个项目实施中,我们经常遇到客户、开发人员需要连接和使用YashanDB但不知如何操作的问题,本文旨在介绍远程连接、使用YashanDB的几种方式。
用户10349277
2025/02/21
910
YashanDB |PHP 无法连接 YashanDB?ODBC 驱动“找不到”的锅怎么甩?
在使用 PHP 部署 Web 应用,特别是采用 nginx + php-fpm 架构时,部分开发者在通过 ODBC 方式连接 YashanDB 数据库时会遇到如下问题:
数据库砖家
2025/04/24
1130
浅谈Linux的动态链接库
上一篇我们分析了Hello World是如何编译的,即使一个非常简单的程序,也需要依赖C标准库和系统库,链接其实就是把其他第三方库和自己源代码生成的二进制目标文件融合在一起的过程。经过链接之后,那些第三方库中定义的函数就能被调用执行了。早期的一些操作系统一般使用静态链接的方式,现在基本上都在使用动态链接的方式。
PP鲁
2020/09/15
9.4K1
浅谈Linux的动态链接库
【YashanDB 知识库】DolphinScheduler 适配崖山 Python 驱动
概述 本文主要介绍DolphinScheduler调度器适配崖山Python驱动,支持在DolphinScheduler上通过python任务访问崖山数据库。
用户10349277
2025/03/03
490
CentOS 6.5下配置Nginx
下载最新版本Nginx 网址http://nginx.org/en/download.html
星哥玩云
2022/06/30
7520
崖山数据库 YMP 迁移工具使用体验
崖山迁移平台(YashanDB Migration Platform,YMP)是 YashanDB 提供的数据库迁移产品,支持异构 RDBMS 与 YashanDB 之间进行迁移评估、离线迁移、数据校验的能力。YMP提供可视化服务,用户只需通过简单的界面操作,即可完成从评估到迁移整个流程的执行与监控,实现低门槛、低成本、高效率的异构数据库迁移。
JiekeXu之路
2024/05/28
4740
崖山数据库 YMP 迁移工具使用体验
D-Link DIR-815 路由器多次溢出分析
exploitdb链接:https://www.exploit-db.com/exploits/33863/
用户1423082
2024/12/31
950
D-Link DIR-815 路由器多次溢出分析
Ubuntu16.04 Caffe 安装步骤记录(超详尽)
历时一周终于在 ubuntu16.04 系统成功安装 caffe 并编译,网上有很多教程,但是某些步骤并没有讲解详尽,导致配置过程总是出现各种各样匪夷所思的问题,尤其对于新手而言更是欲哭无泪,在我饱受折磨后决定把安装步骤记录下来,尽量详尽清楚明白,避免后来小白重蹈覆辙。
全栈程序员站长
2022/08/01
1.7K0
Ubuntu16.04 Caffe 安装步骤记录(超详尽)
【安装教程】Ubuntu16.04+Caffe+英伟达驱动410+Cuda10.0+Cudnn7.5+Python2.7+Opencv3.4.6安装教程
对于caffe的安装过程,可以说是让我终身难忘。两个星期就为了一个caffe,这其中的心路历程只有自己懂。从实验室的低配置显卡开始装Ubuntu,到编译caffe,解决各种报错,这个过程花费了一周的时间。把cuda版本和N卡驱动版本一降再降,仍然不管用。因此手剁了一台8000的高配置主机。之后为了平衡实验室项目,首先花了半天时间将win10下的相关和其他杂七杂八的软件配置。只有以为只需Ubuntu安装好,caffe编译成功即可,不想安装完Ubuntu之后,却电脑没有引导启动项,把网上的方法试了个遍,却仍无法解决。因此听到一种说法是,win10的启动路径覆盖了Ubuntu启动路径。因此,决定重新再来,将自己的固态和机械全部初始化,首先在固态上安装Ubuntu16.04,在机械上安装Win10,对于双系统的安装请参照我的另一篇博客:Win10与Ubuntu16.04双系统安装教程。在这种情况下参加那个caffe安装成功。请注意,对于双系统建议先安装Ubuntu,并将caffe编译成功之后在去机械上安装Win10。Caffe的安装教程请参照如下安装教程。
AI那点小事
2020/04/20
1.9K0
【安装教程】Ubuntu16.04+Caffe+英伟达驱动410+Cuda10.0+Cudnn7.5+Python2.7+Opencv3.4.6安装教程
Ubuntu22安装N卡驱动以及CUDA
官网网址:https://www.nvidia.com/Download/index.aspx?lang=en-us
Here_SDUT
2024/02/03
4K0
Ubuntu22安装N卡驱动以及CUDA
彻底解决编译PHP找不到libc-client.a的问题
最近PHP爆出漏洞,老高也在升级PHP版本,并加入一些支持,但是遇到这个问题很蛋疼
老高的技术博客
2022/12/28
9950
论文复现前奏篇:漫漫长路之Caffe-C3D
0.导语1.Caffe源码编译1.0 NVIDIA与Anaconda31.1 GCC与G++降级1.2 cuda 9.01.3 cuDNN1.4 caffe-gpu源码编译1.5 python库安装1.6 编译1.7 环境变量1.8 导包测试2.caffe-cifar10测试2.1 获取数据集2.2 转换数据集格式2.3 训练及测试3.Caffe-C3D3.1 下载及配置3.2 安装库与编译4.C3D-cifar10测试4.1 获取数据集4.2 转换数据集格式4.3 训练及测试
公众号guangcity
2019/09/20
1.3K0
论文复现前奏篇:漫漫长路之Caffe-C3D
Ubuntu14.04下安装Caffe
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/Gavin__Zhou/article/details/47363385
GavinZhou
2019/05/26
9580
Ubuntu 16.04下为TITAN 1080 显卡安装驱动及Gpu版TensorFlow|深度学习
近来入坑了TITAN 1080显卡,在Ubuntu 16.04下为装好驱动以使用Gpu版TensorFlow可不简单,踩了许多坑之后写下此篇为记录。 下载Cuda 按装官方教程,我们可以应该安装Cu
陆勤_数据人网
2018/02/28
1.5K0
Ubuntu 16.04下为TITAN 1080 显卡安装驱动及Gpu版TensorFlow|深度学习
YashanDB Docker镜像制作
常规使用 yasboot 部署数据库的方法,操作流程复杂,需要配置许多配置文件以及环境变量,不同用户使用的环境不同,那么环境配置也会存在差异,每当更换机器或者有新系统开发时都要就要重复不熟⼀次。
用户10349277
2025/02/25
2360
推荐阅读
相关推荐
YashanDB|Ubuntu 加载 C 驱动后 PHP 启动失败?原来是“库冲突”惹的祸
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档